diff --git a/.configurations/configuration.dsc.yaml b/.configurations/configuration.dsc.yaml new file mode 100644 index 0000000000..41839b86f2 --- /dev/null +++ b/.configurations/configuration.dsc.yaml @@ -0,0 +1,67 @@ +# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2 +# Reference: https://github.com/microsoft/vscode/wiki/How-to-Contribute +properties: + resources: + - resource: Microsoft.WinGet.DSC/WinGetPackage + directives: + description: Install Git + allowPrerelease: true + settings: + id: Git.Git + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: npm + directives: + description: Install NodeJS version >=16.17.x and <17 + allowPrerelease: true + settings: + id: OpenJS.NodeJS.LTS + version: "16.20.0" + source: winget + - resource: NpmDsc/NpmPackage + id: yarn + dependsOn: + - npm + directives: + description: Install Yarn + allowPrerelease: true + settings: + Name: 'yarn' + Global: true + PackageDirectory: '${WinGetConfigRoot}\..\' + - resource: Microsoft.WinGet.DSC/WinGetPackage + directives: + description: Install Python 3.10 + allowPrerelease: true + settings: + id: Python.Python.3.10 + source: winget + - resource: Microsoft.WinGet.DSC/WinGetPackage + id: vsPackage + directives: + description: Install Visual Studio 2022 (any edition is OK) + allowPrerelease: true + settings: + id: Microsoft.VisualStudio.2022.BuildTools + source: winget + - resource: Microsoft.VisualStudio.DSC/VSComponents + dependsOn: + - vsPackage + directives: + description: Install required VS workloads + allowPrerelease: true + settings: + productId: Microsoft.VisualStudio.Product.BuildTools + channelId: VisualStudio.17.Release + includeRecommended: true + components: + - Microsoft.VisualStudio.Workload.VCTools + - resource: YarnDsc/YarnInstall + dependsOn: + - npm + directives: + description: Install dependencies + allowPrerelease: true + settings: + PackageDirectory: '${WinGetConfigRoot}\..\' + configurationVersion: 0.2.0 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000000..ad241c22eb --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/devcontainers/typescript-node:16-bullseye + +ADD install-vscode.sh /root/ +RUN /root/install-vscode.sh + +RUN git config --system codespaces-theme.hide-status 1 + +USER node +RUN YARN_CACHE="$(yarn cache dir)" && rm -rf "$YARN_CACHE" && ln -s /vscode-dev/yarn-cache "$YARN_CACHE" + +USER root +CMD chown node:node /vscode-dev && sudo -u node mkdir -p /vscode-dev/yarn-cache && sleep inf diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 9050664fce..6522e98aac 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -1,84 +1,32 @@ # Code - OSS Development Container -[![Open in Remote - Containers](https://img.shields.io/static/v1?label=Remote%20-%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) +This dev container includes configuration for a development container for working with Code - OSS in a local container. For using [GitHub Codespaces](https://github.com/features/codespaces) follow the [prebuilt setup](prebuilt/README.md) which installs VNC for displaying the application window. -This repository includes configuration for a development container for working with Code - OSS in a local container or using [GitHub Codespaces](https://github.com/features/codespaces). +> **Note:** You will need X11's `DISPLAY` or Wayland's `WAYLAND_DISPLAY` environment variable set locally to allow for the Code - OSS window to display. See [Running GUI app on WSL](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) for Windows and [Quartz](https://www.xquartz.org) for Mac. -> **Tip:** The default VNC password is `vscode`. The VNC server runs on port `5901` and a web client is available on port `6080`. - -## Quick start - local - -If you already have VS Code and Docker installed, you can click the badge above or [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) to get started. Clicking these links will cause VS Code to automatically install the Remote - Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use. +## Quick start 1. Install Docker Desktop or Docker for Linux on your local machine. (See [docs](https://aka.ms/vscode-remote/containers/getting-started) for additional details.) -2. **Important**: Docker needs at least **4 Cores and 8 GB of RAM** to run a full build. If you are on macOS, or are using the old Hyper-V engine for Windows, update these values for Docker Desktop by right-clicking on the Docker status bar item and going to **Preferences/Settings > Resources > Advanced**. +2. **Important**: Docker needs at least **4 Cores and 8 GB of RAM** to run a full build with **9 GB of RAM** being recommended. If you are on macOS, or are using the old Hyper-V engine for Windows, update these values for Docker Desktop by right-clicking on the Docker status bar item and going to **Preferences/Settings > Resources > Advanced**. > **Note:** The [Resource Monitor](https://marketplace.visualstudio.com/items?itemName=mutantdino.resourcemonitor) extension is included in the container so you can keep an eye on CPU/Memory in the status bar. -3. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the [Remote - Containers](https://aka.ms/vscode-remote/download/containers) extension. +3. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the [Dev Containers](https://aka.ms/vscode-remote/download/containers) extension. - ![Image of Remote - Containers extension](https://microsoft.github.io/vscode-remote-release/images/remote-containers-extn.png) + ![Image of Dev Containers extension](https://microsoft.github.io/vscode-remote-release/images/dev-containers-extn.png) - > **Note:** The Remote - Containers extension requires the Visual Studio Code distribution of Code - OSS. See the [FAQ](https://aka.ms/vscode-remote/faq/license) for details. + > **Note:** The Dev Containers extension requires the Visual Studio Code distribution of Code - OSS. See the [FAQ](https://aka.ms/vscode-remote/faq/license) for details. -4. Press Ctrl/Cmd + Shift + P or F1 and select **Remote-Containers: Clone Repository in Container Volume...**. - - > **Tip:** While you can use your local source tree instead, operations like `yarn install` can be slow on macOS or when using the Hyper-V engine on Windows. We recommend the "clone repository in container" approach instead since it uses "named volume" rather than the local filesystem. - -5. Type `https://github.com/microsoft/vscode` (or a branch or PR URL) in the input box and press Enter. - -6. After the container is running, open a web browser and go to [http://localhost:6080](http://localhost:6080), or use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password. - -Anything you start in VS Code, or the integrated terminal, will appear here. - -Next: **[Try it out!](#try-it)** - -## Quick start - GitHub Codespaces - -1. From the [microsoft/vscode GitHub repository](https://github.com/microsoft/vscode), click on the **Code** dropdown, select **Open with Codespaces**, and then click on **New codespace**. If prompted, select the **Standard** machine size (which is also the default). - - > **Note:** You will not see these options within GitHub if you are not in the Codespaces beta. - -2. After the codespace is up and running in your browser, press Ctrl/Cmd + Shift + P or F1 and select **Ports: Focus on Ports View**. - -3. You should see **VNC web client (6080)** under in the list of ports. Select the line and click on the globe icon to open it in a browser tab. - - > **Tip:** If you do not see the port, Ctrl/Cmd + Shift + P or F1, select **Forward a Port** and enter port `6080`. - -4. In the new tab, you should see noVNC. Click **Connect** and enter `vscode` as the password. - -Anything you start in VS Code, or the integrated terminal, will appear here. - -Next: **[Try it out!](#try-it)** - -### Using VS Code with GitHub Codespaces - -You may see improved VNC responsiveness when accessing a codespace from VS Code client since you can use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/). Here's how to do it. - -1. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the the [GitHub Codespaces extension](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces). - - > **Note:** The GitHub Codespaces extension requires the Visual Studio Code distribution of Code - OSS. - -2. After the VS Code is up and running, press Ctrl/Cmd + Shift + P or F1, choose **Codespaces: Create New Codespace**, and use the following settings: - - `microsoft/vscode` for the repository. - - Select any branch (e.g. **main**) - you can select a different one later. - - Choose **Standard** (4-core, 8GB) as the size. - -4. After you have connected to the codespace, you can use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password. - - > **Tip:** You may also need change your VNC client's **Picture Quality** setting to **High** to get a full color desktop. - -5. Anything you start in VS Code, or the integrated terminal, will appear here. +4. Due to the size of the repository we strongly recommend cloning it on a Linux filesystem for better bind mount performance. On macOS we recommend using a Docker volume (press F1 and select **Dev Containers: Clone Repository in Container Volume...**) and on Windows we recommend using a WSL folder: +- Make sure you are running a recent WSL version to get X11 and Wayland support. +- Use the WSL extension for VS Code to open the cloned folder in WSL. +- Press F1 and select **Dev Containers: Reopen in Container**. Next: **[Try it out!](#try-it)** ## Try it! -This container uses the [Fluxbox](http://fluxbox.org/) window manager to keep things lean. **Right-click on the desktop** to see menu options. It works with GNOME and GTK applications, so other tools can be installed if needed. - -> **Note:** You can also set the resolution from the command line by typing `set-resolution`. - To start working with Code - OSS, follow these steps: 1. In your local VS Code client, open a terminal (Ctrl/Cmd + Shift + \`) and type the following commands: @@ -88,18 +36,20 @@ To start working with Code - OSS, follow these steps: bash scripts/code.sh ``` -2. After the build is complete, open a web browser or a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to the desktop environment as described in the quick start and enter `vscode` as the password. - -3. You should now see Code - OSS! +2. You should now see Code - OSS! Next, let's try debugging. -1. Shut down Code - OSS by clicking the box in the upper right corner of the Code - OSS window through your browser or VNC viewer. +1. Shut down Code - OSS by clicking the box in the upper right corner of the Code - OSS window. 2. Go to your local VS Code client, and use the **Run / Debug** view to launch the **VS Code** configuration. (Typically the default, so you can likely just press F5). - > **Note:** If launching times out, you can increase the value of `timeout` in the "VS Code", "Attach Main Process", "Attach Extension Host", and "Attach to Shared Process" configurations in [launch.json](../.vscode/launch.json). However, running `scripts/code.sh` first will set up Electron which will usually solve timeout issues. + > **Note:** If launching times out, you can increase the value of `timeout` in the "VS Code", "Attach Main Process", "Attach Extension Host", and "Attach to Shared Process" configurations in [launch.json](../../.vscode/launch.json). However, running `scripts/code.sh` first will set up Electron which will usually solve timeout issues. 3. After a bit, Code - OSS will appear with the debugger attached! Enjoy! + +# Notes + +The container comes with VS Code Insiders installed. To run it from an Integrated Terminal use `VSCODE_IPC_HOOK_CLI= /usr/bin/code-insiders .`. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3e40ce61f9..58c0cebd20 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,40 +1,22 @@ { - "name": "Code - OSS", - - // Image contents: https://github.com/microsoft/vscode-dev-containers/blob/master/repository-containers/images/github.com/microsoft/vscode/.devcontainer/base.Dockerfile - "image": "mcr.microsoft.com/vscode/devcontainers/repos/microsoft/vscode:branch-main", + "name": "Code - OSS with X11/Wayland", + "build": { + "dockerfile": "Dockerfile" + }, "overrideCommand": false, - "runArgs": [ "--init", "--security-opt", "seccomp=unconfined", "--shm-size=1g"], - - "settings": { - "resmon.show.battery": false, - "resmon.show.cpufreq": false - }, - - // noVNC, VNC - "forwardPorts": [6080, 5901], - "portsAttributes": { - "6080": { - "label": "VNC web client (noVNC)", - "onAutoForward": "silent" - }, - "5901": { - "label": "VNC TCP port", - "onAutoForward": "silent" + "privileged": true, + "mounts": [ + { + "source": "vscode-dev", + "target": "/vscode-dev", + "type": "volume" } - }, - - "extensions": [ - "dbaeumer.vscode-eslint", - "mutantdino.resourcemonitor" ], - - // Optionally loads a cached yarn install for the repo - "postCreateCommand": ".devcontainer/cache/restore-diff.sh", - - "remoteUser": "node", - - "hostRequirements": { - "memory": "8gb" + "customizations": { + "vscode": { + "extensions": [ + "mutantdino.resourcemonitor" + ] + } } } diff --git a/.devcontainer/install-vscode.sh b/.devcontainer/install-vscode.sh new file mode 100755 index 0000000000..9d4b52755d --- /dev/null +++ b/.devcontainer/install-vscode.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +apt update +apt install -y wget gpg + +wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg +install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg +sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list' +rm -f packages.microsoft.gpg + +apt update +apt install -y code-insiders libsecret-1-dev libxkbfile-dev diff --git a/.devcontainer/prebuilt/README.md b/.devcontainer/prebuilt/README.md new file mode 100644 index 0000000000..82e731230c --- /dev/null +++ b/.devcontainer/prebuilt/README.md @@ -0,0 +1,105 @@ +# Code - OSS Development Container + +[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) + +This repository includes configuration for a development container for working with Code - OSS in a local container or using [GitHub Codespaces](https://github.com/features/codespaces). + +> **Tip:** The default VNC password is `vscode`. The VNC server runs on port `5901` and a web client is available on port `6080`. + +## Quick start - local + +If you already have VS Code and Docker installed, you can click the badge above or [here](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/microsoft/vscode) to get started. Clicking these links will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use. + +1. Install Docker Desktop or Docker for Linux on your local machine. (See [docs](https://aka.ms/vscode-remote/containers/getting-started) for additional details.) + +2. **Important**: Docker needs at least **4 Cores and 8 GB of RAM** to run a full build with **9 GB of RAM** being recommended. If you are on macOS, or are using the old Hyper-V engine for Windows, update these values for Docker Desktop by right-clicking on the Docker status bar item and going to **Preferences/Settings > Resources > Advanced**. + + > **Note:** The [Resource Monitor](https://marketplace.visualstudio.com/items?itemName=mutantdino.resourcemonitor) extension is included in the container so you can keep an eye on CPU/Memory in the status bar. + +3. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the [Dev Containers](https://aka.ms/vscode-remote/download/containers) extension. + + ![Image of Dev Containers extension](https://microsoft.github.io/vscode-remote-release/images/dev-containers-extn.png) + + > **Note:** The Dev Containers extension requires the Visual Studio Code distribution of Code - OSS. See the [FAQ](https://aka.ms/vscode-remote/faq/license) for details. + +4. Press Ctrl/Cmd + Shift + P or F1 and select **Dev Containers: Clone Repository in Container Volume...**. + + > **Tip:** While you can use your local source tree instead, operations like `yarn install` can be slow on macOS or when using the Hyper-V engine on Windows. We recommend the "clone repository in container" approach instead since it uses "named volume" rather than the local filesystem. + +5. Type `https://github.com/microsoft/vscode` (or a branch or PR URL) in the input box and press Enter. + +6. After the container is running, open a web browser and go to [http://localhost:6080](http://localhost:6080), or use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password. + +Anything you start in VS Code, or the integrated terminal, will appear here. + +Next: **[Try it out!](#try-it)** + +## Quick start - GitHub Codespaces + +1. From the [microsoft/vscode GitHub repository](https://github.com/microsoft/vscode), click on the **Code** dropdown, select **Open with Codespaces**, and then click on **New codespace**. If prompted, select the **Standard** machine size (which is also the default). + + > **Note:** You will not see these options within GitHub if you are not in the Codespaces beta. + +2. After the codespace is up and running in your browser, press Ctrl/Cmd + Shift + P or F1 and select **Ports: Focus on Ports View**. + +3. You should see **VNC web client (6080)** under in the list of ports. Select the line and click on the globe icon to open it in a browser tab. + + > **Tip:** If you do not see the port, Ctrl/Cmd + Shift + P or F1, select **Forward a Port** and enter port `6080`. + +4. In the new tab, you should see noVNC. Click **Connect** and enter `vscode` as the password. + +Anything you start in VS Code, or the integrated terminal, will appear here. + +Next: **[Try it out!](#try-it)** + +### Using VS Code with GitHub Codespaces + +You may see improved VNC responsiveness when accessing a codespace from VS Code client since you can use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/). Here's how to do it. + +1. Install [Visual Studio Code Stable](https://code.visualstudio.com/) or [Insiders](https://code.visualstudio.com/insiders/) and the the [GitHub Codespaces extension](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces). + + > **Note:** The GitHub Codespaces extension requires the Visual Studio Code distribution of Code - OSS. + +2. After the VS Code is up and running, press Ctrl/Cmd + Shift + P or F1, choose **Codespaces: Create New Codespace**, and use the following settings: + - `microsoft/vscode` for the repository. + - Select any branch (e.g. **main**) - you can select a different one later. + - Choose **Standard** (4-core, 8GB) as the size. + +4. After you have connected to the codespace, you can use a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to `localhost:5901` and enter `vscode` as the password. + + > **Tip:** You may also need change your VNC client's **Picture Quality** setting to **High** to get a full color desktop. + +5. Anything you start in VS Code, or the integrated terminal, will appear here. + +Next: **[Try it out!](#try-it)** + +## Try it! + +This container uses the [Fluxbox](http://fluxbox.org/) window manager to keep things lean. **Right-click on the desktop** to see menu options. It works with GNOME and GTK applications, so other tools can be installed if needed. + +> **Note:** You can also set the resolution from the command line by typing `set-resolution`. + +To start working with Code - OSS, follow these steps: + +1. In your local VS Code client, open a terminal (Ctrl/Cmd + Shift + \`) and type the following commands: + + ```bash + yarn install + bash scripts/code.sh + ``` + +2. After the build is complete, open a web browser or a [VNC Viewer](https://www.realvnc.com/en/connect/download/viewer/) to connect to the desktop environment as described in the quick start and enter `vscode` as the password. + +3. You should now see Code - OSS! + +Next, let's try debugging. + +1. Shut down Code - OSS by clicking the box in the upper right corner of the Code - OSS window through your browser or VNC viewer. + +2. Go to your local VS Code client, and use the **Run / Debug** view to launch the **VS Code** configuration. (Typically the default, so you can likely just press F5). + + > **Note:** If launching times out, you can increase the value of `timeout` in the "VS Code", "Attach Main Process", "Attach Extension Host", and "Attach to Shared Process" configurations in [launch.json](../../.vscode/launch.json). However, running `scripts/code.sh` first will set up Electron which will usually solve timeout issues. + +3. After a bit, Code - OSS will appear with the debugger attached! + +Enjoy! diff --git a/.devcontainer/cache/before-cache.sh b/.devcontainer/prebuilt/cache/before-cache.sh similarity index 100% rename from .devcontainer/cache/before-cache.sh rename to .devcontainer/prebuilt/cache/before-cache.sh diff --git a/.devcontainer/cache/build-cache-image.sh b/.devcontainer/prebuilt/cache/build-cache-image.sh similarity index 96% rename from .devcontainer/cache/build-cache-image.sh rename to .devcontainer/prebuilt/cache/build-cache-image.sh index 451d1ab45a..96767f079a 100755 --- a/.devcontainer/cache/build-cache-image.sh +++ b/.devcontainer/prebuilt/cache/build-cache-image.sh @@ -17,7 +17,7 @@ fi TAG="branch-${BRANCH//\//-}" echo "[$(date)] ${BRANCH} => ${TAG}" -cd "${SCRIPT_PATH}/../.." +cd "${SCRIPT_PATH}/../../.." echo "[$(date)] Starting image build and push..." export DOCKER_BUILDKIT=1 diff --git a/.devcontainer/cache/cache-diff.sh b/.devcontainer/prebuilt/cache/cache-diff.sh similarity index 100% rename from .devcontainer/cache/cache-diff.sh rename to .devcontainer/prebuilt/cache/cache-diff.sh diff --git a/.devcontainer/cache/cache.Dockerfile b/.devcontainer/prebuilt/cache/cache.Dockerfile similarity index 86% rename from .devcontainer/cache/cache.Dockerfile rename to .devcontainer/prebuilt/cache/cache.Dockerfile index 217122a4e9..26dbc46f43 100644 --- a/.devcontainer/cache/cache.Dockerfile +++ b/.devcontainer/prebuilt/cache/cache.Dockerfile @@ -9,9 +9,9 @@ COPY --chown=${USERNAME}:${USERNAME} . /repo-source-tmp/ RUN mkdir -p ${CACHE_FOLDER} && chown ${USERNAME} ${CACHE_FOLDER} /repo-source-tmp \ && su ${USERNAME} -c "\ cd /repo-source-tmp \ - && .devcontainer/cache/before-cache.sh . ${CACHE_FOLDER} \ - && .devcontainer/prepare.sh . ${CACHE_FOLDER} \ - && .devcontainer/cache/cache-diff.sh . ${CACHE_FOLDER}" + && .devcontainer/prebuilt/cache/before-cache.sh . ${CACHE_FOLDER} \ + && .devcontainer/prebuilt/prepare.sh . ${CACHE_FOLDER} \ + && .devcontainer/prebuilt/cache/cache-diff.sh . ${CACHE_FOLDER}" # This second stage starts fresh and just copies in cache.tar from the previous stage. The related # devcontainer.json file is then setup to have postCreateCommand fire restore-diff.sh to expand it. diff --git a/.devcontainer/cache/restore-diff.sh b/.devcontainer/prebuilt/cache/restore-diff.sh similarity index 100% rename from .devcontainer/cache/restore-diff.sh rename to .devcontainer/prebuilt/cache/restore-diff.sh diff --git a/.devcontainer/prebuilt/devcontainer.json b/.devcontainer/prebuilt/devcontainer.json new file mode 100644 index 0000000000..079b8de6cd --- /dev/null +++ b/.devcontainer/prebuilt/devcontainer.json @@ -0,0 +1,44 @@ +{ + "name": "Code - OSS with VNC", + + // Image contents: https://github.com/microsoft/vscode-dev-containers/blob/master/repository-containers/images/github.com/microsoft/vscode/.devcontainer/base.Dockerfile + "image": "mcr.microsoft.com/vscode/devcontainers/repos/microsoft/vscode:branch-main", + "overrideCommand": false, + "runArgs": [ "--init", "--security-opt", "seccomp=unconfined", "--shm-size=1g"], + + // VS Code extensions and settings + "customizations": { + "vscode": { + "settings": { + "resmon.show.battery": false, + "resmon.show.cpufreq": false + }, + "extensions": [ + "dbaeumer.vscode-eslint", + "mutantdino.resourcemonitor" + ] + } + }, + + // noVNC, VNC + "forwardPorts": [6080, 5901], + "portsAttributes": { + "6080": { + "label": "VNC web client (noVNC)", + "onAutoForward": "silent" + }, + "5901": { + "label": "VNC TCP port", + "onAutoForward": "silent" + } + }, + + // Optionally loads a cached yarn install for the repo + "postCreateCommand": ".devcontainer/prebuilt/cache/restore-diff.sh", + + "remoteUser": "node", + + "hostRequirements": { + "memory": "9gb" + } +} diff --git a/.devcontainer/prepare.sh b/.devcontainer/prebuilt/prepare.sh similarity index 92% rename from .devcontainer/prepare.sh rename to .devcontainer/prebuilt/prepare.sh index 9b5c81ff40..3f82462cfd 100755 --- a/.devcontainer/prepare.sh +++ b/.devcontainer/prebuilt/prepare.sh @@ -5,5 +5,5 @@ # running commands like "yarn install" from the ground up. Developers (and should) still run these commands # after the actual dev container is created, but only differences will be processed. -yarn install +yarn install --network-timeout 180000 yarn electron diff --git a/.eslintplugin/code-declare-service-brand.ts b/.eslintplugin/code-declare-service-brand.ts new file mode 100644 index 0000000000..d6ce2b5209 --- /dev/null +++ b/.eslintplugin/code-declare-service-brand.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; + +export = new class DeclareServiceBrand implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + fixable: 'code' + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + ['PropertyDefinition[key.name="_serviceBrand"][value]']: (node: any) => { + return context.report({ + node, + message: `The '_serviceBrand'-property should not have a value`, + fix: (fixer) => { + return fixer.replaceText(node, 'declare _serviceBrand: undefined;') + } + }); + } + }; + } +}; diff --git a/build/lib/eslint/code-import-patterns.ts b/.eslintplugin/code-import-patterns.ts similarity index 95% rename from build/lib/eslint/code-import-patterns.ts rename to .eslintplugin/code-import-patterns.ts index 96a024c8d5..8b67aafcb3 100644 --- a/build/lib/eslint/code-import-patterns.ts +++ b/.eslintplugin/code-import-patterns.ts @@ -6,10 +6,10 @@ import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as path from 'path'; -import * as minimatch from 'minimatch'; +import minimatch from 'minimatch'; import { createImportRuleListener } from './utils'; -const REPO_ROOT = path.normalize(path.join(__dirname, '../../../')); +const REPO_ROOT = path.normalize(path.join(__dirname, '../')); interface ConditionalPattern { when?: 'hasBrowser' | 'hasNode' | 'test'; @@ -18,7 +18,7 @@ interface ConditionalPattern { interface RawImportPatternsConfig { target: string; - layer?: 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-browser' | 'electron-main'; + layer?: 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-main'; test?: boolean; restrictions: string | (string | ConditionalPattern)[]; } @@ -77,7 +77,7 @@ export = new class implements eslint.Rule.RuleModule { return this._optionsCache.get(options)!; } - type Layer = 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-browser' | 'electron-main'; + type Layer = 'common' | 'worker' | 'browser' | 'electron-sandbox' | 'node' | 'electron-main'; interface ILayerRule { layer: Layer; @@ -96,7 +96,6 @@ export = new class implements eslint.Rule.RuleModule { { layer: 'browser', deps: orSegment(['common', 'browser']), isBrowser: true }, { layer: 'electron-sandbox', deps: orSegment(['common', 'browser', 'electron-sandbox']), isBrowser: true }, { layer: 'node', deps: orSegment(['common', 'node']), isNode: true }, - { layer: 'electron-browser', deps: orSegment(['common', 'browser', 'node', 'electron-sandbox', 'electron-browser']), isBrowser: true, isNode: true }, { layer: 'electron-main', deps: orSegment(['common', 'node', 'electron-main']), isNode: true }, ]; diff --git a/build/lib/eslint/code-layering.ts b/.eslintplugin/code-layering.ts similarity index 100% rename from build/lib/eslint/code-layering.ts rename to .eslintplugin/code-layering.ts diff --git a/build/lib/eslint/code-no-look-behind-regex.ts b/.eslintplugin/code-no-look-behind-regex.ts similarity index 100% rename from build/lib/eslint/code-no-look-behind-regex.ts rename to .eslintplugin/code-no-look-behind-regex.ts diff --git a/build/lib/eslint/code-no-nls-in-standalone-editor.ts b/.eslintplugin/code-no-nls-in-standalone-editor.ts similarity index 100% rename from build/lib/eslint/code-no-nls-in-standalone-editor.ts rename to .eslintplugin/code-no-nls-in-standalone-editor.ts diff --git a/build/lib/eslint/code-no-standalone-editor.ts b/.eslintplugin/code-no-standalone-editor.ts similarity index 100% rename from build/lib/eslint/code-no-standalone-editor.ts rename to .eslintplugin/code-no-standalone-editor.ts diff --git a/build/lib/eslint/code-no-test-only.ts b/.eslintplugin/code-no-test-only.ts similarity index 79% rename from build/lib/eslint/code-no-test-only.ts rename to .eslintplugin/code-no-test-only.ts index 6a1102fb38..d01255a214 100644 --- a/build/lib/eslint/code-no-test-only.ts +++ b/.eslintplugin/code-no-test-only.ts @@ -9,10 +9,10 @@ export = new class NoTestOnly implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['MemberExpression[object.name="test"][property.name="only"]']: (node: any) => { + ['MemberExpression[object.name=/^(test|suite)$/][property.name="only"]']: (node: any) => { return context.report({ node, - message: 'test.only is a dev-time tool and CANNOT be pushed' + message: 'only is a dev-time tool and CANNOT be pushed' }); } }; diff --git a/build/lib/eslint/code-no-unexternalized-strings.ts b/.eslintplugin/code-no-unexternalized-strings.ts similarity index 81% rename from build/lib/eslint/code-no-unexternalized-strings.ts rename to .eslintplugin/code-no-unexternalized-strings.ts index a0cacc63fc..328e4b9fbf 100644 --- a/build/lib/eslint/code-no-unexternalized-strings.ts +++ b/.eslintplugin/code-no-unexternalized-strings.ts @@ -85,6 +85,27 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { } } + function visitL10NCall(node: TSESTree.CallExpression) { + + // localize(key, message) + const [messageNode] = (node).arguments; + + // remove message-argument from doubleQuoted list and make + // sure it is a string-literal + if (isStringLiteral(messageNode)) { + doubleQuotedStringLiterals.delete(messageNode); + } else if (messageNode.type === AST_NODE_TYPES.ObjectExpression) { + for (const prop of messageNode.properties) { + if (prop.type === AST_NODE_TYPES.Property) { + if (prop.key.type === AST_NODE_TYPES.Identifier && prop.key.name === 'message') { + doubleQuotedStringLiterals.delete(prop.value); + break; + } + } + } + } + } + function reportBadStringsAndBadKeys() { // (1) // report all strings that are in double quotes @@ -117,7 +138,16 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { return { ['Literal']: (node: any) => collectDoubleQuotedStrings(node), ['ExpressionStatement[directive] Literal:exit']: (node: any) => doubleQuotedStringLiterals.delete(node), + + // localize(...) ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node: any) => visitLocalizeCall(node), + + // vscode.l10n.t(...) + ['CallExpression[callee.type="MemberExpression"][callee.object.property.name="l10n"][callee.property.name="t"]:exit']: (node: any) => visitL10NCall(node), + + // l10n.t(...) + ['CallExpression[callee.object.name="l10n"][callee.property.name="t"]:exit']: (node: any) => visitL10NCall(node), + ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node), ['Program:exit']: reportBadStringsAndBadKeys, }; diff --git a/build/lib/eslint/code-no-unused-expressions.ts b/.eslintplugin/code-no-unused-expressions.ts similarity index 100% rename from build/lib/eslint/code-no-unused-expressions.ts rename to .eslintplugin/code-no-unused-expressions.ts diff --git a/build/lib/eslint/code-translation-remind.ts b/.eslintplugin/code-translation-remind.ts similarity index 100% rename from build/lib/eslint/code-translation-remind.ts rename to .eslintplugin/code-translation-remind.ts diff --git a/.eslintplugin/index.js b/.eslintplugin/index.js new file mode 100644 index 0000000000..9f45316837 --- /dev/null +++ b/.eslintplugin/index.js @@ -0,0 +1,12 @@ +const glob = require('glob'); +const path = require('path'); + +require('ts-node').register({ experimentalResolver: true, transpileOnly: true }); + +// Re-export all .ts files as rules +const rules = {}; +glob.sync(`${__dirname}/*.ts`).forEach((file) => { + rules[path.basename(file, '.ts')] = require(file); +}); + +exports.rules = rules; diff --git a/.eslintplugin/tsconfig.json b/.eslintplugin/tsconfig.json new file mode 100644 index 0000000000..0da715fd03 --- /dev/null +++ b/.eslintplugin/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": [ + "ES2020" + ], + "module": "commonjs", + "esModuleInterop": true, + "alwaysStrict": true, + "allowJs": true, + "strict": true, + "exactOptionalPropertyTypes": false, + "useUnknownInCatchVariables": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "newLine": "lf", + "noEmit": true + }, + "include": [ + "**/*.ts", + "**/*.js" + ], + "exclude": [ + "node_modules/**" + ] +} diff --git a/build/lib/eslint/utils.ts b/.eslintplugin/utils.ts similarity index 100% rename from build/lib/eslint/utils.ts rename to .eslintplugin/utils.ts diff --git a/build/lib/eslint/vscode-dts-cancellation.ts b/.eslintplugin/vscode-dts-cancellation.ts similarity index 100% rename from build/lib/eslint/vscode-dts-cancellation.ts rename to .eslintplugin/vscode-dts-cancellation.ts diff --git a/build/lib/eslint/vscode-dts-create-func.ts b/.eslintplugin/vscode-dts-create-func.ts similarity index 100% rename from build/lib/eslint/vscode-dts-create-func.ts rename to .eslintplugin/vscode-dts-create-func.ts diff --git a/build/lib/eslint/vscode-dts-event-naming.ts b/.eslintplugin/vscode-dts-event-naming.ts similarity index 100% rename from build/lib/eslint/vscode-dts-event-naming.ts rename to .eslintplugin/vscode-dts-event-naming.ts diff --git a/build/lib/eslint/vscode-dts-interface-naming.ts b/.eslintplugin/vscode-dts-interface-naming.ts similarity index 96% rename from build/lib/eslint/vscode-dts-interface-naming.ts rename to .eslintplugin/vscode-dts-interface-naming.ts index 01f18b621c..f01045a98c 100644 --- a/build/lib/eslint/vscode-dts-interface-naming.ts +++ b/.eslintplugin/vscode-dts-interface-naming.ts @@ -8,7 +8,7 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; export = new class ApiInterfaceNaming implements eslint.Rule.RuleModule { - private static _nameRegExp = /I[A-Z]/; + private static _nameRegExp = /^I[A-Z]/; readonly meta: eslint.Rule.RuleMetaData = { messages: { diff --git a/build/lib/eslint/vscode-dts-literal-or-types.ts b/.eslintplugin/vscode-dts-literal-or-types.ts similarity index 100% rename from build/lib/eslint/vscode-dts-literal-or-types.ts rename to .eslintplugin/vscode-dts-literal-or-types.ts diff --git a/build/lib/eslint/vscode-dts-provider-naming.ts b/.eslintplugin/vscode-dts-provider-naming.ts similarity index 100% rename from build/lib/eslint/vscode-dts-provider-naming.ts rename to .eslintplugin/vscode-dts-provider-naming.ts diff --git a/build/lib/eslint/vscode-dts-region-comments.ts b/.eslintplugin/vscode-dts-region-comments.ts similarity index 100% rename from build/lib/eslint/vscode-dts-region-comments.ts rename to .eslintplugin/vscode-dts-region-comments.ts diff --git a/.eslintplugin/vscode-dts-string-type-literals.ts b/.eslintplugin/vscode-dts-string-type-literals.ts new file mode 100644 index 0000000000..3d8ca06f54 --- /dev/null +++ b/.eslintplugin/vscode-dts-string-type-literals.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; + +export = new class ApiTypeDiscrimination implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines' }, + messages: { + noTypeDiscrimination: 'Do not use type descrimination properties' + } + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + ['TSPropertySignature[optional=undefined] TSTypeAnnotation TSLiteralType Literal']: (node: any) => { + + const raw = String((node).raw) + + if (/^('|").*\1$/.test(raw)) { + + context.report({ + node: node, + messageId: 'noTypeDiscrimination' + }); + } + } + } + } +}; diff --git a/build/lib/eslint/vscode-dts-use-thenable.ts b/.eslintplugin/vscode-dts-use-thenable.ts similarity index 100% rename from build/lib/eslint/vscode-dts-use-thenable.ts rename to .eslintplugin/vscode-dts-use-thenable.ts diff --git a/build/lib/eslint/vscode-dts-vscode-in-comments.ts b/.eslintplugin/vscode-dts-vscode-in-comments.ts similarity index 100% rename from build/lib/eslint/vscode-dts-vscode-in-comments.ts rename to .eslintplugin/vscode-dts-vscode-in-comments.ts diff --git a/.eslintrc.json b/.eslintrc.json index 481e18997c..045929033b 100755 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,7 +8,8 @@ "plugins": [ "@typescript-eslint", "jsdoc", - "header" + "header", + "local" ], "rules": { "no-undef": "off", @@ -55,17 +56,18 @@ ] } ], - "code-no-unused-expressions": [ + "local/code-no-unused-expressions": [ "off", { "allowTernary": true } ], "code-translation-remind": "off", - "code-no-nls-in-standalone-editor": "warn", - "code-no-standalone-editor": "warn", + "local/code-no-nls-in-standalone-editor": "warn", + "local/code-no-standalone-editor": "warn", "code-no-unexternalized-strings": "off", - "code-layering": [ + "local/code-declare-service-brand": "warn", + "local/code-layering": [ "warn", { "common": [], @@ -79,12 +81,6 @@ "common", "browser" ], - "electron-browser": [ - "common", - "browser", - "node", - "electron-sandbox" - ], "electron-main": [ "common", "node" @@ -1066,9 +1062,9 @@ "**/sql/**" ], "rules": { - "no-sync": "warn", + "code-no-test-only": "off", "strict": ["warn", "never"], - "no-console": "warn" + "code-no-unexternalized-strings": "off" } }, { @@ -1077,14 +1073,15 @@ "**/vscode.proposed.d.ts" ], "rules": { - "vscode-dts-create-func": "warn", - "vscode-dts-literal-or-types": "warn", - "vscode-dts-interface-naming": "warn", - "vscode-dts-cancellation": "warn", - "vscode-dts-use-thenable": "warn", - "vscode-dts-region-comments": "warn", - "vscode-dts-vscode-in-comments": "warn", - "vscode-dts-provider-naming": [ + "local/vscode-dts-create-func": "warn", + "local/vscode-dts-literal-or-types": "warn", + "local/vscode-dts-string-type-literals": "warn", + "local/vscode-dts-interface-naming": "warn", + "local/vscode-dts-cancellation": "warn", + "local/vscode-dts-use-thenable": "warn", + "local/vscode-dts-region-comments": "warn", + "local/vscode-dts-vscode-in-comments": "warn", + "local/vscode-dts-provider-naming": [ "warn", { "allowed": [ @@ -1099,7 +1096,7 @@ ] } ], - "vscode-dts-event-naming": [ + "local/vscode-dts-event-naming": [ "warn", { "allowed": [ @@ -1123,6 +1120,7 @@ "invalidate", "open", "override", + "perform", "receive", "register", "remove", @@ -1139,6 +1137,425 @@ ] } }, + { + "files": [ + "src/**/*.ts" + ], + "rules": { + "local/code-no-look-behind-regex": "warn", + "local/code-import-patterns": [ + "off", + { + // imports that are allowed in all files of layers: + // - browser + // - electron-sandbox + "when": "hasBrowser", + "allow": [ + "vs/css!./**/*" + ] + }, + { + // imports that are allowed in all files of layers: + // - node + // - electron-main + "when": "hasNode", + "allow": [ + "@parcel/watcher", + "@vscode/sqlite3", + "@vscode/vscode-languagedetection", + "@vscode/ripgrep", + "@vscode/iconv-lite-umd", + "@vscode/policy-watcher", + "@vscode/proxy-agent", + "@vscode/spdlog", + "@vscode/windows-process-tree", + "assert", + "child_process", + "console", + "cookie", + "crypto", + "electron", + "events", + "fs", + "graceful-fs", + "http", + "https", + "minimist", + "native-keymap", + "native-watchdog", + "net", + "node-pty", + "os", + "path", + "perf_hooks", + "stream", + "string_decoder", + "tas-client-umd", + "tls", + "url", + "util", + "v8-inspect-profiler", + "vscode-regexpp", + "vscode-textmate", + "worker_threads", + "xterm", + "xterm-addon-canvas", + "xterm-addon-image", + "xterm-addon-search", + "xterm-addon-serialize", + "xterm-addon-unicode11", + "xterm-addon-webgl", + "xterm-headless", + "yauzl", + "yazl", + "zlib" + ] + }, + { + // imports that are allowed in all /test/ files + "when": "test", + "allow": [ + "vs/css.build", + "assert", + "sinon", + "sinon-test" + ] + }, + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // !!! Do not relax these rules !!! + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // + // A path ending in /~ has a special meaning. It indicates a template position + // which will be substituted with one or more layers. + // + // When /~ is used in the target, the rule will be expanded to 14 distinct rules. + // e.g. "src/vs/base/~" will be expanded to: + // - src/vs/base/common + // - src/vs/base/worker + // - src/vs/base/browser + // - src/vs/base/electron-sandbox + // - src/vs/base/node + // - src/vs/base/electron-main + // - src/vs/base/test/common + // - src/vs/base/test/worker + // - src/vs/base/test/browser + // - src/vs/base/test/electron-sandbox + // - src/vs/base/test/node + // - src/vs/base/test/electron-main + // + // When /~ is used in the restrictions, it will be replaced with the correct + // layers that can be used e.g. "src/vs/base/electron-sandbox" will be able + // to import "{common,browser,electron-sanbox}", etc. + // + // It is possible to use /~ in the restrictions property even without using it in + // the target property by adding a layer property. + { + "target": "src/vs/base/~", + "restrictions": [ + "vs/base/~" + ] + }, + { + "target": "src/vs/base/parts/*/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~" + ] + }, + { + "target": "src/vs/platform/*/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "tas-client-umd", // node module allowed even in /common/ + "@microsoft/1ds-core-js", // node module allowed even in /common/ + "@microsoft/1ds-post-js" // node module allowed even in /common/ + ] + }, + { + "target": "src/vs/editor/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~" + ] + }, + { + "target": "src/vs/editor/contrib/*/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~" + ] + }, + { + "target": "src/vs/editor/standalone/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/editor/standalone/~" + ] + }, + { + "target": "src/vs/editor/editor.all.ts", + "layer": "browser", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~" + ] + }, + { + "target": "src/vs/editor/editor.worker.ts", + "layer": "worker", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~" + ] + }, + { + "target": "src/vs/editor/{editor.api.ts,editor.main.ts}", + "layer": "browser", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/editor/standalone/~", + "vs/editor/*" + ] + }, + { + "target": "src/vs/workbench/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/workbench/~", + "vs/workbench/services/*/~", + "assert", + { + "when": "test", + "pattern": "vs/workbench/contrib/*/~" + } // TODO@layers + ] + }, + { + "target": "src/vs/workbench/api/~", + "restrictions": [ + "vscode", + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/workbench/api/~", + "vs/workbench/~", + "vs/workbench/services/*/~", + "vs/workbench/contrib/*/~", + "vs/workbench/contrib/terminalContrib/*/~" + ] + }, + { + "target": "src/vs/workbench/services/*/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/workbench/~", + "vs/workbench/services/*/~", + { + "when": "test", + "pattern": "vs/workbench/contrib/*/~" + }, // TODO@layers + "tas-client-umd", // node module allowed even in /common/ + "vscode-textmate", // node module allowed even in /common/ + "@vscode/vscode-languagedetection" // node module allowed even in /common/ + ] + }, + { + "target": "src/vs/workbench/contrib/*/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/workbench/~", + "vs/workbench/services/*/~", + "vs/workbench/contrib/*/~", + "vscode-notebook-renderer", // Type only import + { + "when": "hasBrowser", + "pattern": "xterm" + }, // node module allowed even in /browser/ + { + "when": "hasBrowser", + "pattern": "xterm-addon-*" + }, // node module allowed even in /browser/ + { + "when": "hasBrowser", + "pattern": "vscode-textmate" + } // node module allowed even in /browser/ + ] + }, + { + "target": "src/vs/workbench/contrib/terminalContrib/*/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/workbench/~", + "vs/workbench/services/*/~", + "vs/workbench/contrib/*/~", + // Only allow terminalContrib to import from itself, this works because + // terminalContrib is one extra folder deep + "vs/workbench/contrib/terminalContrib/*/~", + "vscode-notebook-renderer", // Type only import + { + "when": "hasBrowser", + "pattern": "xterm" + }, // node module allowed even in /browser/ + { + "when": "hasBrowser", + "pattern": "xterm-addon-*" + }, // node module allowed even in /browser/ + { + "when": "hasBrowser", + "pattern": "vscode-textmate" + } // node module allowed even in /browser/ + ] + }, + { + "target": "src/vs/code/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/code/~", + { + "when": "hasBrowser", + "pattern": "vs/workbench/workbench.web.main" + }, + { + "when": "hasBrowser", + "pattern": "vs/workbench/~" + }, + { + "when": "hasBrowser", + "pattern": "vs/workbench/services/*/~" + } + ] + }, + { + "target": "src/vs/server/~", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/workbench/~", + "vs/workbench/api/~", + "vs/workbench/services/*/~", + "vs/workbench/contrib/*/~", + "vs/server/~" + ] + }, + { + "target": "src/vs/workbench/contrib/terminal/terminal.all.ts", + "layer": "browser", + "restrictions": [ + "vs/workbench/contrib/**" + ] + }, + { + "target": "src/vs/workbench/workbench.common.main.ts", + "layer": "browser", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/editor/editor.all", + "vs/workbench/~", + "vs/workbench/api/~", + "vs/workbench/services/*/~", + "vs/workbench/contrib/*/~", + "vs/workbench/contrib/terminal/terminal.all" + ] + }, + { + "target": "src/vs/workbench/workbench.web.main.ts", + "layer": "browser", + "restrictions": [ + "vs/base/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/editor/editor.all", + "vs/workbench/~", + "vs/workbench/api/~", + "vs/workbench/services/*/~", + "vs/workbench/contrib/*/~", + "vs/workbench/workbench.common.main" + ] + }, + { + "target": "src/vs/workbench/workbench.desktop.main.ts", + "layer": "electron-sandbox", + "restrictions": [ + "vs/base/*/~", + "vs/base/parts/*/~", + "vs/platform/*/~", + "vs/editor/~", + "vs/editor/contrib/*/~", + "vs/editor/editor.all", + "vs/workbench/~", + "vs/workbench/api/~", + "vs/workbench/services/*/~", + "vs/workbench/contrib/*/~", + "vs/workbench/workbench.common.main" + ] + }, + { + "target": "src/vs/workbench/{workbench.desktop.main.nls.js,workbench.web.main.nls.js}", + "restrictions": [] + }, + { + "target": "src/vs/{loader.d.ts,css.ts,css.build.ts,monaco.d.ts,nls.ts,nls.build.ts,nls.mock.ts}", + "restrictions": [] + }, + { + "target": "src/vscode-dts/**", + "restrictions": [] + }, + { + "target": "src/{bootstrap-amd.js,bootstrap-fork.js,bootstrap-node.js,bootstrap-window.js,bootstrap.js,cli.js,main.js,server-cli.js,server-main.js}", + "restrictions": [] + } + ] + } + }, { "files": [ "src/{vs,sql}/server/*", @@ -1182,3 +1599,4 @@ } ] } + diff --git a/.git-blame-ignore b/.git-blame-ignore index 24b19f36c3..457d2604f3 100644 --- a/.git-blame-ignore +++ b/.git-blame-ignore @@ -15,6 +15,9 @@ ae1452eea678f5266ef513f22dacebb90955d6c9 # joaomoreno: add ghooks dev dependency 0dfc06e0f9de5925de792cdf9f0e6597bb25908f +# joaomoreno: line endings +12ab70d329a13dd5b18d892cd40edd7138259bc3 + # mjbvz: organize imports 494cbbd02d67e87727ec885f98d19551aa33aad1 a3cb14be7f2cceadb17adf843675b1a59537dbbd diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..5ace4600a1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/bad-tag.yml b/.github/workflows/bad-tag.yml index e996639dfb..ba4e0524cc 100644 --- a/.github/workflows/bad-tag.yml +++ b/.github/workflows/bad-tag.yml @@ -8,7 +8,7 @@ jobs: if: github.event.ref == '1.999.0' steps: - name: Checkout Actions - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: "microsoft/vscode-github-triage-actions" ref: stable diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml new file mode 100644 index 0000000000..3bfaf8117c --- /dev/null +++ b/.github/workflows/telemetry.yml @@ -0,0 +1,19 @@ +name: 'Telemetry' +on: + pull_request: +jobs: + check-metdata: + name: 'Check metadata' + runs-on: 'ubuntu-latest' + + steps: + - uses: 'actions/checkout@v3' + + - uses: 'actions/setup-node@v3' + with: + node-version: 'lts/*' + + - name: 'Run vscode-telemetry-extractor' + run: 'npx --package=@vscode/telemetry-extractor --yes vscode-telemetry-extractor -s .' + env: + GITHUB_TOKEN: ${{secrets.VSCODE_ISSUE_TRIAGE_BOT_PAT}} diff --git a/.gitignore b/.gitignore index 74504885b3..6ae20a83e4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ vscode.lsif vscode.db /.profile-oss *.orig +/cli/target +product.overrides.json diff --git a/.nvmrc b/.nvmrc index 0cf077e6b4..5cb297e3e4 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.14 +16.17 diff --git a/.vscode/cglicenses.schema.json b/.vscode/cglicenses.schema.json index 8c0ee74010..8131a35217 100644 --- a/.vscode/cglicenses.schema.json +++ b/.vscode/cglicenses.schema.json @@ -55,6 +55,31 @@ } } } + }, + { + "type": "object", + "required": [ + "name", + "fullLicenseTextUri" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the dependency" + }, + "fullLicenseTextUri": { + "type": "string", + "description": "The URI to the license text of this repository", + "format": "uri" + }, + "prependLicenseText": { + "type": "array", + "description": "A piece of text to prepend to the auto-detected license text of the dependency", + "items": { + "type": "string" + } + } + } } ] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 20d53a66c6..0d3101d30a 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,6 +4,8 @@ "recommendations": [ "dbaeumer.vscode-eslint", "EditorConfig.EditorConfig", + "GitHub.vscode-pull-request-github", + "ms-vscode.vscode-github-issue-notebooks", "ms-vscode.vscode-selfhost-test-provider" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 479d4863bb..d3fce1224d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "program": "${workspaceFolder}/node_modules/gulp/bin/gulp.js", "stopOnEntry": true, "args": [ - "hygiene" + "vscode-win32-x64" ] }, { diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 6ba33aa8ae..b32c51eaae 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"July 2022\"" + "value": "$repo=repo:microsoft/vscode\n$milestone=milestone:\"May 2023\"" }, { "kind": 1, diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index d637ade8f2..64658b950f 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-unpkg\n\n$MILESTONE=milestone:\"July 2022\"" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"May 2023\"" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS -$MILESTONE is:issue is:closed label:bug label:insiders-released -label:verified -label:*duplicate -label:*as-designed -label:z-author-verified -label:on-testplan" + "value": "$REPOS -$MILESTONE is:issue is:closed reason:completed label:bug label:insiders-released -label:verified -label:*duplicate -label:*as-designed -label:z-author-verified -label:on-testplan" }, { "kind": 1, @@ -42,7 +42,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS -$MILESTONE is:issue is:closed label:feature-request label:insiders-released -label:on-testplan -label:verified -label:*duplicate" + "value": "$REPOS -$MILESTONE is:issue is:closed reason:completed label:feature-request label:insiders-released -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, @@ -62,7 +62,7 @@ { "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" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, @@ -97,7 +97,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed label:verification-needed -label:verified" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed label:verification-needed -label:verified" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed sort:updated-asc 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 -label:z-author-verified -label:unreleased" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified -label:unreleased" }, { "kind": 1, @@ -122,7 +122,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed sort:updated-asc 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 -label:z-author-verified label:unreleased" + "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified label:unreleased" }, { "kind": 1, diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 25b9625ef3..ffb68cb37a 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remotehub repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal\n\n$MILESTONE=milestone:\"July 2022\"\n\n$MINE=assignee:@me" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n\n$MILESTONE=milestone:\"May 2023\"\n\n$MINE=assignee:@me" }, { "kind": 1, @@ -42,7 +42,7 @@ { "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" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, @@ -62,7 +62,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed label:feature-request label:verification-needed -label:verified" + "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request label:verification-needed -label:verified" }, { "kind": 1, @@ -87,7 +87,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed -label:verification-steps-needed -label:unreleased" }, { "kind": 1, @@ -147,7 +147,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found" }, { "kind": 1, @@ -157,7 +157,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -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:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:dynamicwebpaige -author:eamodio -author:egamma -author:fiveisprime -author:greazer -author:gregvanl -author:hediet -author:IanMatthewHuff -author:isidorn -author:ItalyPaleAle -author:JacksonKearl -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr-author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:ornellaalt -author:orta -author:rchiodo -author:rebornix -author:roblourens -author:rzhao271 -author:sana-ajani -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:kimadeline -author:amunger" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -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:andreamah -author:bamurtaugh -author:bpasero -author:chrisdias -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:gregvanl -author:hediet -author:isidorn -author:joaomoreno -author:joyceerhl -author:jrieken -author:karrtikr -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar" }, { "kind": 1, @@ -167,7 +167,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found" + "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found" }, { "kind": 1, @@ -177,6 +177,6 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode $MILESTONE $MINE is:issue is:closed label:feature-request -label:on-release-notes" + "value": "repo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\nrepo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\nrepo:microsoft/vscode $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 84f67029d3..8e4a9942c4 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce\n\n// current milestone name\n$milestone=milestone:\"July 2022\"" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-markdown-languageservice\n\n// current milestone name\n$milestone=milestone:\"May 2023\"" }, { "kind": 1, @@ -94,7 +94,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"needs more info\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry" + "value": "$repos assignee:@me is:open type:issue -label:bug -label:\"info-needed\" -label:feature-request -label:under-discussion -label:debt -label:plan-item -label:upstream -label:polish -label:testplan-item -label:error-telemetry" }, { "kind": 1, @@ -104,7 +104,17 @@ { "kind": 2, "language": "github-issues", - "value": "repo:microsoft/vscode assignee:@me is:open type:issue -label:\"needs more info\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:code-lens -label:color-palette -label:comments -label:config -label:context-keys -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor -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-RTL -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:engineering -label:error-list -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-io -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:gpu -label:grammar -label:grid-view -label:html -label:i18n -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-window -label:ipc -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:L10N -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list -label:live-server -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:network -label:notebook -label:notebook-api -label:notebook-celltoolbar -label:notebook-diff -label:notebook-dnd -label:notebook-folding -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-keybinding -label:notebook-layout -label:notebook-markdown -label:notebook-minimap -label:notebook-multiselect -label:notebook-output -label:notebook-perf -label:notebook-statusbar -label:open-editors -label:opener -label:outline -label:output -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remotehub -label:rename -label:sandbox -label:sash -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:table -label:tasks -label:telemetry -label:terminal -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-links -label:terminal-local-echo -label:terminal-profiles -label:terminal-reconnection -label:terminal-rendering -label:terminal-tabs -label:terminal-winpty -label:testing -label:themes -label:timeline -label:timeline-git -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typehierarchy -label:typescript -label:undo-redo -label:uri -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-build -label:vscode-website -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -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-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom" + "value": "repo:microsoft/vscode assignee:@me is:open type:issue -label:\"info-needed\" -label:api -label:api-finalization -label:api-proposal -label:authentication -label:bisect-ext -label:bracket-pair-colorization -label:bracket-pair-guides -label:breadcrumbs -label:callhierarchy -label:chrome-devtools -label:code-lens -label:color-palette -label:comments -label:config -label:context-keys -label:css-less-scss -label:custom-editors -label:debug -label:debug-disassembly -label:dialogs -label:diff-editor -label:dropdown -label:editor -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-RTL -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:engineering -label:error-list -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-io -label:file-watcher -label:font-rendering -label:formatting -label:getting-started -label:ghost-text -label:git -label:github -label:gpu -label:grammar -label:grid-view -label:html -label:i18n -label:icon-brand -label:icons-product -label:image-preview -label:inlay-hints -label:inline-completions -label:install-update -label:intellisense-config -label:interactive-window -label:ipc -label:issue-bot -label:issue-reporter -label:javascript -label:json -label:keybindings -label:keybindings-editor -label:keyboard-layout -label:L10N -label:label-provider -label:languages-basic -label:languages-diagnostics -label:languages-guessing -label:layout -label:lcd-text-rendering -label:list -label:live-server -label:log -label:markdown -label:marketplace -label:menus -label:merge-conflict -label:network -label:notebook -label:notebook-api -label:notebook-celltoolbar -label:notebook-diff -label:notebook-dnd -label:notebook-folding -label:notebook-globaltoolbar -label:notebook-ipynb -label:notebook-kernel -label:notebook-keybinding -label:notebook-layout -label:notebook-markdown -label:notebook-minimap -label:notebook-multiselect -label:notebook-output -label:notebook-perf -label:notebook-statusbar -label:open-editors -label:opener -label:outline -label:output -label:perf -label:perf-bloat -label:perf-startup -label:php -label:portable-mode -label:proxy -label:quick-open -label:quick-pick -label:references-viewlet -label:release-notes -label:remote -label:remote-connection -label:remote-explorer -label:remotehub -label:rename -label:sandbox -label:sash -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:table -label:tasks -label:telemetry -label:terminal -label:terminal-conpty -label:terminal-editors -label:terminal-external -label:terminal-links -label:terminal-local-echo -label:terminal-profiles -label:terminal-reconnection -label:terminal-rendering -label:terminal-tabs -label:terminal-winpty -label:testing -label:themes -label:timeline -label:timeline-git -label:titlebar -label:tokenization -label:touch/pointer -label:trackpad/scroll -label:tree-views -label:tree-widget -label:typehierarchy -label:typescript -label:undo-redo -label:uri -label:ux -label:variable-resolving -label:VIM -label:virtual-workspaces -label:vscode-build -label:vscode-website -label:web -label:webview -label:webview-views -label:workbench-actions -label:workbench-cli -label:workbench-diagnostics -label:workbench-dnd -label:workbench-editor-grid -label:workbench-editor-groups -label:workbench-editor-resolver -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-untitled-editors -label:workbench-views -label:workbench-welcome -label:workbench-window -label:workbench-zen -label:workspace-edit -label:workspace-symbols -label:workspace-trust -label:zoom" + }, + { + "kind": 1, + "language": "markdown", + "value": "### Missing Milestone" + }, + { + "kind": 2, + "language": "github-issues", + "value": "$repos assignee:@me is:open type:issue no:milestone -label:info-needed -label:triage-needed" }, { "kind": 1, @@ -140,5 +150,15 @@ "kind": 2, "language": "github-issues", "value": "$repos author:@me is:open is:pr review:required" + }, + { + "kind": 1, + "language": "markdown", + "value": "âš ï¸ Changes Requested" + }, + { + "kind": 2, + "language": "github-issues", + "value": "$repos author:@me is:open is:pr review:changes_requested" } ] \ No newline at end of file diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index 7015a401be..15cf60b17d 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -2,7 +2,7 @@ { "kind": 1, "language": "markdown", - "value": "### Bug Verification Queries\n\nBefore shipping we want to verify _all_ bugs. That means when a bug is fixed we check that the fix actually works. It's always best to start with bugs that you have filed and the proceed with bugs that have been filed from users outside the development team. " + "value": "### Bug Verification Queries\r\n\r\nBefore shipping we want to verify _all_ bugs. That means when a bug is fixed we check that the fix actually works. It's always best to start with bugs that you have filed and the proceed with bugs that have been filed from users outside the development team. " }, { "kind": 1, @@ -12,7 +12,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev 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-emmet-helper repo:microsoft/vscode-jupyter repo:microsoft/vscode-python\n$milestone=milestone:\"May 2022\"" + "value": "$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-dev repo:microsoft/vscode-unpkg repo:microsoft/vscode-references-view repo:microsoft/vscode-anycode repo:microsoft/vscode-hexeditor repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-livepreview repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-remote-repositories-github repo:microsoft/monaco-editor repo:microsoft/vscode-vsce repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-livepreview repo:microsoft/vscode-livepreview repo:microsoft/vscode-python repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-l10n repo:microsoft/vscode-remote-tunnels\n$milestone=milestone:\"February 2023\"" }, { "kind": 1, @@ -22,7 +22,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate author:@me" + "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate author:@me" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate -author:@me -assignee:@me label:bug -label:verified -author:@me -author:aeschli -author:alexdima -author:alexr00 -author:bpasero -author:chrisdias -author:chrmarti -author:connor4312 -author:dbaeumer -author:deepak1556 -author:eamodio -author:egamma -author:gregvanl -author:isidorn -author:JacksonKearl -author:joaomoreno -author:jrieken -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:RMacfarlane -author:roblourens -author:sana-ajani -author:sandy081 -author:sbatten -author:Tyriar -author:weinand -author:rzhao271 -author:kieferrm -author:TylerLeonhardt -author:bamurtaugh -author:hediet -author:joyceerhl -author:rchiodo -author:IanMatthewHuff" + "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate -author:@me -assignee:@me label:bug -label:verified -author:@me -author:aeschli -author:alexdima -author:alexr00 -author:bpasero -author:chrisdias -author:chrmarti -author:connor4312 -author:dbaeumer -author:deepak1556 -author:eamodio -author:egamma -author:gregvanl -author:isidorn -author:JacksonKearl -author:joaomoreno -author:jrieken -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:RMacfarlane -author:roblourens -author:sana-ajani -author:sandy081 -author:sbatten -author:Tyriar -author:weinand -author:rzhao271 -author:kieferrm -author:TylerLeonhardt -author:bamurtaugh -author:hediet -author:joyceerhl -author:rchiodo" }, { "kind": 1, @@ -42,6 +42,6 @@ { "kind": 2, "language": "github-issues", - "value": "$repos $milestone is:closed -assignee:@me label:bug -label:verified -label:*duplicate" + "value": "$repos $milestone is:closed reason:completed -assignee:@me label:bug -label:verified -label:*duplicate" } ] \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 718cfd2205..f5ed543e0a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,27 +6,44 @@ ".build": true, ".profile-oss": true, "**/.DS_Store": true, + "cli/target": true, "build/**/*.js": { "when": "$(basename).ts" } }, "files.associations": { - "cglicenses.json": "jsonc" + "cglicenses.json": "jsonc", + "*.tst": "typescript" }, "search.exclude": { "**/node_modules": true, - "**/bower_components": true, + "cli/target/**": true, ".build/**": true, "out/**": true, "out-build/**": true, "out-vscode/**": true, "i18n/**": true, + "extensions/**/dist/**": true, "extensions/**/out/**": true, "test/smoke/out/**": true, "test/automation/out/**": true, "test/integration/browser/out/**": true, + "src/vs/base/test/common/filters.perf.data.js": true, "src/vs/base/test/node/uri.test.data.txt": true, - "src/vs/workbench/api/test/browser/extHostDocumentData.test.perf-data.ts": true + "src/vs/workbench/api/test/browser/extHostDocumentData.test.perf-data.ts": true, + "src/vs/editor/test/node/diffing/fixtures/**": true, + }, + "files.readonlyInclude": { + "**/node_modules/**": true, + "out/**": true, + "out-build/**": true, + "out-vscode/**": true, + "out-vscode-reh/**": true, + "extensions/**/dist/**": true, + "extensions/**/out/**": true, + "test/smoke/out/**": true, + "test/automation/out/**": true, + "test/integration/browser/out/**": true, }, "lcov.path": [ "./.build/coverage/lcov.info", @@ -41,11 +58,6 @@ } } ], - "eslint.options": { - "rulePaths": [ - "./build/lib/eslint" - ] - }, "typescript.tsdk": "node_modules/typescript/lib", "npm.exclude": "**/extensions/**", "npm.packageManager": "yarn", @@ -57,7 +69,7 @@ "fileMatch": [ "cgmanifest.json" ], - "url": "https://json.schemastore.org/component-detection-manifest.json" + "url": "./.vscode/cgmanifest.schema.json" }, { "fileMatch": [ @@ -69,10 +81,12 @@ "git.ignoreLimitWarning": true, "git.branchProtection": [ "main", + "distro", "release/*" ], "git.branchProtectionPrompt": "alwaysCommitToNewBranch", "git.branchRandomName.enable": true, + "git.pullBeforeCheckout": true, "git.mergeEditor": true, "remote.extensionKind": { "msjsdiag.debugger-for-chrome": "workspace" @@ -90,6 +104,13 @@ "editor.defaultFormatter": "vscode.typescript-language-features", "editor.formatOnSave": true }, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true, + }, + "rust-analyzer.linkedProjects": [ + "cli/Cargo.toml" + ], "typescript.tsc.autoDetect": "off", "testing.autoRun.mode": "rerun", "conventionalCommits.scopes": [ @@ -102,12 +123,18 @@ "git", "sash" ], - "editor.quickSuggestions": { - "other": "inline", - "comments": "inline", - "strings": "inline" - }, - "yaml.schemas": { - "https://raw.githubusercontent.com/microsoft/azure-pipelines-vscode/master/service-schema.json": "build/azure-pipelines/**/*.yml" + "debug.javascript.terminalOptions": { + "outFiles": [ + "${workspaceFolder}/out/**/*.js", + "${workspaceFolder}/build/**/*.js" + ] }, + "githubPullRequests.assignCreated": "${user}", + "githubPullRequests.defaultMergeMethod": "squash", + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ], + "application.experimental.rendererProfiling": true, + "editor.experimental.asyncTokenization": true, + "editor.experimental.asyncTokenizationVerification": true } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ef8e3bf538..cb81d1572f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -203,6 +203,28 @@ "reveal": "never" } }, + { + "type": "shell", + "command": "./scripts/code-web.sh", + "windows": { + "command": ".\\scripts\\code-web.bat" + }, + "args": ["--port", "8080", "--browser", "none"], + "label": "Run code web", + "isBackground": true, + "problemMatcher": { + "pattern": { + "regexp": "" + }, + "background": { + "beginsPattern": ".*node .*", + "endsPattern": "Listening on .*" + } + }, + "presentation": { + "reveal": "never" + } + }, { "type": "npm", "script": "eslint", diff --git a/.yarnrc b/.yarnrc index 6d4029fb88..2e1d808197 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,4 +1,4 @@ disturl "https://electronjs.org/headers" -target "19.1.8" +target "22.3.10" runtime "electron" build_from_source "true" diff --git a/build/.cachesalt b/build/.cachesalt index c0d4fa40fc..e41ee49d6d 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2022-07-19T07:55:26.168Z +2023-03-31T12:39:03.753Z diff --git a/build/.moduleignore b/build/.moduleignore index e018f82e09..c8230083f4 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -12,6 +12,14 @@ fsevents/src/** fsevents/test/** !fsevents/**/*.node +@vscode/spdlog/binding.gyp +@vscode/spdlog/build/** +@vscode/spdlog/deps/** +@vscode/spdlog/src/** +@vscode/spdlog/test/** +@vscode/spdlog/*.yml +!@vscode/spdlog/build/Release/*.node + @vscode/sqlite3/binding.gyp @vscode/sqlite3/benchmark/** @vscode/sqlite3/cloudformation/** @@ -21,10 +29,20 @@ fsevents/test/** @vscode/sqlite3/src/** !@vscode/sqlite3/build/Release/*.node -windows-mutex/binding.gyp -windows-mutex/build/** -windows-mutex/src/** -!windows-mutex/**/*.node +@vscode/windows-mutex/binding.gyp +@vscode/windows-mutex/build/** +@vscode/windows-mutex/src/** +!@vscode/windows-mutex/**/*.node + +@vscode/windows-process-tree/binding.gyp +@vscode/windows-process-tree/build/** +@vscode/windows-process-tree/src/** +!@vscode/windows-process-tree/**/*.node + +@vscode/windows-registry/binding.gyp +@vscode/windows-registry/src/** +@vscode/windows-registry/build/** +!@vscode/windows-registry/build/Release/*.node native-keymap/binding.gyp native-keymap/build/** @@ -43,26 +61,16 @@ 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/** +node-vsce-sign/** +!node-vsce-sign/src/main.js +!node-vsce-sign/package.json +!node-vsce-sign/bin/** 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/** @@ -73,9 +81,11 @@ keytar/node_modules/** node-pty/binding.gyp node-pty/build/** node-pty/src/** +node-pty/lib/*.test.js node-pty/tools/** node-pty/deps/** node-pty/scripts/** +!node-pty/build/Release/spawn-helper !node-pty/build/Release/*.exe !node-pty/build/Release/*.dll !node-pty/build/Release/*.node @@ -104,18 +114,6 @@ kerberos/build/** # END SQL Modules -nsfw/binding.gyp -nsfw/build/** -nsfw/src/** -nsfw/includes/** -!nsfw/build/Release/*.node - -vscode-nsfw/binding.gyp -vscode-nsfw/build/** -vscode-nsfw/src/** -vscode-nsfw/includes/** -!vscode-nsfw/build/Release/*.node - @parcel/watcher/binding.gyp @parcel/watcher/build/** @parcel/watcher/prebuilds/** @@ -128,6 +126,7 @@ vsda/src/** vsda/.gitignore vsda/binding.gyp vsda/README.md +vsda/SECURITY.md vsda/targets !vsda/build/Release/vsda.node @@ -139,19 +138,20 @@ vscode-encrypt/binding.gyp vscode-encrypt/README.md !vscode-encrypt/build/Release/vscode-encrypt-native.node -vscode-policy-watcher/build/** -vscode-policy-watcher/.husky/** -vscode-policy-watcher/src/** -vscode-policy-watcher/binding.gyp -vscode-policy-watcher/README.md -vscode-policy-watcher/index.d.ts -!vscode-policy-watcher/build/Release/vscode-policy-watcher.node +@vscode/policy-watcher/build/** +@vscode/policy-watcher/.husky/** +@vscode/policy-watcher/src/** +@vscode/policy-watcher/binding.gyp +@vscode/policy-watcher/README.md +@vscode/policy-watcher/index.d.ts +!@vscode/policy-watcher/build/Release/vscode-policy-watcher.node -vscode-windows-ca-certs/**/* -!vscode-windows-ca-certs/package.json -!vscode-windows-ca-certs/**/*.node +@vscode/windows-ca-certs/**/* +!@vscode/windows-ca-certs/package.json +!@vscode/windows-ca-certs/**/*.node node-addon-api/**/* +prebuild-install/**/* # other node modules @@ -166,11 +166,22 @@ node-addon-api/**/* **/README.md **/readme.md **/readme.markdown +**/CODE_OF_CONDUCT.md +**/SUPPORT.md +**/CONTRIBUTING.md **/*.ts -!typescript/**/*.d.ts -jschardet/dist/** +# Exclude TS files that aren't needed by TS extension +typescript/lib/tsc.js +typescript/lib/typescriptServices.js +typescript/lib/tsserverlibrary.js +# We still need to include stdlib d.ts +!typescript/lib/lib*.d.ts + +jschardet/index.js +jschardet/src/** +jschardet/dist/jschardet.js es6-promise/lib/** @@ -189,5 +200,3 @@ xterm-addon-*/src/** xterm-addon-*/fixtures/** xterm-addon-*/out/** xterm-addon-*/out-test/** - - diff --git a/build/.webignore b/build/.webignore index fc1d206764..8d7e36713a 100644 --- a/build/.webignore +++ b/build/.webignore @@ -20,6 +20,12 @@ vscode-textmate/webpack.config.js xterm/src/** +xterm-addon-canvas/src/** +xterm-addon-canvas/out/** + +xterm-addon-image/src/** +xterm-addon-image/out/** + xterm-addon-search/src/** xterm-addon-search/out/** xterm-addon-search/fixtures/** @@ -32,3 +38,15 @@ xterm-addon-webgl/out/** # This makes sure the model is included in the package !@vscode/vscode-languagedetection/model/** +# Ensure only the required telemetry pieces are loaded in web to reduce bundle size +@microsoft/1ds-core-js/** +@microsoft/1ds-post-js/** +@microsoft/applicationinsights-core-js/** +@microsoft/applicationinsights-shims/** +!@microsoft/1ds-core-js/dist/ms.core.min.js +!@microsoft/1ds-post-js/dist/ms.post.min.js +!@microsoft/applicationinsights-core-js/browser/applicationinsights-core-js.min.js +!@microsoft/applicationinsights-shims/dist/umd/applicationinsights-shims.min.js + +vsda/** +!vsda/rust/web/** diff --git a/build/actions/api/api.js b/build/actions/api/api.js index bda3e6b964..ce6fe87ffb 100644 --- a/build/actions/api/api.js +++ b/build/actions/api/api.js @@ -4,3 +4,4 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRyJ9 \ No newline at end of file diff --git a/build/actions/api/octokit.js b/build/actions/api/octokit.js index 865fe5f710..da802ab1d1 100644 --- a/build/actions/api/octokit.js +++ b/build/actions/api/octokit.js @@ -10,13 +10,16 @@ const github_1 = require("@actions/github"); const child_process_1 = require("child_process"); const utils_1 = require("../utils/utils"); class OctoKit { + token; + params; + options; + octokit; + // when in readonly mode, record labels just-created so at to not throw unneccesary errors + mockLabels = new Set(); constructor(token, params, options = { readonly: false }) { this.token = token; this.params = params; this.options = options; - // when in readonly mode, record labels just-created so at to not throw unneccesary errors - this.mockLabels = new Set(); - this.writeAccessCache = {}; this.octokit = new github_1.GitHub(token); } async *query(query) { @@ -41,18 +44,17 @@ class OctoKit { }; for await (const pageResponse of this.octokit.paginate.iterator(options)) { await timeout(); - await utils_1.logRateLimit(this.token); + await (0, utils_1.logRateLimit)(this.token); const page = pageResponse.data; yield page.map((issue) => new OctoKitIssue(this.token, this.params, this.octokitIssueToIssue(issue))); } } async createIssue(owner, repo, title, body) { - core_1.debug(`Creating issue \`${title}\` on ${owner}/${repo}`); + (0, core_1.debug)(`Creating issue \`${title}\` on ${owner}/${repo}`); if (!this.options.readonly) await this.octokit.issues.create({ owner, repo, title, body }); } octokitIssueToIssue(issue) { - var _a, _b, _c, _d, _e, _f; return { author: { name: issue.user.login, isGitHubApp: issue.user.type === 'Bot' }, body: issue.body, @@ -63,19 +65,20 @@ class OctoKit { locked: issue.locked, numComments: issue.comments, reactions: issue.reactions, - assignee: (_b = (_a = issue.assignee) === null || _a === void 0 ? void 0 : _a.login) !== null && _b !== void 0 ? _b : (_d = (_c = issue.assignees) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.login, - milestoneId: (_f = (_e = issue.milestone) === null || _e === void 0 ? void 0 : _e.number) !== null && _f !== void 0 ? _f : null, + assignee: issue.assignee?.login ?? issue.assignees?.[0]?.login, + milestoneId: issue.milestone?.number ?? null, createdAt: +new Date(issue.created_at), updatedAt: +new Date(issue.updated_at), closedAt: issue.closed_at ? +new Date(issue.closed_at) : undefined, }; } + writeAccessCache = {}; async hasWriteAccess(user) { if (user.name in this.writeAccessCache) { - core_1.debug('Got permissions from cache for ' + user); + (0, core_1.debug)('Got permissions from cache for ' + user); return this.writeAccessCache[user.name]; } - core_1.debug('Fetching permissions for ' + user); + (0, core_1.debug)('Fetching permissions for ' + user); const permissions = (await this.octokit.repos.getCollaboratorPermissionLevel({ ...this.params, username: user.name, @@ -95,14 +98,14 @@ class OctoKit { } } async createLabel(name, color, description) { - core_1.debug('Creating label ' + name); + (0, core_1.debug)('Creating label ' + name); if (!this.options.readonly) await this.octokit.issues.createLabel({ ...this.params, color, description, name }); else this.mockLabels.add(name); } async deleteLabel(name) { - core_1.debug('Deleting label ' + name); + (0, core_1.debug)('Deleting label ' + name); try { if (!this.options.readonly) await this.octokit.issues.deleteLabel({ ...this.params, name }); @@ -115,7 +118,7 @@ class OctoKit { } } async readConfig(path) { - core_1.debug('Reading config at ' + path); + (0, core_1.debug)('Reading config at ' + path); const repoPath = `.github/${path}.json`; const data = (await this.octokit.repos.getContents({ ...this.params, path: repoPath })).data; if ('type' in data && data.type === 'file') { @@ -127,21 +130,23 @@ class OctoKit { throw Error('Found directory at config path when expecting file' + JSON.stringify(data)); } async releaseContainsCommit(release, commit) { - if (utils_1.getInput('commitReleasedDebuggingOverride')) { + if ((0, utils_1.getInput)('commitReleasedDebuggingOverride')) { return true; } - return new Promise((resolve, reject) => child_process_1.exec(`git -C ./repo merge-base --is-ancestor ${commit} ${release}`, (err) => !err || err.code === 1 ? resolve(!err) : reject(err))); + return new Promise((resolve, reject) => (0, child_process_1.exec)(`git -C ./repo merge-base --is-ancestor ${commit} ${release}`, (err) => !err || err.code === 1 ? resolve(!err) : reject(err))); } } exports.OctoKit = OctoKit; class OctoKitIssue extends OctoKit { + params; + issueData; constructor(token, params, issueData, options = { readonly: false }) { super(token, params, options); this.params = params; this.issueData = issueData; } async addAssignee(assignee) { - core_1.debug('Adding assignee ' + assignee + ' to ' + this.issueData.number); + (0, core_1.debug)('Adding assignee ' + assignee + ' to ' + this.issueData.number); if (!this.options.readonly) { await this.octokit.issues.addAssignees({ ...this.params, @@ -151,7 +156,7 @@ class OctoKitIssue extends OctoKit { } } async closeIssue() { - core_1.debug('Closing issue ' + this.issueData.number); + (0, core_1.debug)('Closing issue ' + this.issueData.number); if (!this.options.readonly) await this.octokit.issues.update({ ...this.params, @@ -160,13 +165,13 @@ class OctoKitIssue extends OctoKit { }); } async lockIssue() { - core_1.debug('Locking issue ' + this.issueData.number); + (0, core_1.debug)('Locking issue ' + this.issueData.number); if (!this.options.readonly) await this.octokit.issues.lock({ ...this.params, issue_number: this.issueData.number }); } async getIssue() { if (isIssue(this.issueData)) { - core_1.debug('Got issue data from query result ' + this.issueData.number); + (0, core_1.debug)('Got issue data from query result ' + this.issueData.number); return this.issueData; } const issue = (await this.octokit.issues.get({ @@ -177,7 +182,7 @@ class OctoKitIssue extends OctoKit { return (this.issueData = this.octokitIssueToIssue(issue)); } async postComment(body) { - core_1.debug(`Posting comment ${body} on ${this.issueData.number}`); + (0, core_1.debug)(`Posting comment ${body} on ${this.issueData.number}`); if (!this.options.readonly) await this.octokit.issues.createComment({ ...this.params, @@ -186,7 +191,7 @@ class OctoKitIssue extends OctoKit { }); } async deleteComment(id) { - core_1.debug(`Deleting comment ${id} on ${this.issueData.number}`); + (0, core_1.debug)(`Deleting comment ${id} on ${this.issueData.number}`); if (!this.options.readonly) await this.octokit.issues.deleteComment({ owner: this.params.owner, @@ -195,7 +200,7 @@ class OctoKitIssue extends OctoKit { }); } async setMilestone(milestoneId) { - core_1.debug(`Setting milestone for ${this.issueData.number} to ${milestoneId}`); + (0, core_1.debug)(`Setting milestone for ${this.issueData.number} to ${milestoneId}`); if (!this.options.readonly) await this.octokit.issues.update({ ...this.params, @@ -204,7 +209,7 @@ class OctoKitIssue extends OctoKit { }); } async *getComments(last) { - core_1.debug('Fetching comments for ' + this.issueData.number); + (0, core_1.debug)('Fetching comments for ' + this.issueData.number); const response = this.octokit.paginate.iterator(this.octokit.issues.listComments.endpoint.merge({ ...this.params, issue_number: this.issueData.number, @@ -221,7 +226,7 @@ class OctoKitIssue extends OctoKit { } } async addLabel(name) { - core_1.debug(`Adding label ${name} to ${this.issueData.number}`); + (0, core_1.debug)(`Adding label ${name} to ${this.issueData.number}`); if (!(await this.repoHasLabel(name))) { throw Error(`Action could not execute becuase label ${name} is not defined.`); } @@ -233,7 +238,7 @@ class OctoKitIssue extends OctoKit { }); } async removeLabel(name) { - core_1.debug(`Removing label ${name} from ${this.issueData.number}`); + (0, core_1.debug)(`Removing label ${name} from ${this.issueData.number}`); try { if (!this.options.readonly) await this.octokit.issues.removeLabel({ @@ -250,7 +255,6 @@ class OctoKitIssue extends OctoKit { } } async getClosingInfo() { - var _a; if ((await this.getIssue()).open) { return; } @@ -264,7 +268,7 @@ class OctoKitIssue extends OctoKit { for (const timelineEvent of timelineEvents) { if (timelineEvent.event === 'closed') { closingCommit = { - hash: (_a = timelineEvent.commit_id) !== null && _a !== void 0 ? _a : undefined, + hash: timelineEvent.commit_id ?? undefined, timestamp: +new Date(timelineEvent.created_at), }; } @@ -287,3 +291,4 @@ function isIssue(object) { 'milestoneId' in object; return isIssue; } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2N0b2tpdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm9jdG9raXQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsd0NBQXFDO0FBQ3JDLDRDQUFxRDtBQUVyRCxpREFBb0M7QUFDcEMsMENBQXVEO0FBR3ZELE1BQWEsT0FBTztJQU1WO0lBQ0U7SUFDQTtJQVBELE9BQU8sQ0FBVztJQUM1QiwwRkFBMEY7SUFDaEYsVUFBVSxHQUFnQixJQUFJLEdBQUcsRUFBRSxDQUFBO0lBRTdDLFlBQ1MsS0FBYSxFQUNYLE1BQXVDLEVBQ3ZDLFVBQWlDLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRTtRQUZ0RCxVQUFLLEdBQUwsS0FBSyxDQUFRO1FBQ1gsV0FBTSxHQUFOLE1BQU0sQ0FBaUM7UUFDdkMsWUFBTyxHQUFQLE9BQU8sQ0FBNkM7UUFFOUQsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLGVBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQTtJQUNwQyxDQUFDO0lBRUQsS0FBSyxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQVk7UUFDeEIsTUFBTSxDQUFDLEdBQUcsS0FBSyxDQUFDLENBQUMsR0FBRyxTQUFTLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUE7UUFFcEUsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMscUJBQXFCLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQztZQUN4RSxHQUFHLEtBQUs7WUFDUixDQUFDO1lBQ0QsUUFBUSxFQUFFLEdBQUc7WUFDYixPQUFPLEVBQUUsRUFBRSxNQUFNLEVBQUUsbURBQW1ELEVBQUU7U0FDeEUsQ0FBQyxDQUFBO1FBRUYsSUFBSSxPQUFPLEdBQUcsQ0FBQyxDQUFBO1FBRWYsTUFBTSxPQUFPLEdBQUcsS0FBSyxJQUFJLEVBQUU7WUFDMUIsSUFBSSxPQUFPLEdBQUcsQ0FBQyxFQUFFO2dCQUNoQixVQUFVO2FBQ1Y7aUJBQU0sSUFBSSxPQUFPLEdBQUcsQ0FBQyxFQUFFO2dCQUN2QixNQUFNLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUE7YUFDekQ7aUJBQU07Z0JBQ04sTUFBTSxJQUFJLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsVUFBVSxDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFBO2FBQzFEO1FBQ0YsQ0FBQyxDQUFBO1FBRUQsSUFBSSxLQUFLLEVBQUUsTUFBTSxZQUFZLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ3pFLE1BQU0sT0FBTyxFQUFFLENBQUE7WUFDZixNQUFNLElBQUEsb0JBQVksRUFBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUE7WUFDOUIsTUFBTSxJQUFJLEdBQWdFLFlBQVksQ0FBQyxJQUFJLENBQUE7WUFDM0YsTUFBTSxJQUFJLENBQUMsR0FBRyxDQUNiLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxJQUFJLFlBQVksQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLG1CQUFtQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQ3JGLENBQUE7U0FDRDtJQUNGLENBQUM7SUFFRCxLQUFLLENBQUMsV0FBVyxDQUFDLEtBQWEsRUFBRSxJQUFZLEVBQUUsS0FBYSxFQUFFLElBQVk7UUFDekUsSUFBQSxZQUFLLEVBQUMsb0JBQW9CLEtBQUssU0FBUyxLQUFLLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQTtRQUN4RCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRO1lBQUUsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFBO0lBQzNGLENBQUM7SUFFUyxtQkFBbUIsQ0FDNUIsS0FBdUY7UUFFdkYsT0FBTztZQUNOLE1BQU0sRUFBRSxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSSxDQUFDLEtBQUssRUFBRSxXQUFXLEVBQUUsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEtBQUssS0FBSyxFQUFFO1lBQzFFLElBQUksRUFBRSxLQUFLLENBQUMsSUFBSTtZQUNoQixNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07WUFDcEIsS0FBSyxFQUFFLEtBQUssQ0FBQyxLQUFLO1lBQ2xCLE1BQU0sRUFBRyxLQUFLLENBQUMsTUFBMkMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUM7WUFDckYsSUFBSSxFQUFFLEtBQUssQ0FBQyxLQUFLLEtBQUssTUFBTTtZQUM1QixNQUFNLEVBQUcsS0FBYSxDQUFDLE1BQU07WUFDN0IsV0FBVyxFQUFFLEtBQUssQ0FBQyxRQUFRO1lBQzNCLFNBQVMsRUFBRyxLQUFhLENBQUMsU0FBUztZQUNuQyxRQUFRLEVBQUUsS0FBSyxDQUFDLFFBQVEsRUFBRSxLQUFLLElBQUssS0FBYSxDQUFDLFNBQVMsRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLEtBQUs7WUFDdkUsV0FBVyxFQUFFLEtBQUssQ0FBQyxTQUFTLEVBQUUsTUFBTSxJQUFJLElBQUk7WUFDNUMsU0FBUyxFQUFFLENBQUMsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQztZQUN0QyxTQUFTLEVBQUUsQ0FBQyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDO1lBQ3RDLFFBQVEsRUFBRSxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFFLEtBQUssQ0FBQyxTQUErQixDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDekYsQ0FBQTtJQUNGLENBQUM7SUFFTyxnQkFBZ0IsR0FBNEIsRUFBRSxDQUFBO0lBQ3RELEtBQUssQ0FBQyxjQUFjLENBQUMsSUFBVTtRQUM5QixJQUFJLElBQUksQ0FBQyxJQUFJLElBQUksSUFBSSxDQUFDLGdCQUFnQixFQUFFO1lBQ3ZDLElBQUEsWUFBSyxFQUFDLGlDQUFpQyxHQUFHLElBQUksQ0FBQyxDQUFBO1lBQy9DLE9BQU8sSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQTtTQUN2QztRQUNELElBQUEsWUFBSyxFQUFDLDJCQUEyQixHQUFHLElBQUksQ0FBQyxDQUFBO1FBQ3pDLE1BQU0sV0FBVyxHQUFHLENBQ25CLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsOEJBQThCLENBQUM7WUFDdkQsR0FBRyxJQUFJLENBQUMsTUFBTTtZQUNkLFFBQVEsRUFBRSxJQUFJLENBQUMsSUFBSTtTQUNuQixDQUFDLENBQ0YsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFBO1FBQ2pCLE9BQU8sQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLFdBQVcsS0FBSyxPQUFPLElBQUksV0FBVyxLQUFLLE9BQU8sQ0FBQyxDQUFBO0lBQy9GLENBQUM7SUFFRCxLQUFLLENBQUMsWUFBWSxDQUFDLElBQVk7UUFDOUIsSUFBSTtZQUNILE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLEVBQUUsR0FBRyxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7WUFDNUQsT0FBTyxJQUFJLENBQUE7U0FDWDtRQUFDLE9BQU8sR0FBRyxFQUFFO1lBQ2IsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLEdBQUcsRUFBRTtnQkFDdkIsT0FBTyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQTthQUN6RDtZQUNELE1BQU0sR0FBRyxDQUFBO1NBQ1Q7SUFDRixDQUFDO0lBRUQsS0FBSyxDQUFDLFdBQVcsQ0FBQyxJQUFZLEVBQUUsS0FBYSxFQUFFLFdBQW1CO1FBQ2pFLElBQUEsWUFBSyxFQUFDLGlCQUFpQixHQUFHLElBQUksQ0FBQyxDQUFBO1FBQy9CLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVE7WUFDekIsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUMsRUFBRSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFBOztZQUMvRSxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQTtJQUMvQixDQUFDO0lBRUQsS0FBSyxDQUFDLFdBQVcsQ0FBQyxJQUFZO1FBQzdCLElBQUEsWUFBSyxFQUFDLGlCQUFpQixHQUFHLElBQUksQ0FBQyxDQUFBO1FBQy9CLElBQUk7WUFDSCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRO2dCQUFFLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLEVBQUUsR0FBRyxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7U0FDM0Y7UUFBQyxPQUFPLEdBQUcsRUFBRTtZQUNiLElBQUksR0FBRyxDQUFDLE1BQU0sS0FBSyxHQUFHLEVBQUU7Z0JBQ3ZCLE9BQU07YUFDTjtZQUNELE1BQU0sR0FBRyxDQUFBO1NBQ1Q7SUFDRixDQUFDO0lBRUQsS0FBSyxDQUFDLFVBQVUsQ0FBQyxJQUFZO1FBQzVCLElBQUEsWUFBSyxFQUFDLG9CQUFvQixHQUFHLElBQUksQ0FBQyxDQUFBO1FBQ2xDLE1BQU0sUUFBUSxHQUFHLFdBQVcsSUFBSSxPQUFPLENBQUE7UUFDdkMsTUFBTSxJQUFJLEdBQUcsQ0FBQyxNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxFQUFFLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQTtRQUU1RixJQUFJLE1BQU0sSUFBSSxJQUFJLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUU7WUFDM0MsSUFBSSxJQUFJLENBQUMsUUFBUSxLQUFLLFFBQVEsSUFBSSxJQUFJLENBQUMsT0FBTyxFQUFFO2dCQUMvQyxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLFFBQVEsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFBO2FBQ3hFO1lBQ0QsTUFBTSxLQUFLLENBQUMsNEJBQTRCLElBQUksQ0FBQyxPQUFPLGtCQUFrQixJQUFJLENBQUMsUUFBUSxHQUFHLENBQUMsQ0FBQTtTQUN2RjtRQUNELE1BQU0sS0FBSyxDQUFDLG9EQUFvRCxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQTtJQUN6RixDQUFDO0lBRUQsS0FBSyxDQUFDLHFCQUFxQixDQUFDLE9BQWUsRUFBRSxNQUFjO1FBQzFELElBQUksSUFBQSxnQkFBUSxFQUFDLGlDQUFpQyxDQUFDLEVBQUU7WUFDaEQsT0FBTyxJQUFJLENBQUE7U0FDWDtRQUNELE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUUsQ0FDdEMsSUFBQSxvQkFBSSxFQUFDLDBDQUEwQyxNQUFNLElBQUksT0FBTyxFQUFFLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRSxDQUMzRSxDQUFDLEdBQUcsSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FDcEQsQ0FDRCxDQUFBO0lBQ0YsQ0FBQztDQUNEO0FBOUlELDBCQThJQztBQUVELE1BQWEsWUFBYSxTQUFRLE9BQU87SUFHN0I7SUFDRjtJQUhULFlBQ0MsS0FBYSxFQUNILE1BQXVDLEVBQ3pDLFNBQXFDLEVBQzdDLFVBQWlDLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRTtRQUVwRCxLQUFLLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQTtRQUpuQixXQUFNLEdBQU4sTUFBTSxDQUFpQztRQUN6QyxjQUFTLEdBQVQsU0FBUyxDQUE0QjtJQUk5QyxDQUFDO0lBRUQsS0FBSyxDQUFDLFdBQVcsQ0FBQyxRQUFnQjtRQUNqQyxJQUFBLFlBQUssRUFBQyxrQkFBa0IsR0FBRyxRQUFRLEdBQUcsTUFBTSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDckUsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFO1lBQzNCLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDO2dCQUN0QyxHQUFHLElBQUksQ0FBQyxNQUFNO2dCQUNkLFlBQVksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU07Z0JBQ25DLFNBQVMsRUFBRSxDQUFDLFFBQVEsQ0FBQzthQUNyQixDQUFDLENBQUE7U0FDRjtJQUNGLENBQUM7SUFFRCxLQUFLLENBQUMsVUFBVTtRQUNmLElBQUEsWUFBSyxFQUFDLGdCQUFnQixHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDL0MsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUTtZQUN6QixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQztnQkFDaEMsR0FBRyxJQUFJLENBQUMsTUFBTTtnQkFDZCxZQUFZLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNO2dCQUNuQyxLQUFLLEVBQUUsUUFBUTthQUNmLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFFRCxLQUFLLENBQUMsU0FBUztRQUNkLElBQUEsWUFBSyxFQUFDLGdCQUFnQixHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDL0MsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUTtZQUN6QixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxFQUFFLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxZQUFZLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFBO0lBQ3pGLENBQUM7SUFFRCxLQUFLLENBQUMsUUFBUTtRQUNiLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsRUFBRTtZQUM1QixJQUFBLFlBQUssRUFBQyxtQ0FBbUMsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFBO1lBQ2xFLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQTtTQUNyQjtRQUVELE1BQU0sS0FBSyxHQUFHLENBQ2IsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUM7WUFDN0IsR0FBRyxJQUFJLENBQUMsTUFBTTtZQUNkLFlBQVksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU07WUFDbkMsU0FBUyxFQUFFLEVBQUUsUUFBUSxFQUFFLENBQUMsZUFBZSxDQUFDLEVBQUU7U0FDMUMsQ0FBQyxDQUNGLENBQUMsSUFBSSxDQUFBO1FBQ04sT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLG1CQUFtQixDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUE7SUFDMUQsQ0FBQztJQUVELEtBQUssQ0FBQyxXQUFXLENBQUMsSUFBWTtRQUM3QixJQUFBLFlBQUssRUFBQyxtQkFBbUIsSUFBSSxPQUFPLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQTtRQUM1RCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRO1lBQ3pCLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsYUFBYSxDQUFDO2dCQUN2QyxHQUFHLElBQUksQ0FBQyxNQUFNO2dCQUNkLFlBQVksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU07Z0JBQ25DLElBQUk7YUFDSixDQUFDLENBQUE7SUFDSixDQUFDO0lBRUQsS0FBSyxDQUFDLGFBQWEsQ0FBQyxFQUFVO1FBQzdCLElBQUEsWUFBSyxFQUFDLG9CQUFvQixFQUFFLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFBO1FBQzNELElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVE7WUFDekIsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxhQUFhLENBQUM7Z0JBQ3ZDLEtBQUssRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUs7Z0JBQ3hCLElBQUksRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUk7Z0JBQ3RCLFVBQVUsRUFBRSxFQUFFO2FBQ2QsQ0FBQyxDQUFBO0lBQ0osQ0FBQztJQUVELEtBQUssQ0FBQyxZQUFZLENBQUMsV0FBbUI7UUFDckMsSUFBQSxZQUFLLEVBQUMseUJBQXlCLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxPQUFPLFdBQVcsRUFBRSxDQUFDLENBQUE7UUFDekUsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUTtZQUN6QixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQztnQkFDaEMsR0FBRyxJQUFJLENBQUMsTUFBTTtnQkFDZCxZQUFZLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNO2dCQUNuQyxTQUFTLEVBQUUsV0FBVzthQUN0QixDQUFDLENBQUE7SUFDSixDQUFDO0lBRUQsS0FBSyxDQUFDLENBQUMsV0FBVyxDQUFDLElBQWM7UUFDaEMsSUFBQSxZQUFLLEVBQUMsd0JBQXdCLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUV2RCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQzlDLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDO1lBQy9DLEdBQUcsSUFBSSxDQUFDLE1BQU07WUFDZCxZQUFZLEVBQUUsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNO1lBQ25DLFFBQVEsRUFBRSxHQUFHO1lBQ2IsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQyxFQUFFLElBQUksRUFBRSxDQUFDLE1BQU0sSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztTQUMzRSxDQUFDLENBQ0YsQ0FBQTtRQUVELElBQUksS0FBSyxFQUFFLE1BQU0sSUFBSSxJQUFJLFFBQVEsRUFBRTtZQUNsQyxNQUFPLElBQUksQ0FBQyxJQUFpRCxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsQ0FBQztnQkFDL0UsTUFBTSxFQUFFLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLFdBQVcsRUFBRSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxLQUFLLEVBQUU7Z0JBQzlFLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSTtnQkFDbEIsRUFBRSxFQUFFLE9BQU8sQ0FBQyxFQUFFO2dCQUNkLFNBQVMsRUFBRSxDQUFDLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7YUFDeEMsQ0FBQyxDQUFDLENBQUE7U0FDSDtJQUNGLENBQUM7SUFFRCxLQUFLLENBQUMsUUFBUSxDQUFDLElBQVk7UUFDMUIsSUFBQSxZQUFLLEVBQUMsZ0JBQWdCLElBQUksT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUE7UUFDekQsSUFBSSxDQUFDLENBQUMsTUFBTSxJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDLEVBQUU7WUFDckMsTUFBTSxLQUFLLENBQUMsMENBQTBDLElBQUksa0JBQWtCLENBQUMsQ0FBQTtTQUM3RTtRQUNELElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVE7WUFDekIsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUM7Z0JBQ25DLEdBQUcsSUFBSSxDQUFDLE1BQU07Z0JBQ2QsWUFBWSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTTtnQkFDbkMsTUFBTSxFQUFFLENBQUMsSUFBSSxDQUFDO2FBQ2QsQ0FBQyxDQUFBO0lBQ0osQ0FBQztJQUVELEtBQUssQ0FBQyxXQUFXLENBQUMsSUFBWTtRQUM3QixJQUFBLFlBQUssRUFBQyxrQkFBa0IsSUFBSSxTQUFTLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQTtRQUM3RCxJQUFJO1lBQ0gsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUTtnQkFDekIsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxXQUFXLENBQUM7b0JBQ3JDLEdBQUcsSUFBSSxDQUFDLE1BQU07b0JBQ2QsWUFBWSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTTtvQkFDbkMsSUFBSTtpQkFDSixDQUFDLENBQUE7U0FDSDtRQUFDLE9BQU8sR0FBRyxFQUFFO1lBQ2IsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLEdBQUcsRUFBRTtnQkFDdkIsT0FBTTthQUNOO1lBQ0QsTUFBTSxHQUFHLENBQUE7U0FDVDtJQUNGLENBQUM7SUFFRCxLQUFLLENBQUMsY0FBYztRQUNuQixJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxJQUFJLEVBQUU7WUFDakMsT0FBTTtTQUNOO1FBRUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMscUJBQXFCLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQztZQUN4RSxHQUFHLElBQUksQ0FBQyxNQUFNO1lBQ2QsWUFBWSxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTTtTQUNuQyxDQUFDLENBQUE7UUFDRixJQUFJLGFBQTBFLENBQUE7UUFDOUUsSUFBSSxLQUFLLEVBQUUsTUFBTSxLQUFLLElBQUksSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ2xFLE1BQU0sY0FBYyxHQUFHLEtBQUssQ0FBQyxJQUF5RCxDQUFBO1lBQ3RGLEtBQUssTUFBTSxhQUFhLElBQUksY0FBYyxFQUFFO2dCQUMzQyxJQUFJLGFBQWEsQ0FBQyxLQUFLLEtBQUssUUFBUSxFQUFFO29CQUNyQyxhQUFhLEdBQUc7d0JBQ2YsSUFBSSxFQUFFLGFBQWEsQ0FBQyxTQUFTLElBQUksU0FBUzt3QkFDMUMsU0FBUyxFQUFFLENBQUMsSUFBSSxJQUFJLENBQUMsYUFBYSxDQUFDLFVBQVUsQ0FBQztxQkFDOUMsQ0FBQTtpQkFDRDthQUNEO1NBQ0Q7UUFDRCxPQUFPLGFBQWEsQ0FBQTtJQUNyQixDQUFDO0NBQ0Q7QUE5SkQsb0NBOEpDO0FBRUQsU0FBUyxPQUFPLENBQUMsTUFBVztJQUMzQixNQUFNLE9BQU8sR0FDWixRQUFRLElBQUksTUFBTTtRQUNsQixNQUFNLElBQUksTUFBTTtRQUNoQixPQUFPLElBQUksTUFBTTtRQUNqQixRQUFRLElBQUksTUFBTTtRQUNsQixNQUFNLElBQUksTUFBTTtRQUNoQixRQUFRLElBQUksTUFBTTtRQUNsQixRQUFRLElBQUksTUFBTTtRQUNsQixhQUFhLElBQUksTUFBTTtRQUN2QixXQUFXLElBQUksTUFBTTtRQUNyQixhQUFhLElBQUksTUFBTSxDQUFBO0lBRXhCLE9BQU8sT0FBTyxDQUFBO0FBQ2YsQ0FBQyJ9 \ No newline at end of file diff --git a/build/actions/api/testbed.js b/build/actions/api/testbed.js index 75c03ff6ab..1b157d3c77 100644 --- a/build/actions/api/testbed.js +++ b/build/actions/api/testbed.js @@ -6,16 +6,17 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.TestbedIssue = exports.Testbed = void 0; class Testbed { + config; constructor(config) { - var _a, _b, _c, _d, _e; this.config = { - globalLabels: (_a = config === null || config === void 0 ? void 0 : config.globalLabels) !== null && _a !== void 0 ? _a : [], - configs: (_b = config === null || config === void 0 ? void 0 : config.configs) !== null && _b !== void 0 ? _b : {}, - writers: (_c = config === null || config === void 0 ? void 0 : config.writers) !== null && _c !== void 0 ? _c : [], - releasedCommits: (_d = config === null || config === void 0 ? void 0 : config.releasedCommits) !== null && _d !== void 0 ? _d : [], - queryRunner: (_e = config === null || config === void 0 ? void 0 : config.queryRunner) !== null && _e !== void 0 ? _e : async function* () { - yield []; - }, + globalLabels: config?.globalLabels ?? [], + configs: config?.configs ?? {}, + writers: config?.writers ?? [], + releasedCommits: config?.releasedCommits ?? [], + queryRunner: config?.queryRunner ?? + async function* () { + yield []; + }, }; } async *query(query) { @@ -47,17 +48,17 @@ class Testbed { } exports.Testbed = Testbed; class TestbedIssue extends Testbed { + issueConfig; constructor(globalConfig, issueConfig) { - var _a, _b, _c; super(globalConfig); - issueConfig = issueConfig !== null && issueConfig !== void 0 ? issueConfig : {}; - issueConfig.comments = (_a = issueConfig === null || issueConfig === void 0 ? void 0 : issueConfig.comments) !== null && _a !== void 0 ? _a : []; - issueConfig.labels = (_b = issueConfig === null || issueConfig === void 0 ? void 0 : issueConfig.labels) !== null && _b !== void 0 ? _b : []; + issueConfig = issueConfig ?? {}; + issueConfig.comments = issueConfig?.comments ?? []; + issueConfig.labels = issueConfig?.labels ?? []; issueConfig.issue = { author: { name: 'JacksonKearl' }, body: 'issue body', locked: false, - numComments: ((_c = issueConfig === null || issueConfig === void 0 ? void 0 : issueConfig.comments) === null || _c === void 0 ? void 0 : _c.length) || 0, + numComments: issueConfig?.comments?.length || 0, number: 1, open: true, title: 'issue title', @@ -91,7 +92,7 @@ class TestbedIssue extends Testbed { } async postComment(body, author) { this.issueConfig.comments.push({ - author: { name: author !== null && author !== void 0 ? author : 'bot' }, + author: { name: author ?? 'bot' }, body, id: Math.random(), timestamp: +new Date(), @@ -122,3 +123,4 @@ class TestbedIssue extends Testbed { } } exports.TestbedIssue = TestbedIssue; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdGJlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3RiZWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFjaEcsTUFBYSxPQUFPO0lBQ1osTUFBTSxDQUFlO0lBRTVCLFlBQVksTUFBK0I7UUFDMUMsSUFBSSxDQUFDLE1BQU0sR0FBRztZQUNiLFlBQVksRUFBRSxNQUFNLEVBQUUsWUFBWSxJQUFJLEVBQUU7WUFDeEMsT0FBTyxFQUFFLE1BQU0sRUFBRSxPQUFPLElBQUksRUFBRTtZQUM5QixPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sSUFBSSxFQUFFO1lBQzlCLGVBQWUsRUFBRSxNQUFNLEVBQUUsZUFBZSxJQUFJLEVBQUU7WUFDOUMsV0FBVyxFQUNWLE1BQU0sRUFBRSxXQUFXO2dCQUNuQixLQUFLLFNBQVMsQ0FBQztvQkFDZCxNQUFNLEVBQUUsQ0FBQTtnQkFDVCxDQUFDO1NBQ0YsQ0FBQTtJQUNGLENBQUM7SUFFRCxLQUFLLENBQUMsQ0FBQyxLQUFLLENBQUMsS0FBWTtRQUN4QixJQUFJLEtBQUssRUFBRSxNQUFNLElBQUksSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsRUFBRTtZQUN4RCxNQUFNLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUN4QixLQUFLLFlBQVksWUFBWSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksWUFBWSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQzVFLENBQUE7U0FDRDtJQUNGLENBQUM7SUFFRCxLQUFLLENBQUMsV0FBVyxDQUFDLE1BQWMsRUFBRSxLQUFhLEVBQUUsTUFBYyxFQUFFLEtBQWE7UUFDN0UsVUFBVTtJQUNYLENBQUM7SUFFRCxLQUFLLENBQUMsVUFBVSxDQUFDLElBQVk7UUFDNUIsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FB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 \ No newline at end of file diff --git a/build/actions/auto-labeler/index.js b/build/actions/auto-labeler/index.js index 8147413f82..788eba5966 100644 --- a/build/actions/auto-labeler/index.js +++ b/build/actions/auto-labeler/index.js @@ -8,15 +8,16 @@ const core = require("@actions/core"); const github_1 = require("@actions/github"); const octokit_1 = require("../api/octokit"); const utils_1 = require("../utils/utils"); -const token = utils_1.getRequiredInput('token'); -const label = utils_1.getRequiredInput('label'); +const token = (0, utils_1.getRequiredInput)('token'); +const label = (0, utils_1.getRequiredInput)('label'); async function main() { const pr = new octokit_1.OctoKitIssue(token, github_1.context.repo, { number: github_1.context.issue.number }); pr.addLabel(label); } main() - .then(() => utils_1.logRateLimit(token)) + .then(() => (0, utils_1.logRateLimit)(token)) .catch(async (error) => { core.setFailed(error.message); - await utils_1.logErrorToIssue(error.message, true, token); + await (0, utils_1.logErrorToIssue)(error.message, true, token); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHNDQUFxQztBQUNyQyw0Q0FBeUM7QUFDekMsNENBQTZDO0FBQzdDLDBDQUFnRjtBQUVoRixNQUFNLEtBQUssR0FBRyxJQUFBLHdCQUFnQixFQUFDLE9BQU8sQ0FBQyxDQUFDO0FBQ3hDLE1BQU0sS0FBSyxHQUFHLElBQUEsd0JBQWdCLEVBQUMsT0FBTyxDQUFDLENBQUM7QUFFeEMsS0FBSyxVQUFVLElBQUk7SUFFbEIsTUFBTSxFQUFFLEdBQUcsSUFBSSxzQkFBWSxDQUFDLEtBQUssRUFBRSxnQkFBTyxDQUFDLElBQUksRUFBRSxFQUFFLE1BQU0sRUFBRSxnQkFBTyxDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO0lBRW5GLEVBQUUsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7QUFDcEIsQ0FBQztBQUVELElBQUksRUFBRTtLQUNKLElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFBLG9CQUFZLEVBQUMsS0FBSyxDQUFDLENBQUM7S0FDL0IsS0FBSyxDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsRUFBRTtJQUN0QixJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQTtJQUM3QixNQUFNLElBQUEsdUJBQWUsRUFBQyxLQUFLLENBQUMsT0FBTyxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQTtBQUNsRCxDQUFDLENBQUMsQ0FBQSJ9 \ No newline at end of file diff --git a/build/actions/utils/utils.js b/build/actions/utils/utils.js index 56b52770a1..1b35c65adb 100644 --- a/build/actions/utils/utils.js +++ b/build/actions/utils/utils.js @@ -9,9 +9,11 @@ const core = require("@actions/core"); const github_1 = require("@actions/github"); const axios_1 = require("axios"); const octokit_1 = require("../api/octokit"); -exports.getInput = (name) => core.getInput(name) || undefined; -exports.getRequiredInput = (name) => core.getInput(name, { required: true }); -exports.normalizeIssue = (issue) => { +const getInput = (name) => core.getInput(name) || undefined; +exports.getInput = getInput; +const getRequiredInput = (name) => core.getInput(name, { required: true }); +exports.getRequiredInput = getRequiredInput; +const normalizeIssue = (issue) => { const { body, title } = issue; const isBug = body.includes('bug_report_template') || /Issue Type:.*Bug.*/.test(body); const isFeatureRequest = body.includes('feature_request_template') || /Issue Type:.*Feature Request.*/.test(body); @@ -34,20 +36,25 @@ exports.normalizeIssue = (issue) => { issueType: isBug ? 'bug' : isFeatureRequest ? 'feature_request' : 'unknown', }; }; -exports.loadLatestRelease = async (quality) => (await axios_1.default.get(`https://vscode-update.azurewebsites.net/api/update/darwin/${quality}/latest`)).data; -exports.daysAgoToTimestamp = (days) => +new Date(Date.now() - days * 24 * 60 * 60 * 1000); -exports.daysAgoToHumanReadbleDate = (days) => new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().replace(/\.\d{3}\w$/, ''); -exports.logRateLimit = async (token) => { +exports.normalizeIssue = normalizeIssue; +const loadLatestRelease = async (quality) => (await axios_1.default.get(`https://vscode-update.azurewebsites.net/api/update/darwin/${quality}/latest`)).data; +exports.loadLatestRelease = loadLatestRelease; +const daysAgoToTimestamp = (days) => +new Date(Date.now() - days * 24 * 60 * 60 * 1000); +exports.daysAgoToTimestamp = daysAgoToTimestamp; +const daysAgoToHumanReadbleDate = (days) => new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().replace(/\.\d{3}\w$/, ''); +exports.daysAgoToHumanReadbleDate = daysAgoToHumanReadbleDate; +const logRateLimit = async (token) => { const usageData = (await new github_1.GitHub(token).rateLimit.get()).data.resources; ['core', 'graphql', 'search'].forEach(async (category) => { const usage = 1 - usageData[category].remaining / usageData[category].limit; const message = `Usage at ${usage} for ${category}`; if (usage > 0.5) { - await exports.logErrorToIssue(message, false, token); + await (0, exports.logErrorToIssue)(message, false, token); } }); }; -exports.logErrorToIssue = async (message, ping, token) => { +exports.logRateLimit = logRateLimit; +const logErrorToIssue = async (message, ping, token) => { // Attempt to wait out abuse detection timeout if present await new Promise((resolve) => setTimeout(resolve, 10000)); const dest = github_1.context.repo.repo === 'vscode-internalbacklog' @@ -68,3 +75,5 @@ ${JSON.stringify(github_1.context, null, 2).replace(/ `); }; +exports.logErrorToIssue = logErrorToIssue; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ1dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxzQ0FBcUM7QUFDckMsNENBQWlEO0FBQ2pELGlDQUF5QjtBQUN6Qiw0Q0FBNkM7QUFHdEMsTUFBTSxRQUFRLEdBQUcsQ0FBQyxJQUFZLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksU0FBUyxDQUFBO0FBQTdELFFBQUEsUUFBUSxZQUFxRDtBQUNuRSxNQUFNLGdCQUFnQixHQUFHLENBQUMsSUFBWSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFBO0FBQTVFLFFBQUEsZ0JBQWdCLG9CQUE0RDtBQUVsRixNQUFNLGNBQWMsR0FBRyxDQUM3QixLQUFZLEVBQ3dFLEVBQUU7SUFDdEYsTUFBTSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsR0FBRyxLQUFLLENBQUE7SUFFN0IsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxxQkFBcUIsQ0FBQyxJQUFJLG9CQUFvQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQTtJQUNyRixNQUFNLGdCQUFnQixHQUNyQixJQUFJLENBQUMsUUFBUSxDQUFDLDBCQUEwQixDQUFDLElBQUksZ0NBQWdDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFBO0lBRXpGLE1BQU0sT0FBTyxHQUFHLENBQUMsR0FBVyxFQUFFLEVBQUUsQ0FDL0IsR0FBRztTQUNELFdBQVcsRUFBRTtTQUNiLE9BQU8sQ0FBQyxjQUFjLEVBQUUsRUFBRSxDQUFDO1NBQzNCLE9BQU8sQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLENBQUM7U0FDL0IsT0FBTyxDQUFDLGtCQUFrQixFQUFFLEVBQUUsQ0FBQztTQUMvQixPQUFPLENBQUMsZ0NBQWdD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 \ No newline at end of file diff --git a/build/azure-pipelines/.gdntsa b/build/azure-pipelines/.gdntsa deleted file mode 100644 index a9d98fe01f..0000000000 --- a/build/azure-pipelines/.gdntsa +++ /dev/null @@ -1,21 +0,0 @@ -{ - "codebaseName": "vscode-client", - "ppe": false, - "notificationAliases": [ - "sbatten@microsoft.com" - ], - "codebaseAdmins": [ - "REDMOND\\stbatt", - "REDMOND\\monacotools", - ], - "instanceUrl": "https://msazure.visualstudio.com/defaultcollection", - "projectName": "One", - "areaPath": "One\\VSCode\\Visual Studio Code Client", - "iterationPath": "One", - "notifyAlways": true, - "tools": [ - "BinSkim", - "CredScan", - "CodeQL" - ] -} diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js new file mode 100644 index 0000000000..a7bb6a1c2f --- /dev/null +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js @@ -0,0 +1,16 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = require("fs"); +const path = require("path"); +const crypto = require("crypto"); +const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../product.json'), 'utf8')); +const shasum = crypto.createHash('sha1'); +for (const ext of productjson.builtInExtensions) { + shasum.update(`${ext.name}@${ext.version}`); +} +process.stdout.write(shasum.digest('hex')); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcHV0ZUJ1aWx0SW5EZXBzQ2FjaGVLZXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjb21wdXRlQnVpbHRJbkRlcHNDYWNoZUtleS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0IsaUNBQWlDO0FBRWpDLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSx1QkFBdUIsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDdkcsTUFBTSxNQUFNLEdBQUcsTUFBTSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsQ0FBQztBQUV6QyxLQUFLLE1BQU0sR0FBRyxJQUFJLFdBQVcsQ0FBQyxpQkFBaUIsRUFBRTtJQUNoRCxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsR0FBRyxDQUFDLElBQUksSUFBSSxHQUFHLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztDQUM1QztBQUVELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyJ9 \ No newline at end of file diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts new file mode 100644 index 0000000000..f4d8ed4c7c --- /dev/null +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as crypto from 'crypto'; + +const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../product.json'), 'utf8')); +const shasum = crypto.createHash('sha1'); + +for (const ext of productjson.builtInExtensions) { + shasum.update(`${ext.name}@${ext.version}`); +} + +process.stdout.write(shasum.digest('hex')); diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.js b/build/azure-pipelines/common/computeNodeModulesCacheKey.js index 61bbe9e27f..0ef8107c1a 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.js +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.js @@ -21,7 +21,8 @@ for (const dir of dirs) { dependencies: packageJson.dependencies, devDependencies: packageJson.devDependencies, optionalDependencies: packageJson.optionalDependencies, - resolutions: packageJson.resolutions + resolutions: packageJson.resolutions, + distro: packageJson.distro }; shasum.update(JSON.stringify(relevantPackageJsonSections)); const yarnLockPath = path.join(ROOT, dir, 'yarn.lock'); @@ -32,3 +33,4 @@ for (let i = 2; i < process.argv.length; i++) { shasum.update(process.argv[i]); } process.stdout.write(shasum.digest('hex')); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcHV0ZU5vZGVNb2R1bGVzQ2FjaGVLZXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjb21wdXRlTm9kZU1vZHVsZXNDYWNoZUtleS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0IsaUNBQWlDO0FBQ2pDLE1BQU0sRUFBRSxJQUFJLEVBQUUsR0FBRyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUUzQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxXQUFXLENBQUMsQ0FBQztBQUUvQyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBRXpDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNwRSxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQzNELE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUVsRSwyQ0FBMkM7QUFDM0MsS0FBSyxNQUFNLEdBQUcsSUFBSSxJQUFJLEVBQUU7SUFDdkIsTUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLGNBQWMsQ0FBQyxDQUFDO0lBQzdELE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxlQUFlLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQzVFLE1BQU0sMkJBQTJCLEdBQUc7UUFDbkMsWUFBWSxFQUFFLFdBQVcsQ0FBQyxZQUFZO1FBQ3RDLGVBQWUsRUFBRSxXQUFXLENBQUMsZUFBZTtRQUM1QyxvQkFBb0IsRUFBRSxXQUFXLENBQUMsb0JBQW9CO1FBQ3RELFdBQVcsRUFBRSxXQUFXLENBQUMsV0FBVztRQUNwQyxNQUFNLEVBQUUsV0FBVyxDQUFDLE1BQU07S0FDMUIsQ0FBQztJQUNGLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQywyQkFBMkIsQ0FBQyxDQUFDLENBQUM7SUFFM0QsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsR0FBRyxFQUFFLFdBQVcsQ0FBQyxDQUFDO0lBQ3ZELE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDO0NBQzdDO0FBRUQsdUNBQXVDO0FBQ3ZDLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtJQUM3QyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztDQUMvQjtBQUVELE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyJ9 \ No newline at end of file diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts index 8b8869f378..18b4e3596f 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts @@ -24,7 +24,8 @@ for (const dir of dirs) { dependencies: packageJson.dependencies, devDependencies: packageJson.devDependencies, optionalDependencies: packageJson.optionalDependencies, - resolutions: packageJson.resolutions + resolutions: packageJson.resolutions, + distro: packageJson.distro }; shasum.update(JSON.stringify(relevantPackageJsonSections)); diff --git a/build/azure-pipelines/common/copyArtifacts.js b/build/azure-pipelines/common/copyArtifacts.js index c5cbae128c..3f74b8cf96 100644 --- a/build/azure-pipelines/common/copyArtifacts.js +++ b/build/azure-pipelines/common/copyArtifacts.js @@ -40,3 +40,4 @@ main().catch(err => { console.error(err); process.exit(1); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29weUFydGlmYWN0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImNvcHlBcnRpZmFjdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztnR0FHZ0c7QUFFaEcsWUFBWSxDQUFDOztBQUViLGdDQUFnQztBQUNoQyw2QkFBNkI7QUFDN0IsbUNBQW1DO0FBQ25DLHlCQUF5QjtBQUV6QixNQUFNLEtBQUssR0FBRztJQUNiLDRCQUE0QjtJQUM1Qiw2QkFBNkI7SUFDN0IsaUNBQWlDO0lBQ2pDLG1DQUFtQztJQUNuQywrQkFBK0I7SUFDL0Isa0NBQWtDO0lBQ2xDLCtCQUErQjtJQUMvQix1QkFBdUI7SUFDdkIsd0JBQXdCO0lBQ3hCLGlCQUFpQjtJQUNqQixpQkFBaUI7SUFDakIscUJBQXFCLENBQUMsc0JBQXNCO0NBQzVDLENBQUM7QUFFRixLQUFLLFVBQVUsSUFBSTtJQUNsQixPQUFPLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1FBQzVDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxVQUFVLEVBQUUsSUFBSSxFQUFFLENBQUM7YUFDakUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFDdkIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLDhCQUErQjtZQUNyRSx1REFBdUQ7WUFDdkQsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN0RCxFQUFFLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztZQUMxRCxFQUFFLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFDcEMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUVMLE1BQU0sQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7UUFDbEMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNwQyxDQUFDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRCxJQUFJLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEVBQUU7SUFDbEIsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNuQixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2pCLENBQUMsQ0FBQyxDQUFDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/common/createAsset.js b/build/azure-pipelines/common/createAsset.js index df47fbab0e..634d940a18 100644 --- a/build/azure-pipelines/common/createAsset.js +++ b/build/azure-pipelines/common/createAsset.js @@ -43,6 +43,8 @@ function getPlatform(product, os, arch, type) { throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } return arch === 'ia32' ? 'server-win32-web' : `server-win32-${arch}-web`; + case 'cli': + return `cli-win32-${arch}`; default: throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } @@ -52,6 +54,8 @@ function getPlatform(product, os, arch, type) { return `server-alpine-${arch}`; case 'web': return `server-alpine-${arch}-web`; + case 'cli': + return `cli-alpine-${arch}`; default: throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } @@ -74,6 +78,8 @@ function getPlatform(product, os, arch, type) { return `linux-deb-${arch}`; case 'rpm-package': return `linux-rpm-${arch}`; + case 'cli': + return `cli-linux-${arch}`; default: throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } @@ -94,6 +100,8 @@ function getPlatform(product, os, arch, type) { return 'server-darwin-web'; } return `server-darwin-${arch}-web`; + case 'cli': + return `cli-darwin-${arch}`; default: throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } @@ -135,7 +143,7 @@ async function main() { const platform = getPlatform(product, os, arch, unprocessedType); const type = getRealType(unprocessedType); const quality = getEnv('VSCODE_QUALITY'); - const commit = process.env['VSCODE_DISTRO_COMMIT'] || getEnv('BUILD_SOURCEVERSION'); + const commit = getEnv('BUILD_SOURCEVERSION'); console.log('Creating asset...'); const stat = await new Promise((c, e) => fs.stat(filePath, (err, stat) => err ? e(err) : c(stat))); const size = stat.size; @@ -150,11 +158,6 @@ async function main() { const blobServiceClient = new storage_blob_1.BlobServiceClient(`https://vscode.blob.core.windows.net`, credential, storagePipelineOptions); const containerClient = blobServiceClient.getContainerClient(quality); const blobClient = containerClient.getBlockBlobClient(blobName); - const blobExists = await blobClient.exists(); - if (blobExists) { - console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); - return; - } const blobOptions = { blobHTTPHeaders: { blobContentType: mime.lookup(filePath), @@ -162,29 +165,42 @@ async function main() { blobCacheControl: 'max-age=31536000, public' } }; - const uploadPromises = [ - (0, retry_1.retry)(async () => { + const uploadPromises = []; + if (await blobClient.exists()) { + console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); + } + else { + uploadPromises.push((0, retry_1.retry)(async () => { await blobClient.uploadFile(filePath, blobOptions); console.log('Blob successfully uploaded to Azure storage.'); - }) - ]; + })); + } const shouldUploadToMooncake = /true/i.test(process.env['VSCODE_PUBLISH_TO_MOONCAKE'] ?? 'true'); if (shouldUploadToMooncake) { const mooncakeCredential = new identity_1.ClientSecretCredential(process.env['AZURE_MOONCAKE_TENANT_ID'], process.env['AZURE_MOONCAKE_CLIENT_ID'], process.env['AZURE_MOONCAKE_CLIENT_SECRET']); const mooncakeBlobServiceClient = new storage_blob_1.BlobServiceClient(`https://vscode.blob.core.chinacloudapi.cn`, mooncakeCredential, storagePipelineOptions); const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality); const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); - uploadPromises.push((0, retry_1.retry)(async () => { - await mooncakeBlobClient.uploadFile(filePath, blobOptions); - console.log('Blob successfully uploaded to Mooncake Azure storage.'); - })); - console.log('Uploading blobs to Azure storage and Mooncake Azure storage...'); + if (await mooncakeBlobClient.exists()) { + console.log(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`); + } + else { + uploadPromises.push((0, retry_1.retry)(async () => { + await mooncakeBlobClient.uploadFile(filePath, blobOptions); + console.log('Blob successfully uploaded to Mooncake Azure storage.'); + })); + } + if (uploadPromises.length) { + console.log('Uploading blobs to Azure storage and Mooncake Azure storage...'); + } } else { - console.log('Uploading blobs to Azure storage...'); + if (uploadPromises.length) { + console.log('Uploading blobs to Azure storage...'); + } } await Promise.all(uploadPromises); - console.log('All blobs successfully uploaded.'); + console.log(uploadPromises.length ? 'All blobs successfully uploaded.' : 'No blobs to upload.'); const assetUrl = `${process.env['AZURE_CDN_URL']}/${quality}/${blobName}`; const blobPath = new URL(assetUrl).pathname; const mooncakeUrl = `${process.env['MOONCAKE_CDN_URL']}${blobPath}`; @@ -214,3 +230,4 @@ main().then(() => { console.error(err); process.exit(1); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlQXNzZXQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGVBc3NldC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUV6QixpQ0FBaUM7QUFDakMsc0RBQXdJO0FBQ3hJLDZCQUE2QjtBQUM3QiwwQ0FBNkM7QUFDN0MsOENBQXlEO0FBQ3pELG1DQUFnQztBQWFoQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtJQUM5QixPQUFPLENBQUMsS0FBSyxDQUFDLDJEQUEyRCxDQUFDLENBQUM7SUFDM0UsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsd0ZBQXdGO0FBQ3hGLFNBQVMsV0FBVyxDQUFDLE9BQWUsRUFBRSxFQUFVLEVBQUUsSUFBWSxFQUFFLElBQVk7SUFDM0UsUUFBUSxFQUFFLEVBQUU7UUFDWCxLQUFLLE9BQU87WUFDWCxRQUFRLE9BQU8sRUFBRTtnQkFDaEIsS0FBSyxRQUFRLENBQUMsQ0FBQztvQkFDZCxNQUFNLEtBQUssR0FBRyxJQUFJLEtBQUssTUFBTSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsSUFBSSxFQUFFLENBQUM7b0JBQzFELFFBQVEsSUFBSSxFQUFFO3dCQUNiLEtBQUssU0FBUzs0QkFDYixPQUFPLEdBQUcsS0FBSyxVQUFVLENBQUM7d0JBQzNCLEtBQUssT0FBTzs0QkFDWCxPQUFPLEtBQUssQ0FBQzt3QkFDZCxLQUFLLFlBQVk7NEJBQ2hCLE9BQU8sR0FBRyxLQUFLLE9BQU8sQ0FBQzt3QkFDeEI7NEJBQ0MsTUFBTSxJQUFJLEtBQUssQ0FBQyxpQkFBaUIsT0FBTyxJQUFJLEVBQUUsSUFBSSxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQztxQkFDbkU7aUJBQ0Q7Z0JBQ0QsS0FBSyxRQUFRO29CQUNaLElBQUksSUFBSSxLQUFLLE9BQU8s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 \ No newline at end of file diff --git a/build/azure-pipelines/common/createAsset.ts b/build/azure-pipelines/common/createAsset.ts index fff05be615..bf2b2eff1d 100644 --- a/build/azure-pipelines/common/createAsset.ts +++ b/build/azure-pipelines/common/createAsset.ts @@ -56,6 +56,8 @@ function getPlatform(product: string, os: string, arch: string, type: string): s throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } return arch === 'ia32' ? 'server-win32-web' : `server-win32-${arch}-web`; + case 'cli': + return `cli-win32-${arch}`; default: throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } @@ -65,6 +67,8 @@ function getPlatform(product: string, os: string, arch: string, type: string): s return `server-alpine-${arch}`; case 'web': return `server-alpine-${arch}-web`; + case 'cli': + return `cli-alpine-${arch}`; default: throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } @@ -87,6 +91,8 @@ function getPlatform(product: string, os: string, arch: string, type: string): s return `linux-deb-${arch}`; case 'rpm-package': return `linux-rpm-${arch}`; + case 'cli': + return `cli-linux-${arch}`; default: throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } @@ -107,6 +113,8 @@ function getPlatform(product: string, os: string, arch: string, type: string): s return 'server-darwin-web'; } return `server-darwin-${arch}-web`; + case 'cli': + return `cli-darwin-${arch}`; default: throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); } @@ -155,7 +163,7 @@ async function main(): Promise { const platform = getPlatform(product, os, arch, unprocessedType); const type = getRealType(unprocessedType); const quality = getEnv('VSCODE_QUALITY'); - const commit = process.env['VSCODE_DISTRO_COMMIT'] || getEnv('BUILD_SOURCEVERSION'); + const commit = getEnv('BUILD_SOURCEVERSION'); console.log('Creating asset...'); @@ -178,12 +186,6 @@ async function main(): Promise { const blobServiceClient = new BlobServiceClient(`https://vscode.blob.core.windows.net`, credential, storagePipelineOptions); const containerClient = blobServiceClient.getContainerClient(quality); const blobClient = containerClient.getBlockBlobClient(blobName); - const blobExists = await blobClient.exists(); - - if (blobExists) { - console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); - return; - } const blobOptions: BlockBlobParallelUploadOptions = { blobHTTPHeaders: { @@ -193,12 +195,15 @@ async function main(): Promise { } }; - const uploadPromises: Promise[] = [ - retry(async () => { + const uploadPromises: Promise[] = []; + if (await blobClient.exists()) { + console.log(`Blob ${quality}, ${blobName} already exists, not publishing again.`); + } else { + uploadPromises.push(retry(async () => { await blobClient.uploadFile(filePath, blobOptions); console.log('Blob successfully uploaded to Azure storage.'); - }) - ]; + })); + } const shouldUploadToMooncake = /true/i.test(process.env['VSCODE_PUBLISH_TO_MOONCAKE'] ?? 'true'); @@ -208,18 +213,27 @@ async function main(): Promise { const mooncakeContainerClient = mooncakeBlobServiceClient.getContainerClient(quality); const mooncakeBlobClient = mooncakeContainerClient.getBlockBlobClient(blobName); - uploadPromises.push(retry(async () => { - await mooncakeBlobClient.uploadFile(filePath, blobOptions); - console.log('Blob successfully uploaded to Mooncake Azure storage.'); - })); + if (await mooncakeBlobClient.exists()) { + console.log(`Mooncake Blob ${quality}, ${blobName} already exists, not publishing again.`); + } else { + uploadPromises.push(retry(async () => { + await mooncakeBlobClient.uploadFile(filePath, blobOptions); + console.log('Blob successfully uploaded to Mooncake Azure storage.'); + })); + } - console.log('Uploading blobs to Azure storage and Mooncake Azure storage...'); + if (uploadPromises.length) { + console.log('Uploading blobs to Azure storage and Mooncake Azure storage...'); + } } else { - console.log('Uploading blobs to Azure storage...'); + if (uploadPromises.length) { + console.log('Uploading blobs to Azure storage...'); + } } await Promise.all(uploadPromises); - console.log('All blobs successfully uploaded.'); + + console.log(uploadPromises.length ? 'All blobs successfully uploaded.' : 'No blobs to upload.'); const assetUrl = `${process.env['AZURE_CDN_URL']}/${quality}/${blobName}`; const blobPath = new URL(assetUrl).pathname; diff --git a/build/azure-pipelines/common/createBuild.js b/build/azure-pipelines/common/createBuild.js index 7bfdfbc5d3..256ea3f109 100644 --- a/build/azure-pipelines/common/createBuild.js +++ b/build/azure-pipelines/common/createBuild.js @@ -21,9 +21,9 @@ function getEnv(name) { async function main() { const [, , _version] = process.argv; const quality = getEnv('VSCODE_QUALITY'); - const commit = process.env['VSCODE_DISTRO_COMMIT']?.trim() || getEnv('BUILD_SOURCEVERSION'); + const commit = getEnv('BUILD_SOURCEVERSION'); const queuedBy = getEnv('BUILD_QUEUEDBY'); - const sourceBranch = process.env['VSCODE_DISTRO_REF']?.trim() || getEnv('BUILD_SOURCEBRANCH'); + const sourceBranch = getEnv('BUILD_SOURCEBRANCH'); const version = _version + (quality === 'stable' ? '' : `-${quality}`); console.log('Creating build...'); console.log('Quality:', quality); @@ -34,7 +34,7 @@ async function main() { timestamp: (new Date()).getTime(), version, isReleased: false, - private: Boolean(process.env['VSCODE_DISTRO_REF']?.trim()), + private: process.env['VSCODE_PRIVATE_BUILD']?.toLowerCase() === 'true', sourceBranch, queuedBy, assets: [], @@ -52,3 +52,4 @@ main().then(() => { console.error(err); process.exit(1); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlQnVpbGQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGVCdWlsZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLDhDQUF5RDtBQUN6RCwwQ0FBNkM7QUFDN0MsbUNBQWdDO0FBRWhDLElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO0lBQzlCLE9BQU8sQ0FBQyxLQUFLLENBQUMsb0NBQW9DLENBQUMsQ0FBQztJQUNwRCxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7Q0FDakI7QUFFRCxTQUFTLE1BQU0sQ0FBQyxJQUFZO0lBQzNCLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFakMsSUFBSSxPQUFPLE1BQU0sS0FBSyxXQUFXLEVBQUU7UUFDbEMsTUFBTSxJQUFJLEtBQUssQ0FBQyxlQUFlLEdBQUcsSUFBSSxDQUFDLENBQUM7S0FDeEM7SUFFRCxPQUFPLE1BQU0sQ0FBQztBQUNmLENBQUM7QUFFRCxLQUFLLFVBQVUsSUFBSTtJQUNsQixNQUFNLENBQUMsRUFBRSxBQUFELEVBQUcsUUFBUSxDQUFDLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztJQUNwQyxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztJQUN6QyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMscUJBQXFCLENBQUMsQ0FBQztJQUM3QyxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztJQUMxQyxNQUFNLFlBQVksR0FBRyxNQUFNLENBQUMsb0JBQW9CLENBQUMsQ0FBQztJQUNsRCxNQUFNLE9BQU8sR0FBRyxRQUFRLEdBQUcsQ0FBQyxPQUFPLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksT0FBTyxFQUFFLENBQUMsQ0FBQztJQUV2RSxPQUFPLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDLENBQUM7SUFDakMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDakMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxVQUFVLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDakMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFFL0IsTUFBTSxLQUFLLEdBQUc7UUFDYixFQUFFLEVBQUUsTUFBTTtRQUNWLFNBQVMsRUFBRSxDQUFDLElBQUksSUFBSSxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUU7UUFDakMsT0FBTztRQUNQLFVBQVUsRUFBRSxLQUFLO1FBQ2pCLE9BQU8sRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLHNCQUFzQixDQUFDLEVBQUUsV0FBVyxFQUFFLEtBQUssTUFBTTtRQUN0RSxZQUFZO1FBQ1osUUFBUTtRQUNSLE1BQU0sRUFBRSxFQUFFO1FBQ1YsT0FBTyxFQUFFLEVBQUU7S0FDWCxDQUFDO0lBRUYsTUFBTSxjQUFjLEdBQUcsSUFBSSxpQ0FBc0IsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBRSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLENBQUUsQ0FBQyxDQUFDO0lBQ3pKLE1BQU0sTUFBTSxHQUFHLElBQUkscUJBQVksQ0FBQyxFQUFFLFFBQVEsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLDJCQUEyQixDQUFFLEVBQUUsY0FBYyxFQUFFLENBQUMsQ0FBQztJQUN6RyxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFPLENBQUM7SUFDckUsTUFBTSxJQUFBLGFBQUssRUFBQyxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsZUFBZSxDQUFDLGFBQWEsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxFQUFFLEVBQUUsQ0FBQyxFQUFFLEdBQUcsS0FBSyxFQUFFLGFBQWEsRUFBRSxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUMxRyxDQUFDO0FBRUQsSUFBSSxFQUFFLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRTtJQUNoQixPQUFPLENBQUMsR0FBRyxDQUFDLDRCQUE0QixDQUFDLENBQUM7SUFDMUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNqQixDQUFDLEVBQUUsR0FBRyxDQUFDLEVBQUU7SUFDUixPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ25CLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakIsQ0FBQyxDQUFDLENBQUMifQ== \ No newline at end of file diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts index 9663d6b3ee..480bb6d48e 100644 --- a/build/azure-pipelines/common/createBuild.ts +++ b/build/azure-pipelines/common/createBuild.ts @@ -25,9 +25,9 @@ function getEnv(name: string): string { async function main(): Promise { const [, , _version] = process.argv; const quality = getEnv('VSCODE_QUALITY'); - const commit = process.env['VSCODE_DISTRO_COMMIT']?.trim() || getEnv('BUILD_SOURCEVERSION'); + const commit = getEnv('BUILD_SOURCEVERSION'); const queuedBy = getEnv('BUILD_QUEUEDBY'); - const sourceBranch = process.env['VSCODE_DISTRO_REF']?.trim() || getEnv('BUILD_SOURCEBRANCH'); + const sourceBranch = getEnv('BUILD_SOURCEBRANCH'); const version = _version + (quality === 'stable' ? '' : `-${quality}`); console.log('Creating build...'); @@ -40,7 +40,7 @@ async function main(): Promise { timestamp: (new Date()).getTime(), version, isReleased: false, - private: Boolean(process.env['VSCODE_DISTRO_REF']?.trim()), + private: process.env['VSCODE_PRIVATE_BUILD']?.toLowerCase() === 'true', sourceBranch, queuedBy, assets: [], diff --git a/build/azure-pipelines/common/install-builtin-extensions.yml b/build/azure-pipelines/common/install-builtin-extensions.yml new file mode 100644 index 0000000000..c1ee18d05b --- /dev/null +++ b/build/azure-pipelines/common/install-builtin-extensions.yml @@ -0,0 +1,24 @@ +steps: + - pwsh: mkdir .build -ea 0 + condition: and(succeeded(), contains(variables['Agent.OS'], 'windows')) + displayName: Create .build folder + + - script: mkdir -p .build + condition: and(succeeded(), not(contains(variables['Agent.OS'], 'windows'))) + displayName: Create .build folder + + - script: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash + displayName: Prepare built-in extensions cache key + + - task: Cache@2 + inputs: + key: '"builtin-extensions" | .build/builtindepshash' + path: .build/builtInExtensions + cacheHitVar: BUILTIN_EXTENSIONS_RESTORED + displayName: Restore built-in extensions cache + + - script: node build/lib/builtInExtensions.js + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + condition: and(succeeded(), ne(variables.BUILTIN_EXTENSIONS_RESTORED, 'true')) + displayName: Download built-in extensions diff --git a/build/azure-pipelines/common/installPlaywright.js b/build/azure-pipelines/common/installPlaywright.js index 8beaf687aa..a5dc74f300 100644 --- a/build/azure-pipelines/common/installPlaywright.js +++ b/build/azure-pipelines/common/installPlaywright.js @@ -3,10 +3,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const retry_1 = require("./retry"); +process.env.DEBUG = 'pw:install'; // enable logging for this (https://github.com/microsoft/playwright/issues/17394) const { installDefaultBrowsersForNpmInstall } = require('playwright-core/lib/server'); async function install() { - await (0, retry_1.retry)(() => installDefaultBrowsersForNpmInstall()); + await installDefaultBrowsersForNpmInstall(); } install(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdGFsbFBsYXl3cmlnaHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbnN0YWxsUGxheXdyaWdodC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7QUFFaEcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxLQUFLLEdBQUMsWUFBWSxDQUFDLENBQUMsaUZBQWlGO0FBRWpILE1BQU0sRUFBRSxtQ0FBbUMsRUFBRSxHQUFHLE9BQU8sQ0FBQyw0QkFBNEIsQ0FBQyxDQUFDO0FBRXRGLEtBQUssVUFBVSxPQUFPO0lBQ3JCLE1BQU0sbUNBQW1DLEVBQUUsQ0FBQztBQUM3QyxDQUFDO0FBRUQsT0FBTyxFQUFFLENBQUMifQ== \ No newline at end of file diff --git a/build/azure-pipelines/common/installPlaywright.ts b/build/azure-pipelines/common/installPlaywright.ts index d90b3e657e..c424287c37 100644 --- a/build/azure-pipelines/common/installPlaywright.ts +++ b/build/azure-pipelines/common/installPlaywright.ts @@ -3,11 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { retry } from './retry'; +process.env.DEBUG='pw:install'; // enable logging for this (https://github.com/microsoft/playwright/issues/17394) + const { installDefaultBrowsersForNpmInstall } = require('playwright-core/lib/server'); async function install() { - await retry(() => installDefaultBrowsersForNpmInstall()); + await installDefaultBrowsersForNpmInstall(); } install(); diff --git a/build/azure-pipelines/common/listNodeModules.js b/build/azure-pipelines/common/listNodeModules.js index 308f1882a9..bc94266459 100644 --- a/build/azure-pipelines/common/listNodeModules.js +++ b/build/azure-pipelines/common/listNodeModules.js @@ -38,3 +38,4 @@ function findNodeModulesFiles(location, inNodeModules, result) { const result = []; findNodeModulesFiles('', false, result); fs.writeFileSync(process.argv[2], result.join('\n') + '\n'); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlzdE5vZGVNb2R1bGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibGlzdE5vZGVNb2R1bGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7QUFFaEcseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUU3QixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtJQUM5QixPQUFPLENBQUMsS0FBSyxDQUFDLDRDQUE0QyxDQUFDLENBQUM7SUFDNUQsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsV0FBVyxDQUFDLENBQUM7QUFFL0MsU0FBUyxvQkFBb0IsQ0FBQyxRQUFnQixFQUFFLGFBQXNCLEVBQUUsTUFBZ0I7SUFDdkYsTUFBTSxPQUFPLEdBQUcsRUFBRSxDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQzFELEtBQUssTUFBTSxLQUFLLElBQUksT0FBTyxFQUFFO1FBQzVCLE1BQU0sU0FBUyxHQUFHLEdBQUcsUUFBUSxJQUFJLEtBQUssRUFBRSxDQUFDO1FBRXpDLElBQUksNENBQTRDLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxFQUFFO1lBQ2pFLFNBQVM7U0FDVDtRQUVELElBQUksSUFBYyxDQUFDO1FBQ25CLElBQUk7WUFDSCxJQUFJLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDO1NBQy9DO1FBQUMsT0FBTyxHQUFHLEVBQUU7WUFDYixTQUFTO1NBQ1Q7UUFFRCxJQUFJLElBQUksQ0FBQyxXQUFXLEVBQUUsRUFBRTtZQUN2QixvQkFBb0IsQ0FBQyxTQUFTLEVBQUUsYUFBYSxJQUFJLENBQUMsS0FBSyxLQUFLLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1NBQ3JGO2FBQU07WUFDTixJQUFJLGFBQWEsRUFBRTtnQkFDbEIsTUFBTSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7YUFDakM7U0FDRDtLQUNEO0FBQ0YsQ0FBQztBQUVELE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztBQUM1QixvQkFBb0IsQ0FBQyxFQUFFLEVBQUUsS0FBSyxFQUFFLE1BQU0sQ0FBQyxDQUFDO0FBQ3hDLEVBQUUsQ0FBQyxhQUFhLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js index 8007f8eb5b..de790302f0 100644 --- a/build/azure-pipelines/common/publish.js +++ b/build/azure-pipelines/common/publish.js @@ -232,3 +232,4 @@ function main() { }); } main(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGlzaC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInB1Ymxpc2gudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztnR0FHZ0c7QUFFaEcsWUFBWSxDQUFDOztBQUViLHlCQUF5QjtBQUV6QixpQ0FBaUM7QUFDakMsNkJBQTZCO0FBQzdCLHFDQUFxQztBQUNyQywyQ0FBeUQ7QUFDekQsc0RBQWtIO0FBRWxILHNCQUFzQjtBQUN0QixJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtJQUM1QixPQUFPLENBQUMsS0FBSyxDQUFDLHNIQUFzSCxDQUFDLENBQUM7SUFDdEksT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2pCO0FBRUQsU0FBUyxVQUFVLENBQUMsUUFBZ0IsRUFBRSxNQUFnQjtJQUNyRCxPQUFPLElBQUksT0FBTyxDQUFTLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQ25DLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUM7UUFFM0MsTUFBTTthQUNKLEVBQUUsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7YUFDdEMsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUM7YUFDZCxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5QyxDQUFDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFPRCxTQUFTLG1CQUFtQixDQUFDLE9BQWU7SUFDM0MsT0FBTztRQUNOLEVBQUUsRUFBRSxPQUFPO1FBQ1gsTUFBTSxFQUFFLEtBQUs7S0FDYixDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsU0FBUyxDQUFDLE9BQWU7SUFDakMsT0FBTyxDQUFDLEdBQUcs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 \ No newline at end of file diff --git a/build/azure-pipelines/common/release.js b/build/azure-pipelines/common/release.js index 0127cd4262..8744eb6574 100644 --- a/build/azure-pipelines/common/release.js +++ b/build/azure-pipelines/common/release.js @@ -89,3 +89,4 @@ main().catch(err => { console.error(err); process.exit(1); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVsZWFzZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInJlbGVhc2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztnR0FHZ0c7QUFFaEcsWUFBWSxDQUFDOztBQUViLDJDQUE0QztBQU81QyxTQUFTLG1CQUFtQixDQUFDLE9BQWU7SUFDM0MsT0FBTztRQUNOLEVBQUUsRUFBRSxPQUFPO1FBQ1gsTUFBTSxFQUFFLEtBQUs7S0FDYixDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsU0FBUyxDQUFDLE9BQWU7SUFDakMsTUFBTSxNQUFNLEdBQUcsSUFBSSwyQkFBYyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLENBQUUsRUFBRSxFQUFFLFNBQVMsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLDRCQUE0QixDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZJLE1BQU0sVUFBVSxHQUFHLHlCQUF5QixDQUFDO0lBQzdDLE1BQU0sS0FBSyxHQUFHO1FBQ2IsS0FBSyxFQUFFLDZDQUE2QztRQUNwRCxVQUFVLEVBQUU7WUFDWCxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRTtTQUNwQztLQUNELENBQUM7SUFFRixPQUFPLElBQUksT0FBTyxDQUFTLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQ25DLE1BQU0sQ0FBQyxjQUFjLENBQUMsVUFBVSxFQUFFLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEdBQUcsRUFBRSxPQUFPLEVBQUUsRUFBRTtZQUNqRSxJQUFJLEdBQUcsSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLEdBQUcsRUFBRTtnQkFBRSxPQUFPLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQzthQUFFO1lBRS9DLENBQUMsQ0FBQyxDQUFDLE9BQU8sSUFBSSxPQUFPLENBQUMsTUFBTSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsbUJBQW1CLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxD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 \ No newline at end of file diff --git a/build/azure-pipelines/common/releaseBuild.js b/build/azure-pipelines/common/releaseBuild.js index 3d7046de9d..1e15718e09 100644 --- a/build/azure-pipelines/common/releaseBuild.js +++ b/build/azure-pipelines/common/releaseBuild.js @@ -28,25 +28,29 @@ async function getConfig(client, quality) { } return res.resources[0]; } -async function main() { - const commit = process.env['VSCODE_DISTRO_COMMIT'] || getEnv('BUILD_SOURCEVERSION'); +async function main(force) { + const commit = getEnv('BUILD_SOURCEVERSION'); const quality = getEnv('VSCODE_QUALITY'); const aadCredentials = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], aadCredentials }); - const config = await getConfig(client, quality); - console.log('Quality config:', config); - if (config.frozen) { - console.log(`Skipping release because quality ${quality} is frozen.`); - return; + if (!force) { + const config = await getConfig(client, quality); + console.log('Quality config:', config); + if (config.frozen) { + console.log(`Skipping release because quality ${quality} is frozen.`); + return; + } } console.log(`Releasing build ${commit}...`); const scripts = client.database('builds').container(quality).scripts; await (0, retry_1.retry)(() => scripts.storedProcedure('releaseBuild').execute('', [commit])); } -main().then(() => { +const [, , force] = process.argv; +main(force === 'true').then(() => { console.log('Build successfully released'); process.exit(0); }, err => { console.error(err); process.exit(1); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVsZWFzZUJ1aWxkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicmVsZWFzZUJ1aWxkLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7QUFFaEcsOENBQXlEO0FBQ3pELDBDQUE2QztBQUM3QyxtQ0FBZ0M7QUFFaEMsU0FBUyxNQUFNLENBQUMsSUFBWTtJQUMzQixNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRWpDLElBQUksT0FBTyxNQUFNLEtBQUssV0FBVyxFQUFFO1FBQ2xDLE1BQU0sSUFBSSxLQUFLLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQyxDQUFDO0tBQ3hDO0lBRUQsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDO0FBT0QsU0FBUyxtQkFBbUIsQ0FBQyxPQUFlO0lBQzNDLE9BQU87UUFDTixFQUFFLEVBQUUsT0FBTztRQUNYLE1BQU0sRUFBRSxLQUFLO0tBQ2IsQ0FBQztBQUNILENBQUM7QUFFRCxLQUFLLFVBQVUsU0FBUyxDQUFDLE1BQW9CLEVBQUUsT0FBZTtJQUM3RCxNQUFNLEtBQUssR0FBRyx1Q0FBdUMsT0FBTyxHQUFHLENBQUM7SUFFaEUsTUFBTSxHQUFHLEdBQUcsTUFBTSxNQUFNLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBRTlGLElBQUksR0FBRyxDQUFDLFNBQVMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQy9CLE9BQU8sbUJBQW1CLENBQUMsT0FBTyxDQUFDLENBQUM7S0FDcEM7SUFFRCxPQUFPLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFXLENBQUM7QUFDbkMsQ0FBQztBQUVELEtBQUssVUFBVSxJQUFJLENBQUMsS0FBYztJQUNqQyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMscUJBQXFCLENBQUMsQ0FBQztJQUM3QyxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztJQUV6QyxNQUFNLGNBQWMsR0FBRyxJQUFJLGlDQUFzQixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBRSxDQUFDLENBQUM7SUFDekosTUFBTSxNQUFNLEdBQUcsSUFBSSxxQkFBWSxDQUFDLEVBQUUsUUFBUSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLENBQUUsRUFBRSxjQUFjLEVBQUUsQ0FBQyxDQUFDO0lBRXpHLElBQUksQ0FBQyxLQUFLLEVBQUU7UUFDWCxNQUFNLE1BQU0sR0FBRyxNQUFNLFNBQVMsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFFaEQsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUV2QyxJQUFJLE1BQU0sQ0FBQyxNQUFNLEVBQUU7WUFDbEIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxvQ0FBb0MsT0FBTyxhQUFhLENBQUMsQ0FBQztZQUN0RSxPQUFPO1NBQ1A7S0FDRDtJQUVELE9BQU8sQ0FBQyxHQUFHLENBQUMsbUJBQW1CLE1BQU0sS0FBSyxDQUFDLENBQUM7SUFFNUMsTUFBTSxPQUFPLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxDQUFDO0lBQ3JFLE1BQU0sSUFBQSxhQUFLLEVBQUMsR0FBRyxFQUFFLENBQUMsT0FBTyxDQUFDLGVBQWUsQ0FBQyxjQUFjLENBQUMsQ0FBQyxPQUFPLENBQUMsRUFBRSxFQUFFLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2xGLENBQUM7QUFFRCxNQUFNLENBQUMsRUFBRSxBQUFELEVBQUcsS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztBQUVqQyxJQUFJLENBQUMsS0FBSyxLQUFLLE1BQU0sQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUU7SUFDaEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyw2QkFBNkIsQ0FBQyxDQUFDO0lBQzNDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakIsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxFQUFFO0lBQ1IsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNuQixPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ2pCLENBQUMsQ0FBQyxDQUFDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts index 1ea27a6964..1858567928 100644 --- a/build/azure-pipelines/common/releaseBuild.ts +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -41,19 +41,22 @@ async function getConfig(client: CosmosClient, quality: string): Promise return res.resources[0] as Config; } -async function main(): Promise { - const commit = process.env['VSCODE_DISTRO_COMMIT'] || getEnv('BUILD_SOURCEVERSION'); +async function main(force: boolean): Promise { + const commit = getEnv('BUILD_SOURCEVERSION'); const quality = getEnv('VSCODE_QUALITY'); const aadCredentials = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); - const config = await getConfig(client, quality); - console.log('Quality config:', config); + if (!force) { + const config = await getConfig(client, quality); - if (config.frozen) { - console.log(`Skipping release because quality ${quality} is frozen.`); - return; + console.log('Quality config:', config); + + if (config.frozen) { + console.log(`Skipping release because quality ${quality} is frozen.`); + return; + } } console.log(`Releasing build ${commit}...`); @@ -62,7 +65,9 @@ async function main(): Promise { await retry(() => scripts.storedProcedure('releaseBuild').execute('', [commit])); } -main().then(() => { +const [, , force] = process.argv; + +main(force === 'true').then(() => { console.log('Build successfully released'); process.exit(0); }, err => { diff --git a/build/azure-pipelines/common/retry.js b/build/azure-pipelines/common/retry.js index d2cdab9c8c..6779cc2ddd 100644 --- a/build/azure-pipelines/common/retry.js +++ b/build/azure-pipelines/common/retry.js @@ -26,3 +26,4 @@ async function retry(fn) { throw lastError; } exports.retry = retry; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0cnkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJyZXRyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUV6RixLQUFLLFVBQVUsS0FBSyxDQUFJLEVBQW9CO0lBQ2xELElBQUksU0FBNEIsQ0FBQztJQUVqQyxLQUFLLElBQUksR0FBRyxHQUFHLENBQUMsRUFBRSxHQUFHLElBQUksRUFBRSxFQUFFLEdBQUcsRUFBRSxFQUFFO1FBQ25DLElBQUk7WUFDSCxPQUFPLE1BQU0sRUFBRSxFQUFFLENBQUM7U0FDbEI7UUFBQyxPQUFPLEdBQUcsRUFBRTtZQUNiLElBQUksQ0FBQyxtRUFBbUUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxFQUFFO2dCQUMzRixNQUFNLEdBQUcsQ0FBQzthQUNWO1lBRUQsU0FBUyxHQUFHLEdBQUcsQ0FBQztZQUNoQixNQUFNLE1BQU0sR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQ2pFLE9BQU8sQ0FBQyxHQUFHLENBQUMsK0JBQStCLE1BQU0sT0FBTyxDQUFDLENBQUM7WUFFMUQsMENBQTBDO1lBQzFDLE1BQU0sSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7U0FDOUM7S0FDRDtJQUVELE9BQU8sQ0FBQyxHQUFHLENBQUMsNkJBQTZCLENBQUMsQ0FBQztJQUMzQyxNQUFNLFNBQVMsQ0FBQztBQUNqQixDQUFDO0FBdEJELHNCQXNCQyJ9 \ No newline at end of file diff --git a/build/azure-pipelines/common/sign-win32.js b/build/azure-pipelines/common/sign-win32.js index df190a2a04..e122c3eebe 100644 --- a/build/azure-pipelines/common/sign-win32.js +++ b/build/azure-pipelines/common/sign-win32.js @@ -15,3 +15,4 @@ const path = require("path"); path.dirname(process.argv[2]), path.basename(process.argv[2]) ]); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2lnbi13aW4zMi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInNpZ24td2luMzIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxpQ0FBOEI7QUFDOUIsNkJBQTZCO0FBRTdCLElBQUEsV0FBSSxFQUFDO0lBQ0osT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBRTtJQUM5QixTQUFTO0lBQ1QsT0FBTyxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUU7SUFDdkIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBRTtJQUMvQixPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFFO0lBQy9CLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM3QixJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7Q0FDOUIsQ0FBQyxDQUFDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/common/sign.js b/build/azure-pipelines/common/sign.js index b9fd21d41d..3464ae2dfd 100644 --- a/build/azure-pipelines/common/sign.js +++ b/build/azure-pipelines/common/sign.js @@ -7,12 +7,33 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.main = void 0; const cp = require("child_process"); const fs = require("fs"); -const tmp = require("tmp"); +const path = require("path"); +const os = require("os"); const crypto = require("crypto"); +class Temp { + _files = []; + tmpNameSync() { + const file = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex')); + this._files.push(file); + return file; + } + dispose() { + for (const file of this._files) { + try { + fs.unlinkSync(file); + } + catch (err) { + // noop + } + } + } +} function getParams(type) { switch (type) { case 'windows': return '[{"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"}]'; + case 'windows-appx': + return '[{"keyCode":"CP-229979","operationSetCode":"SigntoolSign","parameters":[{"parameterName":"OpusName","parameterValue":"VS Code"},{"parameterName":"OpusInfo","parameterValue":"https://code.visualstudio.com/"},{"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-229979","operationSetCode":"SigntoolVerify","parameters":[],"toolName":"sign","toolVersion":"1.0"}]'; case 'rpm': return '[{ "keyCode": "CP-450779-Pgp", "operationSetCode": "LinuxSign", "parameters": [], "toolName": "sign", "toolVersion": "1.0" }]'; case 'darwin-sign': @@ -24,7 +45,8 @@ function getParams(type) { } } function main([esrpCliPath, type, cert, username, password, folderPath, pattern]) { - tmp.setGracefulCleanup(); + const tmp = new Temp(); + process.on('exit', () => tmp.dispose()); const patternPath = tmp.tmpNameSync(); fs.writeFileSync(patternPath, pattern); const paramsPath = tmp.tmpNameSync(); @@ -83,3 +105,4 @@ if (require.main === module) { main(process.argv.slice(2)); process.exit(0); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2lnbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInNpZ24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsb0NBQW9DO0FBQ3BDLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0IseUJBQXlCO0FBQ3pCLGlDQUFpQztBQUVqQyxNQUFNLElBQUk7SUFDRCxNQUFNLEdBQWEsRUFBRSxDQUFDO0lBRTlCLFdBQVc7UUFDVixNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsRUFBRSxNQUFNLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQzVFLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3ZCLE9BQU8sSUFBSSxDQUFDO0lBQ2IsQ0FBQztJQUVELE9BQU87UUFDTixLQUFLLE1BQU0sSUFBSSxJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUU7WUFDL0IsSUFBSTtnQkFDSCxFQUFFLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQ3BCO1lBQUMsT0FBTyxHQUFHLEVBQUU7Z0JBQ2IsT0FBTzthQUNQO1NBQ0Q7SUFDRixDQUFDO0NBQ0Q7QUFFRCxTQUFTLFNBQVMsQ0FBQyxJQUFZO0lBQzlCLFFBQVEsSUFBSSxFQUFFO1FBQ2IsS0FBSyxTQUFTO1lBQ2IsT0FBTyx3c0JBQXdzQixDQUFDO1FBQ2p0QixLQUFLLGNBQWM7WUFDbEIsT0FBTyxpbUJBQWltQixDQUFDO1FBQzFtQixLQUFLLEtBQUs7WUFDVCxPQUFPLCtIQUErSCxDQUFDO1FBQ3hJLEtBQUssYUFBYTtZQUNqQixPQUFPLGtNQUFrTSxDQUFDO1FBQzNNLEtBQUssaUJBQWlCO1lBQ3JCLE9BQU8sMkhBQTJILENBQUM7UUFDcEk7WUFDQyxNQUFNLElBQUksS0FBSyxDQUFDLGFBQWEsSUFBSSxZQUFZLENB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 \ No newline at end of file diff --git a/build/azure-pipelines/common/sign.ts b/build/azure-pipelines/common/sign.ts index e7e64b83ca..dd3ba869b9 100644 --- a/build/azure-pipelines/common/sign.ts +++ b/build/azure-pipelines/common/sign.ts @@ -5,13 +5,36 @@ import * as cp from 'child_process'; import * as fs from 'fs'; -import * as tmp from 'tmp'; +import * as path from 'path'; +import * as os from 'os'; import * as crypto from 'crypto'; +class Temp { + private _files: string[] = []; + + tmpNameSync(): string { + const file = path.join(os.tmpdir(), crypto.randomBytes(20).toString('hex')); + this._files.push(file); + return file; + } + + dispose(): void { + for (const file of this._files) { + try { + fs.unlinkSync(file); + } catch (err) { + // noop + } + } + } +} + function getParams(type: string): string { switch (type) { case 'windows': return '[{"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"}]'; + case 'windows-appx': + return '[{"keyCode":"CP-229979","operationSetCode":"SigntoolSign","parameters":[{"parameterName":"OpusName","parameterValue":"VS Code"},{"parameterName":"OpusInfo","parameterValue":"https://code.visualstudio.com/"},{"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-229979","operationSetCode":"SigntoolVerify","parameters":[],"toolName":"sign","toolVersion":"1.0"}]'; case 'rpm': return '[{ "keyCode": "CP-450779-Pgp", "operationSetCode": "LinuxSign", "parameters": [], "toolName": "sign", "toolVersion": "1.0" }]'; case 'darwin-sign': @@ -24,7 +47,8 @@ function getParams(type: string): string { } export function main([esrpCliPath, type, cert, username, password, folderPath, pattern]: string[]) { - tmp.setGracefulCleanup(); + const tmp = new Temp(); + process.on('exit', () => tmp.dispose()); const patternPath = tmp.tmpNameSync(); fs.writeFileSync(patternPath, pattern); diff --git a/build/azure-pipelines/common/sql-computeNodeModulesCacheKey.js b/build/azure-pipelines/common/sql-computeNodeModulesCacheKey.js index b8b1f6d5af..c0b11b3501 100644 --- a/build/azure-pipelines/common/sql-computeNodeModulesCacheKey.js +++ b/build/azure-pipelines/common/sql-computeNodeModulesCacheKey.js @@ -45,3 +45,4 @@ for (let i = 2; i < process.argv.length; i++) { shasum.update(process.argv[i]); } process.stdout.write(shasum.digest('hex')); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3FsLWNvbXB1dGVOb2RlTW9kdWxlc0NhY2hlS2V5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsic3FsLWNvbXB1dGVOb2RlTW9kdWxlc0NhY2hlS2V5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Z0dBR2dHO0FBRWhHLFlBQVksQ0FBQzs7QUFFYix5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLGlDQUFpQztBQUVqQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxXQUFXLENBQUMsQ0FBQztBQUUvQyxTQUFTLFNBQVMsQ0FBQyxRQUFnQixFQUFFLE9BQWUsRUFBRSxNQUFnQjtJQUNyRSxNQUFNLE9BQU8sR0FBRyxFQUFFLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDLENBQUM7SUFFMUQsS0FBSyxNQUFNLEtBQUssSUFBSSxPQUFPLEVBQUU7UUFDNUIsTUFBTSxTQUFTLEdBQUcsR0FBRyxRQUFRLElBQUksS0FBSyxFQUFFLENBQUM7UUFDekMsSUFBSSxJQUFjLENBQUM7UUFDbkIsSUFBSTtZQUNILElBQUksR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUM7U0FDL0M7UUFBQyxPQUFPLEdBQUcsRUFBRTtZQUNiLFNBQVM7U0FDVDtRQUNELElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxFQUFFO1lBQ3ZCLFNBQVMsQ0FBQyxTQUFTLEVBQUUsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1NBQ3RDO2FBQU07WUFDTixJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxLQUFLLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxFQUFFO2dCQUM3QyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUM7YUFDeEM7U0FDRDtLQUNEO0FBQ0YsQ0FBQztBQUVELE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUM7QUFFekM7O0dBRUc7QUFDSCxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDcEUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUMzRCxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFbEUsMkNBQTJDO0FBQzNDLE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztBQUM1QixTQUFTLENBQUMsRUFBRSxFQUFFLFdBQVcsRUFBRSxNQUFNLENBQUMsQ0FBQztBQUNuQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUV2RCx1Q0FBdUM7QUFDdkMsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFO0lBQzdDLE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQy9CO0FBRUQsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/common/telemetry-config.json b/build/azure-pipelines/common/telemetry-config.json index fcba1e042b..d51d7d4dd2 100644 --- a/build/azure-pipelines/common/telemetry-config.json +++ b/build/azure-pipelines/common/telemetry-config.json @@ -1,12 +1,4 @@ [ - { - "eventPrefix": "typescript-language-features/", - "sourceDirs": [ - "../../s/extensions/typescript-language-features" - ], - "excludedDirs": [], - "applyEndpoints": true - }, { "eventPrefix": "git/", "sourceDirs": [ @@ -49,16 +41,6 @@ "excludedDirs": [], "applyEndpoints": true }, - { - "eventPrefix": "ms-vscode.node2/", - "sourceDirs": [ - "vscode-chrome-debug-core", - "vscode-node-debug2" - ], - "excludedDirs": [], - "applyEndpoints": true, - "patchDebugEvents": true - }, { "eventPrefix": "ms-vscode.node/", "sourceDirs": [ @@ -69,4 +51,4 @@ "applyEndpoints": true, "patchDebugEvents": true } -] \ No newline at end of file +] diff --git a/build/azure-pipelines/config/tsaoptions.json b/build/azure-pipelines/config/tsaoptions.json index 560d0c2513..fa8e182d8f 100644 --- a/build/azure-pipelines/config/tsaoptions.json +++ b/build/azure-pipelines/config/tsaoptions.json @@ -1,12 +1,21 @@ { - "instanceUrl": "https://msazure.visualstudio.com/defaultcollection", - "projectName": "One", - "areaPath": "One\\VSCode\\Client", - "iterationPath": "One", + "codebaseName": "devdiv_vscode-client", + "ppe": false, "notificationAliases": [ - "sbatten@microsoft.com" + "sbatten@microsoft.com" ], - "ppe": "false", - "template": "TFSMSAzure", - "codebaseName": "vscode-client" + "codebaseAdmins": [ + "REDMOND\\stbatt", + "REDMOND\\monacotools" + ], + "instanceUrl": "https://devdiv.visualstudio.com/defaultcollection", + "projectName": "DevDiv", + "areaPath": "DevDiv\\VS Code (compliance tracking only)\\Visual Studio Code Client", + "notifyAlways": true, + "template": "TFSDEVDIV", + "tools": [ + "BinSkim", + "CredScan", + "CodeQL" + ] } diff --git a/build/azure-pipelines/darwin/app-entitlements.plist b/build/azure-pipelines/darwin/app-entitlements.plist index 432c66c1df..4073eafcf5 100644 --- a/build/azure-pipelines/darwin/app-entitlements.plist +++ b/build/azure-pipelines/darwin/app-entitlements.plist @@ -4,12 +4,6 @@ com.apple.security.cs.allow-jit - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.allow-dyld-environment-variables - - com.apple.security.cs.disable-library-validation - com.apple.security.device.audio-input com.apple.security.device.camera diff --git a/build/azure-pipelines/darwin/cli-build-darwin.yml b/build/azure-pipelines/darwin/cli-build-darwin.yml new file mode 100644 index 0000000000..c0e3850c2e --- /dev/null +++ b/build/azure-pipelines/darwin/cli-build-darwin.yml @@ -0,0 +1,67 @@ +parameters: + - name: VSCODE_QUALITY + type: string + - name: VSCODE_BUILD_MACOS + type: boolean + default: false + - name: VSCODE_BUILD_MACOS_ARM64 + type: boolean + default: false + +steps: + - task: NodeTool@0 + inputs: + versionSpec: "16.x" + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - template: ../distro/download-distro.yml + - script: node build/azure-pipelines/distro/apply-cli-patches + displayName: Apply distro patches + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.8 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - script: | + set -e + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.8.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + - script: node build/azure-pipelines/cli/prepare.js + displayName: Prepare CLI build + env: + VSCODE_CLI_PREPARE_ROOT: $(Build.SourcesDirectory)/.build/distro + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + GITHUB_TOKEN: "$(github-distro-mixin-password)" + + - template: ../cli/install-rust-posix.yml + parameters: + targets: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - x86_64-apple-darwin + - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: + - aarch64-apple-darwin + + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - template: ../cli/cli-compile-and-publish.yml + parameters: + VSCODE_CLI_TARGET: x86_64-apple-darwin + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_x64_cli + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/include + + - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: + - template: ../cli/cli-compile-and-publish.yml + parameters: + VSCODE_CLI_TARGET: aarch64-apple-darwin + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_arm64_cli + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/include diff --git a/build/azure-pipelines/darwin/helper-plugin-entitlements.plist b/build/azure-pipelines/darwin/helper-plugin-entitlements.plist new file mode 100644 index 0000000000..1cc1a152c7 --- /dev/null +++ b/build/azure-pipelines/darwin/helper-plugin-entitlements.plist @@ -0,0 +1,14 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml new file mode 100644 index 0000000000..ec9ac07e60 --- /dev/null +++ b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml @@ -0,0 +1,50 @@ +parameters: + - name: VSCODE_BUILD_MACOS + type: boolean + - name: VSCODE_BUILD_MACOS_ARM64 + type: boolean + +steps: + - task: NodeTool@0 + inputs: + versionSpec: "16.x" + + - script: node build/setup-npm-registry.js $NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: | + set -e + npm config set registry "$NPM_REGISTRY" --location=project + npm config set always-auth=true --location=project + yarn config set registry "$NPM_REGISTRY" + workingDirectory: build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: build/.npmrc + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + for i in {1..5}; do # try 5 times + yarn --frozen-lockfile --check-files && break + if [ $i -eq 3 ]; then + echo "Yarn failed too many times" >&2 + exit 1 + fi + echo "Yarn failed $i, trying again..." + done + workingDirectory: build + displayName: Install build dependencies + + - template: ../cli/cli-darwin-sign.yml + parameters: + VSCODE_CLI_ARTIFACTS: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - unsigned_vscode_cli_darwin_x64_cli + - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: + - unsigned_vscode_cli_darwin_arm64_cli diff --git a/build/azure-pipelines/darwin/product-build-darwin-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-sign.yml index 059e848c0b..076788581c 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-sign.yml @@ -3,132 +3,43 @@ steps: inputs: versionSpec: "16.x" + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpClientTool@1 + continueOnError: true + displayName: Download ESRPClient + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode - SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - - - 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 fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js x64 $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags - - - task: Cache@2 - inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz - displayName: Extract node_modules cache - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - npm install -g node-gyp@latest - node-gyp --version - displayName: Update node-gyp - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages - - - script: | - set -e - export npm_config_arch=$(VSCODE_ARCH) - export npm_config_node_gyp=$(which node-gyp) - - for i in {1..3}; do # try 3 times, for Terrapin - yarn --frozen-lockfile --check-files && break - if [ $i -eq 3 ]; then - echo "Yarn failed too many times" >&2 - exit 1 - fi - echo "Yarn failed $i, trying again..." - done - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive + KeyVaultName: vscode-build-secrets + SecretsFilter: "ESRP-PKI,esrp-aad-username,esrp-aad-password" - download: current artifact: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive displayName: Download $(VSCODE_ARCH) artifact - - script: | - set -e - unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip - displayName: Unzip & move - - - task: UseDotNet@2 - inputs: - version: 2.x - - - task: EsrpClientTool@1 - displayName: Download ESRPClient - - - script: | - set -e - node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" darwin-sign $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(agent.builddirectory) VSCode-darwin-$(VSCODE_ARCH).zip + - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll darwin-sign $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip displayName: Codesign - - script: | - set -e - node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" darwin-notarize $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(agent.builddirectory) VSCode-darwin-$(VSCODE_ARCH).zip + - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll darwin-notarize $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip displayName: Notarize + - script: unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH) + displayName: Extract signed app + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + - script: | set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + 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) + APP_PATH="$APP_ROOT/$APP_NAME" + codesign -dv --deep --verbose=4 "$APP_PATH" + "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build + displayName: Verify signature condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - script: | @@ -141,9 +52,9 @@ steps: echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" displayName: Set asset id variable - - script: mv $(agent.builddirectory)/VSCode-darwin-x64.zip $(agent.builddirectory)/VSCode-darwin.zip - displayName: Rename x64 build to it's legacy name + - script: mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-x64.zip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip + displayName: Rename x64 build to its legacy name condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - publish: $(Agent.BuildDirectory)/VSCode-$(ASSET_ID).zip + - publish: $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-$(ASSET_ID).zip artifact: vscode_client_darwin_$(VSCODE_ARCH)_archive diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index 1094b41ca2..32a9884a14 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -9,48 +9,39 @@ parameters: type: boolean steps: - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + - script: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - ./scripts/test.sh --tfs "Unit Tests" + - script: ./scripts/test.sh --tfs "Unit Tests" displayName: Run unit tests (Electron) timeoutInMinutes: 15 - - script: | - set -e - yarn test-node + - script: yarn test-node displayName: Run unit tests (node.js) timeoutInMinutes: 15 - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" + - script: yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" displayName: Run unit tests (Browser, Chromium & Webkit) timeoutInMinutes: 30 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - ./scripts/test.sh --build --tfs "Unit Tests" + - script: ./scripts/test.sh --build --tfs "Unit Tests" displayName: Run unit tests (Electron) timeoutInMinutes: 15 - - script: | - set -e - yarn test-node --build + - script: yarn test-node --build displayName: Run unit tests (node.js) timeoutInMinutes: 15 - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + - script: yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" displayName: Run unit tests (Browser, Chromium & Webkit) timeoutInMinutes: 30 @@ -70,16 +61,13 @@ steps: compile-extension:markdown-language-features \ compile-extension-media \ compile-extension:microsoft-authentication \ - compile-extension:typescript-language-features \ compile-extension:vscode-api-tests \ compile-extension:vscode-colorize-tests \ - compile-extension:vscode-notebook-tests \ compile-extension:vscode-test-resolver displayName: Build integration tests - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - ./scripts/test-integration.sh --tfs "Integration Tests" + - script: ./scripts/test-integration.sh --tfs "Integration Tests" displayName: Run integration tests (Electron) timeoutInMinutes: 20 @@ -89,18 +77,18 @@ steps: # 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_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-$(VSCODE_ARCH)" \ ./scripts/test-integration.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH) displayName: Run integration tests (Electron) timeoutInMinutes: 20 - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - ./scripts/test-web-integration.sh --browser webkit + - script: ./scripts/test-web-integration.sh --browser webkit + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH) displayName: Run integration tests (Browser, Webkit) timeoutInMinutes: 20 @@ -109,28 +97,26 @@ steps: 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-$(VSCODE_ARCH)" \ ./scripts/test-remote-integration.sh + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH) displayName: Run integration tests (Remote) timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - script: | - set -e - ps -ef + - script: ps -ef displayName: Diagnostics before smoke test run continueOnError: true condition: succeededOrFailed() - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - yarn --cwd test/smoke compile + - script: yarn --cwd test/smoke compile displayName: Compile smoke tests - - script: | - set -e - yarn smoketest-no-compile --tracing + - script: yarn gulp compile-extension-media + displayName: Compile extensions for smoke tests + + - script: yarn smoketest-no-compile --tracing timeoutInMinutes: 20 displayName: Run smoke tests (Electron) @@ -143,10 +129,9 @@ steps: timeoutInMinutes: 20 displayName: Run smoke tests (Electron) - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --tracing --headless + - script: yarn smoketest-no-compile --web --tracing --headless + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH) timeoutInMinutes: 20 displayName: Run smoke tests (Browser, Chromium) @@ -155,14 +140,13 @@ steps: yarn gulp compile-extension:vscode-test-resolver APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) APP_NAME="`ls $APP_ROOT | head -n 1`" - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ yarn smoketest-no-compile --tracing --remote --build "$APP_ROOT/$APP_NAME" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH) timeoutInMinutes: 20 displayName: Run smoke tests (Remote) - - script: | - set -e - ps -ef + - script: ps -ef displayName: Diagnostics after smoke test run continueOnError: true condition: succeededOrFailed() diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index 929aaf4203..c52e8129a5 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -3,77 +3,37 @@ steps: inputs: versionSpec: "16.x" + - template: ../distro/download-distro.yml + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - 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: node build/setup-npm-registry.js $NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - script: | set -e - git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit + npm config set registry "$NPM_REGISTRY" --location=project + npm config set always-auth=true --location=project + yarn config set registry "$NPM_REGISTRY" + workingDirectory: build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js x64 $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags - - - task: Cache@2 + - task: npmAuthenticate@0 inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache + workingFile: build/.npmrc + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication - script: | set -e - tar -xzf .build/node_modules_cache/cache.tgz - displayName: Extract node_modules cache - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - npm install -g node-gyp@latest - node-gyp --version - displayName: Update node-gyp - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages - - - script: | - set -e - export npm_config_arch=$(VSCODE_ARCH) - export npm_config_node_gyp=$(which node-gyp) - - for i in {1..3}; do # try 3 times, for Terrapin + for i in {1..5}; do # try 5 times yarn --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 @@ -81,25 +41,8 @@ steps: fi echo "Yarn failed $i, trying again..." done - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality + workingDirectory: build + displayName: Install build dependencies - download: current artifact: unsigned_vscode_client_darwin_x64_archive @@ -109,13 +52,14 @@ steps: artifact: unsigned_vscode_client_darwin_arm64_archive displayName: Download arm64 artifact + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + - script: | set -e - cp $(Pipeline.Workspace)/unsigned_vscode_client_darwin_x64_archive/VSCode-darwin-x64.zip $(agent.builddirectory)/VSCode-darwin-x64.zip - cp $(Pipeline.Workspace)/unsigned_vscode_client_darwin_arm64_archive/VSCode-darwin-arm64.zip $(agent.builddirectory)/VSCode-darwin-arm64.zip - unzip $(agent.builddirectory)/VSCode-darwin-x64.zip -d $(agent.builddirectory)/VSCode-darwin-x64 - unzip $(agent.builddirectory)/VSCode-darwin-arm64.zip -d $(agent.builddirectory)/VSCode-darwin-arm64 - DEBUG=* node build/darwin/create-universal-app.js + unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_x64_archive/VSCode-darwin-x64.zip -d $(agent.builddirectory)/VSCode-darwin-x64 + unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_arm64_archive/VSCode-darwin-arm64.zip -d $(agent.builddirectory)/VSCode-darwin-arm64 + DEBUG=* node build/darwin/create-universal-app.js $(agent.builddirectory) displayName: Create Universal App - script: | @@ -125,13 +69,12 @@ steps: 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 + export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) 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 + DEBUG=electron-osx-sign* node build/darwin/sign.js $(agent.builddirectory) displayName: Set Hardened Entitlements - - script: | - set -e - pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip * && popd + - script: pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip * && popd displayName: Archive build - publish: $(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH).zip diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index eda79c53cf..c315dbb3d3 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -1,8 +1,8 @@ parameters: - - name: VSCODE_PUBLISH - type: boolean - name: VSCODE_QUALITY type: string + - name: VSCODE_CIBUILD + type: boolean - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS @@ -21,86 +21,62 @@ steps: versionSpec: "16.x" - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - template: ../distro/download-distro.yml + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: DownloadPipelineArtifact@2 inputs: artifact: Compilation path: $(Build.ArtifactStagingDirectory) displayName: Download compilation output - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz displayName: Extract compilation output - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH > .build/yarnlockhash + displayName: Prepare node_modules cache key - task: Cache@2 inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + key: '"node_modules" | .build/yarnlockhash' path: .build/node_modules_cache cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz + - script: tar -xzf .build/node_modules_cache/cache.tgz condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) displayName: Extract node_modules cache - script: | set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages + npm config set registry "$NPM_REGISTRY" --location=project + npm config set always-auth=true --location=project + yarn config set registry "$NPM_REGISTRY" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication - script: | set -e export npm_config_arch=$(VSCODE_ARCH) export npm_config_node_gyp=$(which node-gyp) - for i in {1..3}; do # try 3 times, for Terrapin + for i in {1..5}; do # try 5 times yarn --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 @@ -115,6 +91,11 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: node build/azure-pipelines/distro/mixin-npm + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + - script: | set -e node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt @@ -124,40 +105,32 @@ steps: displayName: Create node_modules archive - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - # This script brings in the right resources (images, icons, etc) based on the quality (insiders, stable, exploration) - - script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + + - template: ../common/install-builtin-extensions.yml - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci + - script: yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build client - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - node build/azure-pipelines/mixin --server - displayName: Mix in server quality + - script: yarn gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci - displayName: Build Server + - script: yarn gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server (web) - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp "transpile-client" "transpile-extensions" - displayName: Transpile + - ${{ else }}: + - script: yarn gulp transpile-client-swc transpile-extensions + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Transpile - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - template: product-build-darwin-test.yml @@ -167,7 +140,27 @@ steps: VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: + - ${{ elseif and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + patterns: "**" + path: $(Build.ArtifactStagingDirectory)/cli + displayName: Download VS Code CLI + + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + ARCHIVE_NAME=$(ls "$(Build.ArtifactStagingDirectory)/cli" | head -n 1) + unzip "$(Build.ArtifactStagingDirectory)/cli/$ARCHIVE_NAME" -d "$(Build.ArtifactStagingDirectory)/cli" + CLI_APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").tunnelApplicationName") + APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").applicationName") + mv "$(Build.ArtifactStagingDirectory)/cli/$APP_NAME" "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" + chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" + displayName: Make CLI executable + # Setting hardened entitlements is a requirement for: # * Apple notarization # * Running tests on Big Sur (because Big Sur has additional security precautions) @@ -178,17 +171,14 @@ steps: 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 + export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) 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 + DEBUG=electron-osx-sign* node build/darwin/sign.js $(agent.builddirectory) displayName: Set Hardened Entitlements - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - - script: | - set -e - pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip * && popd + - script: cd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH).zip * displayName: Archive build - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - script: | set -e @@ -199,46 +189,38 @@ steps: pushd .. && mv vscode-reh-web-darwin-$(VSCODE_ARCH) vscode-server-darwin-$(VSCODE_ARCH)-web && zip -Xry vscode-server-darwin-$(VSCODE_ARCH)-web.zip vscode-server-darwin-$(VSCODE_ARCH)-web && popd displayName: Prepare to publish servers - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (client) inputs: BuildDropPath: $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) PackageName: Visual Studio Code - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - publish: $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (client) artifact: vscode_client_darwin_$(VSCODE_ARCH)_sbom - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (server) inputs: BuildDropPath: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) PackageName: Visual Studio Code Server - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - publish: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (server) artifact: vscode_server_darwin_$(VSCODE_ARCH)_sbom - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - publish: $(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH).zip artifact: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive displayName: Publish client archive - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - publish: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH).zip artifact: vscode_server_darwin_$(VSCODE_ARCH)_archive-unsigned displayName: Publish server archive - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - publish: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web.zip artifact: vscode_web_darwin_$(VSCODE_ARCH)_archive-unsigned displayName: Publish web server archive - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - task: AzureCLI@2 inputs: azureSubscription: "vscode-builds-subscription" @@ -250,7 +232,6 @@ steps: Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey" - - ${{ if and(eq(parameters.VSCODE_PUBLISH, true), eq(parameters.VSCODE_RUN_UNIT_TESTS, false), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, false), eq(parameters.VSCODE_RUN_SMOKE_TESTS, false)) }}: - script: | set -e AZURE_STORAGE_ACCOUNT="ticino" \ diff --git a/build/azure-pipelines/darwin/sql-product-build-darwin.yml b/build/azure-pipelines/darwin/sql-product-build-darwin.yml index fb5082ecaf..054a588ffc 100644 --- a/build/azure-pipelines/darwin/sql-product-build-darwin.yml +++ b/build/azure-pipelines/darwin/sql-product-build-darwin.yml @@ -172,7 +172,7 @@ steps: set -e unzip $(agent.builddirectory)/drop/darwin/archive/azuredatastudio-darwin-x64-unsigned.zip -d $(agent.builddirectory)/azuredatastudio-darwin-x64 unzip $(agent.builddirectory)/drop/darwin/archive/azuredatastudio-darwin-arm64-unsigned.zip -d $(agent.builddirectory)/azuredatastudio-darwin-arm64 - DEBUG=* node build/darwin/create-universal-app.js + DEBUG=* node build/darwin/create-universal-app.js $(agent.builddirectory) displayName: Create Universal App condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'universal')) diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index db2d245cc5..fe09ecdeb2 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -10,35 +10,6 @@ steps: - task: NodeTool@0 inputs: versionSpec: "16.x" - - - task: AzureKeyVault@1 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode - SecretsFilter: "github-distro-mixin-password" - - - 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" - - git remote add distro "https://github.com/$VSCODE_MIXIN_REPO.git" - git fetch distro - - # Push main branch into oss/main - git push distro origin/main:refs/heads/oss/main - - # 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") - - displayName: Sync & Merge Distro + - template: ./distro/download-distro.yml + - script: node build/azure-pipelines/distro/apply-cli-patches + displayName: Apply distro patches diff --git a/build/azure-pipelines/distro/apply-cli-patches.js b/build/azure-pipelines/distro/apply-cli-patches.js new file mode 100644 index 0000000000..3ef9834b94 --- /dev/null +++ b/build/azure-pipelines/distro/apply-cli-patches.js @@ -0,0 +1,18 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = require("fs"); +const cp = require("child_process"); +function log(...args) { + console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); +} +log(`Applying CLI patches...`); +const basePath = `.build/distro/cli-patches`; +for (const patch of fs.readdirSync(basePath)) { + cp.execSync(`git apply --ignore-whitespace --ignore-space-change ${basePath}/${patch}`, { stdio: 'inherit' }); + log('Applied CLI patch:', patch, '✔︎'); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbHktY2xpLXBhdGNoZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJhcHBseS1jbGktcGF0Y2hlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLHlCQUF5QjtBQUN6QixvQ0FBb0M7QUFFcEMsU0FBUyxHQUFHLENBQUMsR0FBRyxJQUFXO0lBQzFCLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxJQUFJLElBQUksRUFBRSxDQUFDLGtCQUFrQixDQUFDLElBQUksRUFBRSxFQUFFLE1BQU0sRUFBRSxLQUFLLEVBQUUsQ0FBQyxHQUFHLEVBQUUsVUFBVSxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7QUFDakcsQ0FBQztBQUVELEdBQUcsQ0FBQyx5QkFBeUIsQ0FBQyxDQUFDO0FBRS9CLE1BQU0sUUFBUSxHQUFHLDJCQUEyQixDQUFDO0FBRTdDLEtBQUssTUFBTSxLQUFLLElBQUksRUFBRSxDQUFDLFdBQVcsQ0FBQyxRQUFRLENBQUMsRUFBRTtJQUM3QyxFQUFFLENBQUMsUUFBUSxDQUFDLHVEQUF1RCxRQUFRLElBQUksS0FBSyxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQUUsU0FBUyxFQUFFLENBQUMsQ0FBQztJQUM5RyxHQUFHLENBQUMsb0JBQW9CLEVBQUUsS0FBSyxFQUFFLElBQUksQ0FBQyxDQUFDO0NBQ3ZDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/distro/apply-cli-patches.ts b/build/azure-pipelines/distro/apply-cli-patches.ts new file mode 100644 index 0000000000..f3d6a08c6f --- /dev/null +++ b/build/azure-pipelines/distro/apply-cli-patches.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as cp from 'child_process'; + +function log(...args: any[]): void { + console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); +} + +log(`Applying CLI patches...`); + +const basePath = `.build/distro/cli-patches`; + +for (const patch of fs.readdirSync(basePath)) { + cp.execSync(`git apply --ignore-whitespace --ignore-space-change ${basePath}/${patch}`, { stdio: 'inherit' }); + log('Applied CLI patch:', patch, '✔︎'); +} diff --git a/build/azure-pipelines/distro/download-distro.yml b/build/azure-pipelines/distro/download-distro.yml new file mode 100644 index 0000000000..2e727b28b4 --- /dev/null +++ b/build/azure-pipelines/distro/download-distro.yml @@ -0,0 +1,56 @@ +steps: + - task: AzureKeyVault@1 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: "vscode-builds-subscription" + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + # TODO@joaomoreno: Keep pwsh once we move out of running entire jobs in containers + - pwsh: | + "machine github.com`nlogin vscode`npassword $(github-distro-mixin-password)" | Out-File "$Home/_netrc" -Encoding ASCII + condition: and(succeeded(), contains(variables['Agent.OS'], 'windows')) + displayName: Setup distro auth + + - pwsh: | + $ErrorActionPreference = "Stop" + $ArchivePath = "$(Agent.TempDirectory)/distro.zip" + $PackageJson = Get-Content -Path package.json -Raw | ConvertFrom-Json + $DistroVersion = $PackageJson.distro + + Invoke-WebRequest -Uri "https://api.github.com/repos/microsoft/vscode-distro/zipball/$DistroVersion" ` + -OutFile $ArchivePath ` + -Headers @{ "Accept" = "application/vnd.github+json"; "Authorization" = "Bearer $(github-distro-mixin-password)"; "X-GitHub-Api-Version" = "2022-11-28" } + + New-Item -ItemType Directory -Path .build -Force + Expand-Archive -Path $ArchivePath -DestinationPath .build + Rename-Item -Path ".build/microsoft-vscode-distro-$DistroVersion" -NewName distro + condition: and(succeeded(), contains(variables['Agent.OS'], 'windows')) + displayName: Download distro + + - script: | + mkdir -p .build + cat << EOF | tee ~/.netrc .build/.netrc > /dev/null + machine github.com + login vscode + password $(github-distro-mixin-password) + EOF + condition: and(succeeded(), not(contains(variables['Agent.OS'], 'windows'))) + displayName: Setup distro auth + + - script: | + set -e + ArchivePath="$(Agent.TempDirectory)/distro.zip" + DistroVersion=$(node -p "require('./package.json').distro") + + curl -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer $(github-distro-mixin-password)" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -o $ArchivePath \ + -L "https://api.github.com/repos/microsoft/vscode-distro/zipball/$DistroVersion" + + unzip $ArchivePath -d .build + mv .build/microsoft-vscode-distro-$DistroVersion .build/distro + cp remote/.yarnrc .build/distro/npm/remote/.yarnrc + condition: and(succeeded(), not(contains(variables['Agent.OS'], 'windows'))) + displayName: Download distro diff --git a/build/azure-pipelines/distro/mixin-npm.js b/build/azure-pipelines/distro/mixin-npm.js new file mode 100644 index 0000000000..ba17d6d952 --- /dev/null +++ b/build/azure-pipelines/distro/mixin-npm.js @@ -0,0 +1,35 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = require("fs"); +const path = require("path"); +const { dirs } = require('../../npm/dirs'); +function log(...args) { + console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); +} +function mixin(mixinPath) { + if (!fs.existsSync(`${mixinPath}/node_modules`)) { + log(`Skipping distro npm dependencies: ${mixinPath} (no node_modules)`); + return; + } + log(`Mixing in distro npm dependencies: ${mixinPath}`); + const distroPackageJson = JSON.parse(fs.readFileSync(`${mixinPath}/package.json`, 'utf8')); + const targetPath = path.relative('.build/distro/npm', mixinPath); + for (const dependency of Object.keys(distroPackageJson.dependencies)) { + fs.rmSync(`./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true }); + fs.cpSync(`${mixinPath}/node_modules/${dependency}`, `./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true, dereference: true }); + } + log(`Mixed in distro npm dependencies: ${mixinPath} ✔︎`); +} +function main() { + log(`Mixing in distro npm dependencies...`); + const mixinPaths = dirs.filter(d => /^.build\/distro\/npm/.test(d)); + for (const mixinPath of mixinPaths) { + mixin(mixinPath); + } +} +main(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWl4aW4tbnBtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibWl4aW4tbnBtLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7QUFFaEcseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixNQUFNLEVBQUUsSUFBSSxFQUFFLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixDQUF1QixDQUFDO0FBRWpFLFNBQVMsR0FBRyxDQUFDLEdBQUcsSUFBVztJQUMxQixPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksSUFBSSxJQUFJLEVBQUUsQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLEVBQUUsRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLENBQUMsR0FBRyxFQUFFLFVBQVUsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO0FBQ2pHLENBQUM7QUFFRCxTQUFTLEtBQUssQ0FBQyxTQUFpQjtJQUMvQixJQUFJLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxHQUFHLFNBQVMsZUFBZSxDQUFDLEVBQUU7UUFDaEQsR0FBRyxDQUFDLHFDQUFxQyxTQUFTLG9CQUFvQixDQUFDLENBQUM7UUFDeEUsT0FBTztLQUNQO0lBRUQsR0FBRyxDQUFDLHNDQUFzQyxTQUFTLEVBQUUsQ0FBQyxDQUFDO0lBRXZELE1BQU0saUJBQWlCLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLEdBQUcsU0FBUyxlQUFlLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztJQUMzRixNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLG1CQUFtQixFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBRWpFLEtBQUssTUFBTSxVQUFVLElBQUksTUFBTSxDQUFDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxZQUFZLENBQUMsRUFBRTtRQUNyRSxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssVUFBVSxpQkFBaUIsVUFBVSxFQUFFLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzFGLEVBQUUsQ0FBQyxNQUFNLENBQUMsR0FBRyxTQUFTLGlCQUFpQixVQUFVLEVBQUUsRUFBRSxLQUFLLFVBQVUsaUJBQWlCLFVBQVUsRUFBRSxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO0tBQ3hKO0lBRUQsR0FBRyxDQUFDLHFDQUFxQyxTQUFTLEtBQUssQ0FBQyxDQUFDO0FBQzFELENBQUM7QUFFRCxTQUFTLElBQUk7SUFDWixHQUFHLENBQUMsc0NBQXNDLENBQUMsQ0FBQztJQUU1QyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsc0JBQXNCLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFcEUsS0FBSyxNQUFNLFNBQVMsSUFBSSxVQUFVLEVBQUU7UUFDbkMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0tBQ2pCO0FBQ0YsQ0FBQztBQUVELElBQUksRUFBRSxDQUFDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/distro/mixin-npm.ts b/build/azure-pipelines/distro/mixin-npm.ts new file mode 100644 index 0000000000..4a51c6fc42 --- /dev/null +++ b/build/azure-pipelines/distro/mixin-npm.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; +const { dirs } = require('../../npm/dirs') as { dirs: string[] }; + +function log(...args: any[]): void { + console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); +} + +function mixin(mixinPath: string) { + if (!fs.existsSync(`${mixinPath}/node_modules`)) { + log(`Skipping distro npm dependencies: ${mixinPath} (no node_modules)`); + return; + } + + log(`Mixing in distro npm dependencies: ${mixinPath}`); + + const distroPackageJson = JSON.parse(fs.readFileSync(`${mixinPath}/package.json`, 'utf8')); + const targetPath = path.relative('.build/distro/npm', mixinPath); + + for (const dependency of Object.keys(distroPackageJson.dependencies)) { + fs.rmSync(`./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true }); + fs.cpSync(`${mixinPath}/node_modules/${dependency}`, `./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true, dereference: true }); + } + + log(`Mixed in distro npm dependencies: ${mixinPath} ✔︎`); +} + +function main() { + log(`Mixing in distro npm dependencies...`); + + const mixinPaths = dirs.filter(d => /^.build\/distro\/npm/.test(d)); + + for (const mixinPath of mixinPaths) { + mixin(mixinPath); + } +} + +main(); diff --git a/build/azure-pipelines/distro/mixin-quality.js b/build/azure-pipelines/distro/mixin-quality.js new file mode 100644 index 0000000000..3f1e1f5cc7 --- /dev/null +++ b/build/azure-pipelines/distro/mixin-quality.js @@ -0,0 +1,53 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = require("fs"); +const path = require("path"); +function log(...args) { + console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); +} +function main() { + const quality = process.env['VSCODE_QUALITY']; + if (!quality) { + throw new Error('Missing VSCODE_QUALITY, skipping mixin'); + } + log(`Mixing in distro quality...`); + const basePath = `.build/distro/mixin/${quality}`; + for (const name of fs.readdirSync(basePath)) { + const distroPath = path.join(basePath, name); + const ossPath = path.relative(basePath, distroPath); + if (ossPath === 'product.json') { + const distro = JSON.parse(fs.readFileSync(distroPath, 'utf8')); + const oss = JSON.parse(fs.readFileSync(ossPath, 'utf8')); + let builtInExtensions = oss.builtInExtensions; + if (Array.isArray(distro.builtInExtensions)) { + log('Overwriting built-in extensions:', distro.builtInExtensions.map(e => e.name)); + builtInExtensions = distro.builtInExtensions; + } + else if (distro.builtInExtensions) { + const include = distro.builtInExtensions['include'] ?? []; + const exclude = distro.builtInExtensions['exclude'] ?? []; + log('OSS built-in extensions:', builtInExtensions.map(e => e.name)); + log('Including built-in extensions:', include.map(e => e.name)); + log('Excluding built-in extensions:', exclude); + builtInExtensions = builtInExtensions.filter(ext => !include.find(e => e.name === ext.name) && !exclude.find(name => name === ext.name)); + builtInExtensions = [...builtInExtensions, ...include]; + log('Final built-in extensions:', builtInExtensions.map(e => e.name)); + } + else { + log('Inheriting OSS built-in extensions', builtInExtensions.map(e => e.name)); + } + const result = { webBuiltInExtensions: oss.webBuiltInExtensions, ...distro, builtInExtensions }; + fs.writeFileSync(ossPath, JSON.stringify(result, null, '\t'), 'utf8'); + } + else { + fs.cpSync(distroPath, ossPath, { force: true, recursive: true }); + } + log(distroPath, '✔︎'); + } +} +main(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWl4aW4tcXVhbGl0eS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm1peGluLXF1YWxpdHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBbUI3QixTQUFTLEdBQUcsQ0FBQyxHQUFHLElBQVc7SUFDMUIsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLElBQUksSUFBSSxFQUFFLENBQUMsa0JBQWtCLENBQUMsSUFBSSxFQUFFLEVBQUUsTUFBTSxFQUFFLEtBQUssRUFBRSxDQUFDLEdBQUcsRUFBRSxVQUFVLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztBQUNqRyxDQUFDO0FBRUQsU0FBUyxJQUFJO0lBQ1osTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO0lBRTlDLElBQUksQ0FBQyxPQUFPLEVBQUU7UUFDYixNQUFNLElBQUksS0FBSyxDQUFDLHdDQUF3QyxDQUFDLENBQUM7S0FDMUQ7SUFFRCxHQUFHLENBQUMsNkJBQTZCLENBQUMsQ0FBQztJQUVuQyxNQUFNLFFBQVEsR0FBRyx1QkFBdUIsT0FBTyxFQUFFLENBQUM7SUFFbEQsS0FBSyxNQUFNLElBQUksSUFBSSxFQUFFLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxFQUFFO1FBQzVDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQzdDLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxDQUFDO1FBRXBELElBQUksT0FBTyxLQUFLLGNBQWMsRUFBRTtZQUMvQixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsVUFBVSxFQUFFLE1BQU0sQ0FBQyxDQUFZLENBQUM7WUFDMUUsTUFBTSxHQUFHLEdBQUcsSUFBSSxD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 \ No newline at end of file diff --git a/build/azure-pipelines/distro/mixin-quality.ts b/build/azure-pipelines/distro/mixin-quality.ts new file mode 100644 index 0000000000..a7d2f88932 --- /dev/null +++ b/build/azure-pipelines/distro/mixin-quality.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; + +interface IBuiltInExtension { + readonly name: string; + readonly version: string; + readonly repo: string; + readonly metadata: any; +} + +interface OSSProduct { + readonly builtInExtensions: IBuiltInExtension[]; + readonly webBuiltInExtensions?: IBuiltInExtension[]; +} + +interface Product { + readonly builtInExtensions?: IBuiltInExtension[] | { 'include'?: IBuiltInExtension[]; 'exclude'?: string[] }; + readonly webBuiltInExtensions?: IBuiltInExtension[]; +} + +function log(...args: any[]): void { + console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); +} + +function main() { + const quality = process.env['VSCODE_QUALITY']; + + if (!quality) { + throw new Error('Missing VSCODE_QUALITY, skipping mixin'); + } + + log(`Mixing in distro quality...`); + + const basePath = `.build/distro/mixin/${quality}`; + + for (const name of fs.readdirSync(basePath)) { + const distroPath = path.join(basePath, name); + const ossPath = path.relative(basePath, distroPath); + + if (ossPath === 'product.json') { + const distro = JSON.parse(fs.readFileSync(distroPath, 'utf8')) as Product; + const oss = JSON.parse(fs.readFileSync(ossPath, 'utf8')) as OSSProduct; + let builtInExtensions = oss.builtInExtensions; + + if (Array.isArray(distro.builtInExtensions)) { + log('Overwriting built-in extensions:', distro.builtInExtensions.map(e => e.name)); + + builtInExtensions = distro.builtInExtensions; + } else if (distro.builtInExtensions) { + const include = distro.builtInExtensions['include'] ?? []; + const exclude = distro.builtInExtensions['exclude'] ?? []; + + log('OSS built-in extensions:', builtInExtensions.map(e => e.name)); + log('Including built-in extensions:', include.map(e => e.name)); + log('Excluding built-in extensions:', exclude); + + builtInExtensions = builtInExtensions.filter(ext => !include.find(e => e.name === ext.name) && !exclude.find(name => name === ext.name)); + builtInExtensions = [...builtInExtensions, ...include]; + + log('Final built-in extensions:', builtInExtensions.map(e => e.name)); + } else { + log('Inheriting OSS built-in extensions', builtInExtensions.map(e => e.name)); + } + + const result = { webBuiltInExtensions: oss.webBuiltInExtensions, ...distro, builtInExtensions }; + fs.writeFileSync(ossPath, JSON.stringify(result, null, '\t'), 'utf8'); + } else { + fs.cpSync(distroPath, ossPath, { force: true, recursive: true }); + } + + log(distroPath, '✔︎'); + } +} + +main(); diff --git a/build/azure-pipelines/exploration-build.yml b/build/azure-pipelines/exploration-build.yml deleted file mode 100644 index a81a83a0a5..0000000000 --- a/build/azure-pipelines/exploration-build.yml +++ /dev/null @@ -1,41 +0,0 @@ -pool: - vmImage: "Ubuntu-16.04" - -trigger: - branches: - include: ["main"] -pr: - branches: - include: ["main"] - -steps: - - task: NodeTool@0 - inputs: - versionSpec: "16.x" - - - task: AzureKeyVault@1 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode - SecretsFilter: "github-distro-mixin-password" - - - 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" - - git checkout origin/electron-12.x.y - git merge origin/main - - # Push main branch into exploration branch - git push origin HEAD:electron-12.x.y - - displayName: Sync & Merge Exploration diff --git a/build/azure-pipelines/linux/Dockerfile b/build/azure-pipelines/linux/Dockerfile index c2b6b2ac34..d02318286c 100644 --- a/build/azure-pipelines/linux/Dockerfile +++ b/build/azure-pipelines/linux/Dockerfile @@ -1,22 +1,20 @@ -#Download base image ubuntu 20.04 -FROM mcr.microsoft.com/mirror/docker/library/ubuntu:20.04 +#Download base image ubuntu 22.04 +FROM mcr.microsoft.com/mirror/docker/library/ubuntu:22.04 -#Adding apt repos for g++-4.9 -RUN echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial main" >> /etc/apt/sources.list -RUN echo "deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe" >> /etc/apt/sources.list +#Set timezone to avoid blocking prompts on docker build +ENV TZ=America/Los_Angeles +RUN ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime +RUN echo "$TZ" > /etc/timezone + +RUN apt-get update +RUN apt-get install -y tzdata # Update Software repository RUN apt-get update && apt-get upgrade -y RUN apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 \ libkrb5-dev git apt-transport-https ca-certificates curl gnupg-agent software-properties-common \ - libnss3 libasound2 make gcc libx11-dev fakeroot rpm libgconf-2-4 libunwind8 g++-4.9 python-dev \ - libgbm-dev - -RUN rm /usr/bin/gcc -RUN rm /usr/bin/g++ -RUN ln -s /usr/bin/gcc-4.9 /usr/bin/gcc -RUN ln -s /usr/bin/g++-4.9 /usr/bin/g++ + libnss3 libasound2 make gcc libx11-dev fakeroot rpm libgconf-2-4 libunwind8 g++ libgbm-dev #docker RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - diff --git a/build/azure-pipelines/linux/cli-build-linux.yml b/build/azure-pipelines/linux/cli-build-linux.yml new file mode 100644 index 0000000000..1b94c69d78 --- /dev/null +++ b/build/azure-pipelines/linux/cli-build-linux.yml @@ -0,0 +1,91 @@ +parameters: + - name: VSCODE_BUILD_LINUX + type: boolean + default: false + - name: VSCODE_BUILD_LINUX_ARM64 + type: boolean + default: false + - name: VSCODE_BUILD_LINUX_ARMHF + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +steps: + - task: NodeTool@0 + inputs: + versionSpec: "16.x" + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - template: ../distro/download-distro.yml + - script: node build/azure-pipelines/distro/apply-cli-patches + displayName: Apply distro patches + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.8 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - script: | + set -e + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.8.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: + - bash: sudo apt-get install -yq gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf binutils-arm-linux-gnueabihf + displayName: Install arm32 toolchains + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: + - bash: sudo apt-get install -yq gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu + displayName: Install arm64 toolchains + + - script: node build/azure-pipelines/cli/prepare.js + displayName: Prepare CLI build + env: + VSCODE_CLI_PREPARE_ROOT: $(Build.SourcesDirectory)/.build/distro + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + GITHUB_TOKEN: "$(github-distro-mixin-password)" + + - template: ../cli/install-rust-posix.yml + parameters: + targets: + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: + - aarch64-unknown-linux-gnu + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - x86_64-unknown-linux-gnu + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: + - armv7-unknown-linux-gnueabihf + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: + - template: ../cli/cli-compile-and-publish.yml + parameters: + VSCODE_CLI_TARGET: aarch64-unknown-linux-gnu + VSCODE_CLI_ARTIFACT: vscode_cli_linux_arm64_cli + VSCODE_CLI_ENV: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/include + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - template: ../cli/cli-compile-and-publish.yml + parameters: + VSCODE_CLI_TARGET: x86_64-unknown-linux-gnu + VSCODE_CLI_ARTIFACT: vscode_cli_linux_x64_cli + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/include + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: + - template: ../cli/cli-compile-and-publish.yml + parameters: + VSCODE_CLI_TARGET: armv7-unknown-linux-gnueabihf + VSCODE_CLI_ARTIFACT: vscode_cli_linux_armhf_cli + VSCODE_CLI_ENV: + CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/include diff --git a/build/azure-pipelines/linux/product-build-alpine.yml b/build/azure-pipelines/linux/product-build-alpine.yml deleted file mode 100644 index 3aef727924..0000000000 --- a/build/azure-pipelines/linux/product-build-alpine.yml +++ /dev/null @@ -1,197 +0,0 @@ -steps: - - task: NodeTool@0 - inputs: - versionSpec: "16.x" - - - task: AzureKeyVault@1 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode - SecretsFilter: "github-distro-mixin-password" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: | - set -e - tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - - 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-$(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 fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js "alpine" $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags - - - task: Cache@2 - inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages - - - script: | - set -e - for i in {1..3}; do # try 3 times, for Terrapin - yarn --frozen-lockfile --check-files --check-files && break - if [ $i -eq 3 ]; then - echo "Yarn failed too many times" >&2 - exit 1 - fi - echo "Yarn failed $i, trying again..." - done - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - script: | - set -e - node build/azure-pipelines/mixin - node build/azure-pipelines/mixin --server - displayName: Mix in quality - - - script: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - displayName: "Register Docker QEMU" - condition: eq(variables['VSCODE_ARCH'], 'arm64') - - - script: | - set -e - docker run -e VSCODE_QUALITY -e GITHUB_TOKEN -v $(pwd):/root/vscode -v ~/.netrc:/root/.netrc vscodehub.azurecr.io/vscode-linux-build-agent:alpine-$(VSCODE_ARCH) /root/vscode/build/azure-pipelines/linux/scripts/install-remote-dependencies.sh - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Prebuild - - - script: | - set -e - - case $VSCODE_ARCH in - x64) - reh='vscode-reh-linux-alpine-min-ci' - rehweb='vscode-reh-web-linux-alpine-min-ci' - ;; - arm64) - reh='vscode-reh-alpine-arm64-min-ci' - rehweb='vscode-reh-web-alpine-arm64-min-ci' - ;; - esac - - yarn gulp $reh - yarn gulp $rehweb - displayName: Build - - - script: | - set -e - REPO="$(pwd)" - ROOT="$REPO/.." - - case $VSCODE_ARCH in - x64) - PLATFORM_LINUX='linux-alpine' - ;; - arm64) - PLATFORM_LINUX='alpine-arm64' - ;; - esac - - # 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) - - # 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-*-web.tar.* - (cd $ROOT && mv $LEGACY_SERVER_BUILD_NAME $SERVER_BUILD_NAME && tar --owner=0 --group=0 -czf $SERVER_TARBALL_PATH $SERVER_BUILD_NAME) - displayName: Prepare for publish - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - - - publish: $(Agent.BuildDirectory)/vscode-server-alpine-$(VSCODE_ARCH).tar.gz - artifact: vscode_server_alpine_$(VSCODE_ARCH)_archive-unsigned - displayName: Publish server archive - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'), ne(variables['VSCODE_ARCH'], 'x64')) - - - publish: $(Agent.BuildDirectory)/vscode-server-alpine-$(VSCODE_ARCH)-web.tar.gz - artifact: vscode_web_alpine_$(VSCODE_ARCH)_archive-unsigned - displayName: Publish web server archive - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'), ne(variables['VSCODE_ARCH'], 'x64')) - - # Legacy x64 artifact name - - publish: $(Agent.BuildDirectory)/vscode-server-linux-alpine.tar.gz - artifact: vscode_server_linux_alpine_archive-unsigned - displayName: Publish x64 server archive - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'), eq(variables['VSCODE_ARCH'], 'x64')) - - - publish: $(Agent.BuildDirectory)/vscode-server-linux-alpine-web.tar.gz - artifact: vscode_web_linux_alpine_archive-unsigned - displayName: Publish x64 web server archive - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'), eq(variables['VSCODE_ARCH'], 'x64')) diff --git a/build/azure-pipelines/linux/product-build-linux-server.yml b/build/azure-pipelines/linux/product-build-linux-server.yml deleted file mode 100644 index 8ab58da435..0000000000 --- a/build/azure-pipelines/linux/product-build-linux-server.yml +++ /dev/null @@ -1,96 +0,0 @@ -parameters: - - name: VSCODE_QUALITY - type: string - -steps: - - task: NodeTool@0 - inputs: - versionSpec: "16.x" - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - task: AzureKeyVault@1 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode - SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - task: Docker@1 - displayName: "Pull Docker image" - inputs: - azureSubscriptionEndpoint: "vscode-builds-subscription" - azureContainerRegistry: vscodehub.azurecr.io - command: "Run an image" - imageName: "vscode-linux-build-agent:centos7-devtoolset8-arm64" - containerCommand: uname - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64')) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - 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 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages - - - script: | - set -e - $(pwd)/build/azure-pipelines/linux/scripts/install-remote-dependencies.sh - displayName: Install dependencies - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - displayName: Register Docker QEMU - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64')) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - docker run -e VSCODE_QUALITY -e GITHUB_TOKEN -v $(pwd):/root/vscode -v ~/.netrc:/root/.netrc vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-arm64 /root/vscode/build/azure-pipelines/linux/scripts/install-remote-dependencies.sh - displayName: Install dependencies via qemu - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64')) - - - script: | - set -e - tar -cz --ignore-failed-read -f $(Build.ArtifactStagingDirectory)/reh_node_modules-$(VSCODE_ARCH).tar.gz -C $(Build.SourcesDirectory)/remote node_modules - displayName: Compress node_modules output - - - task: PublishPipelineArtifact@0 - displayName: "Publish remote node_modules" - inputs: - artifactName: "reh_node_modules-$(VSCODE_ARCH)" - targetPath: $(Build.ArtifactStagingDirectory)/reh_node_modules-$(VSCODE_ARCH).tar.gz diff --git a/build/azure-pipelines/linux/product-build-linux-client-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml similarity index 78% rename from build/azure-pipelines/linux/product-build-linux-client-test.yml rename to build/azure-pipelines/linux/product-build-linux-test.yml index 31d477e93a..a5a30a340f 100644 --- a/build/azure-pipelines/linux/product-build-linux-client-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -9,10 +9,9 @@ parameters: type: boolean steps: - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + - script: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -41,40 +40,34 @@ steps: - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests" + - script: ./scripts/test.sh --tfs "Unit Tests" + env: + DISPLAY: ":10" displayName: Run unit tests (Electron) timeoutInMinutes: 15 - - script: | - set -e - yarn test-node + - script: yarn test-node displayName: Run unit tests (node.js) timeoutInMinutes: 15 - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests" + - script: yarn test-browser-no-install --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" displayName: Run unit tests (Browser, Chromium) timeoutInMinutes: 15 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - ./scripts/test.sh --build --tfs "Unit Tests" + - script: ./scripts/test.sh --build --tfs "Unit Tests" displayName: Run unit tests (Electron) timeoutInMinutes: 15 - - script: | - set -e - yarn test-node --build + - script: yarn test-node --build displayName: Run unit tests (node.js) timeoutInMinutes: 15 - - script: | - set -e - DEBUG=*browser* yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" + - script: yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" displayName: Run unit tests (Browser, Chromium) timeoutInMinutes: 15 @@ -97,27 +90,22 @@ steps: compile-extension:typescript-language-features \ compile-extension:vscode-api-tests \ compile-extension:vscode-colorize-tests \ - compile-extension:vscode-notebook-tests \ compile-extension:vscode-test-resolver displayName: Build integration tests - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests" + - script: ./scripts/test-integration.sh --tfs "Integration Tests" + env: + DISPLAY: ":10" displayName: Run integration tests (Electron) timeoutInMinutes: 20 - - script: | - set -e - ./scripts/test-web-integration.sh --browser chromium + - script: ./scripts/test-web-integration.sh --browser chromium displayName: Run integration tests (Browser, Chromium) timeoutInMinutes: 20 - - script: | - set -e - ./scripts/test-remote-integration.sh + - script: ./scripts/test-remote-integration.sh displayName: Run integration tests (Remote) timeoutInMinutes: 20 @@ -131,15 +119,15 @@ steps: 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)" \ ./scripts/test-integration.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH) displayName: Run integration tests (Electron) timeoutInMinutes: 20 - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - ./scripts/test-web-integration.sh --browser chromium + - script: ./scripts/test-web-integration.sh --browser chromium + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH) displayName: Run integration tests (Browser, Chromium) timeoutInMinutes: 20 @@ -149,8 +137,9 @@ steps: 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)" \ ./scripts/test-remote-integration.sh + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH) displayName: Run integration tests (Remote) timeoutInMinutes: 20 @@ -165,42 +154,32 @@ steps: condition: succeededOrFailed() - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - yarn --cwd test/smoke compile + - script: yarn --cwd test/smoke compile displayName: Compile smoke tests - - script: | - set -e - yarn smoketest-no-compile --tracing + - script: yarn gulp compile-extension:markdown-language-features compile-extension-media compile-extension:vscode-test-resolver + displayName: Build extensions for smoke tests + + - script: yarn smoketest-no-compile --tracing timeoutInMinutes: 20 displayName: Run smoke tests (Electron) - - script: | - set -e - yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" + - script: yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" timeoutInMinutes: 20 displayName: Run smoke tests (Browser, Chromium) - - script: | - set -e - yarn gulp compile-extension:vscode-test-resolver - yarn smoketest-no-compile --remote --tracing + - script: yarn smoketest-no-compile --remote --tracing timeoutInMinutes: 20 displayName: Run smoke tests (Remote) - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - yarn smoketest-no-compile --tracing --build "$APP_PATH" + - script: yarn smoketest-no-compile --tracing --build "$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)" timeoutInMinutes: 20 displayName: Run smoke tests (Electron) - - script: | - set -e - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" + - script: yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH) timeoutInMinutes: 20 displayName: Run smoke tests (Browser, Chromium) diff --git a/build/azure-pipelines/linux/product-build-linux-client.yml b/build/azure-pipelines/linux/product-build-linux.yml similarity index 51% rename from build/azure-pipelines/linux/product-build-linux-client.yml rename to build/azure-pipelines/linux/product-build-linux.yml index 97a9cf31d6..a3c47d8e15 100644 --- a/build/azure-pipelines/linux/product-build-linux-client.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -1,14 +1,16 @@ parameters: - - name: VSCODE_PUBLISH - type: boolean - name: VSCODE_QUALITY type: string + - name: VSCODE_CIBUILD + type: boolean - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS type: boolean - name: VSCODE_RUN_SMOKE_TESTS type: boolean + - name: VSCODE_ARCH + type: string steps: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: @@ -21,29 +23,24 @@ steps: versionSpec: "16.x" - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - template: ../distro/download-distro.yml + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: DownloadPipelineArtifact@2 inputs: artifact: Compilation path: $(Build.ArtifactStagingDirectory) displayName: Download compilation output - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - task: DownloadPipelineArtifact@2 - inputs: - artifact: reh_node_modules-$(VSCODE_ARCH) - path: $(Build.ArtifactStagingDirectory) - displayName: Download server build dependencies - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | set -e # Start X server @@ -54,79 +51,75 @@ steps: displayName: Setup system services condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js linux $VSCODE_ARCH > .build/yarnlockhash + displayName: Prepare node_modules cache key - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling + - task: Cache@2 + inputs: + key: '"node_modules" | .build/yarnlockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - task: Cache@2 - inputs: - key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - task: Cache@2 - inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz + - script: tar -xzf .build/node_modules_cache/cache.tgz condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) displayName: Extract node_modules cache - script: | set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages + npm config set registry "$NPM_REGISTRY" --location=project + npm config set always-auth=true --location=project + yarn config set registry "$NPM_REGISTRY" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + # TODO@joaomoreno TODO@deepak1556 this should be part of the base image + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + if [ "$VSCODE_ARCH" = "x64" ]; then + OS=ubuntu + else + OS=debian + fi + + sudo apt-get update && sudo apt-get install -y ca-certificates curl gnupg + sudo mkdir -m 0755 -p /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/$OS/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt update && sudo apt install -y docker-ce-cli + displayName: Install Docker client + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - ${{ if and(ne(parameters.VSCODE_QUALITY, 'oss'), or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64'))) }}: + - task: Docker@1 + displayName: "Pull Docker image" + inputs: + azureSubscriptionEndpoint: "vscode-builds-subscription" + azureContainerRegistry: vscodehub.azurecr.io + command: "Run an image" + imageName: vscode-linux-build-agent:centos7-devtoolset8-${{ parameters.VSCODE_ARCH }} + containerCommand: uname + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - ${{ if and(ne(parameters.VSCODE_QUALITY, 'oss'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: + - script: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + displayName: Register Docker QEMU + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_ARCH'], 'arm64')) - script: | set -e - node build/npm/setupBuildYarnrc - for i in {1..3}; do # try 3 times, for Terrapin + + for i in {1..5}; do # try 5 times yarn --cwd build --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 @@ -134,24 +127,10 @@ steps: fi echo "Yarn failed $i, trying again..." done - displayName: Install build dependencies - - - script: | - set -e - if [ "$NPM_ARCH" = "armv7l" ]; then - # There is no target_arch="armv7l" supported by node_gyp, - # arm versions for compilation are decided based on the CC - # macros. - # Mapping value is based on - # https://github.com/nodejs/node/blob/0903515e126c2697042d6546c6aa4b72e1a4b33e/configure.py#L49-L50 - export npm_config_arch="arm" - else - export npm_config_arch=$(NPM_ARCH) - fi if [ -z "$CC" ] || [ -z "$CXX" ]; then # Download clang based on chromium revision used by vscode - curl -s https://raw.githubusercontent.com/chromium/chromium/98.0.4758.109/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux + curl -s https://raw.githubusercontent.com/chromium/chromium/108.0.5359.215/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux # Download libcxx headers and objects from upstream electron releases DEBUG=libcxx-fetcher \ VSCODE_LIBCXX_OBJECTS_DIR=$PWD/.build/libcxx-objects \ @@ -161,18 +140,18 @@ steps: node build/linux/libcxx-fetcher.js # Set compiler toolchain # Flags for the client build are based on - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:build/config/arm.gni - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:build/config/compiler/BUILD.gn - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:build/config/c++/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/arm.gni + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/compiler/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/c++/BUILD.gn export CC=$PWD/.build/CR_Clang/bin/clang export CXX=$PWD/.build/CR_Clang/bin/clang++ - export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -isystem$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit" + export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr" export LDFLAGS="-stdlib=libc++ -fuse-ld=lld -flto=thin -L$PWD/.build/libcxx-objects -lc++abi -Wl,--lto-O0" export VSCODE_REMOTE_CC=$(which gcc) export VSCODE_REMOTE_CXX=$(which g++) fi - for i in {1..3}; do # try 3 times, for Terrapin + for i in {1..5}; do # try 5 times yarn --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 @@ -181,19 +160,19 @@ steps: echo "Yarn failed $i, trying again..." done env: + npm_config_arch: $(NPM_ARCH) ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" + ${{ if and(ne(parameters.VSCODE_QUALITY, 'oss'), or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64'))) }}: + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-${{ parameters.VSCODE_ARCH }} displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - rm -rf remote/node_modules - tar -xzf $(Build.ArtifactStagingDirectory)/reh_node_modules-$(VSCODE_ARCH).tar.gz --directory $(Build.SourcesDirectory)/remote - displayName: Extract server node_modules output - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'armhf')) + - script: node build/azure-pipelines/distro/mixin-npm + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules - script: | set -e @@ -204,133 +183,120 @@ steps: displayName: Create node_modules archive - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + + - template: ../common/install-builtin-extensions.yml - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci - displayName: Build + - script: yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build client - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - node build/azure-pipelines/mixin --server - displayName: Mix in server quality + - script: yarn gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - 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 Server + - script: yarn gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server (web) - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp "transpile-client" "transpile-extensions" + - ${{ else }}: + - script: yarn gulp "transpile-client-swc" "transpile-extensions" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Transpile - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: - - template: product-build-linux-client-test.yml + - template: product-build-linux-test.yml parameters: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} VSCODE_RUN_UNIT_TESTS: ${{ parameters.VSCODE_RUN_UNIT_TESTS }} VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - script: | - set -e - yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" - yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" - displayName: Build deb, rpm packages + - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: vscode_cli_linux_$(VSCODE_ARCH)_cli + patterns: "**" + path: $(Build.ArtifactStagingDirectory)/cli + displayName: Download VS Code CLI - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - script: | set -e - yarn gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" + tar -xzvf $(Build.ArtifactStagingDirectory)/cli/*.tar.gz -C $(Build.ArtifactStagingDirectory)/cli + CLI_APP_NAME=$(node -p "require(\"$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/resources/app/product.json\").tunnelApplicationName") + APP_NAME=$(node -p "require(\"$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/resources/app/product.json\").applicationName") + mv $(Build.ArtifactStagingDirectory)/cli/$APP_NAME $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/bin/$CLI_APP_NAME + displayName: Make CLI executable + + - script: yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" + displayName: Build deb package + + - script: yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" + displayName: Build rpm package + + - script: yarn gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" displayName: Prepare snap package - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - task: UseDotNet@2 inputs: - version: 2.x + version: 6.x - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - task: EsrpClientTool@1 + continueOnError: true displayName: Download ESRPClient - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - script: | - set -e - node build/azure-pipelines/common/sign "$(esrpclient.toolpath)/$(esrpclient.toolname)" rpm $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) .build/linux/rpm '*.rpm' + - script: node build/azure-pipelines/common/sign $(Agent.ToolsDirectory)/esrpclient/*/*/net6.0/esrpcli.dll rpm $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) .build/linux/rpm '*.rpm' displayName: Codesign rpm - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - script: | - set -e - VSCODE_ARCH="$(VSCODE_ARCH)" \ - ./build/azure-pipelines/linux/prepare-publish.sh + - script: ./build/azure-pipelines/linux/prepare-publish.sh displayName: Prepare for Publish - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (client) inputs: BuildDropPath: $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) PackageName: Visual Studio Code - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - publish: $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (client) artifact: vscode_client_linux_$(VSCODE_ARCH)_sbom - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 displayName: Generate SBOM (server) inputs: BuildDropPath: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) PackageName: Visual Studio Code Server - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - publish: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH)/_manifest displayName: Publish SBOM (server) artifact: vscode_server_linux_$(VSCODE_ARCH)_sbom - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - publish: $(DEB_PATH) artifact: vscode_client_linux_$(VSCODE_ARCH)_deb-package displayName: Publish deb package - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - publish: $(RPM_PATH) artifact: vscode_client_linux_$(VSCODE_ARCH)_rpm-package displayName: Publish rpm package - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - publish: $(TARBALL_PATH) artifact: vscode_client_linux_$(VSCODE_ARCH)_archive-unsigned displayName: Publish client archive - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - publish: $(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH).tar.gz artifact: vscode_server_linux_$(VSCODE_ARCH)_archive-unsigned displayName: Publish server archive - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - publish: $(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz artifact: vscode_web_linux_$(VSCODE_ARCH)_archive-unsigned displayName: Publish web server archive - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - task: PublishPipelineArtifact@0 displayName: "Publish Pipeline Artifact" inputs: diff --git a/build/azure-pipelines/linux/scripts/install-remote-dependencies.sh b/build/azure-pipelines/linux/scripts/install-remote-dependencies.sh deleted file mode 100755 index d2f6208766..0000000000 --- a/build/azure-pipelines/linux/scripts/install-remote-dependencies.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "Installing remote dependencies" -(cd remote && rm -rf node_modules) - -for i in {1..3}; do # try 3 times, for Terrapin - yarn --cwd remote --frozen-lockfile --check-files && break - if [ $i -eq 3 ]; then - echo "Yarn failed too many times" >&2 - exit 1 - fi - echo "Yarn failed $i, trying again..." -done diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index 1282933495..9002fcff5d 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -45,7 +45,7 @@ steps: x64) SNAPCRAFT_TARGET_ARGS="" ;; *) SNAPCRAFT_TARGET_ARGS="--target-arch $(VSCODE_ARCH)" ;; esac - (cd $SNAP_ROOT/code-* && sudo --preserve-env snapcraft prime $SNAPCRAFT_TARGET_ARGS && snap pack prime --compression=lzo --filename="$SNAP_PATH") + (cd $SNAP_ROOT/code-* && sudo --preserve-env snapcraft snap $SNAPCRAFT_TARGET_ARGS --output "$SNAP_PATH") # Export SNAP_PATH echo "##vso[task.setvariable variable=SNAP_PATH]$SNAP_PATH" @@ -54,4 +54,3 @@ steps: - publish: $(SNAP_PATH) artifact: vscode_client_linux_$(VSCODE_ARCH)_snap displayName: Publish snap package - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) diff --git a/build/azure-pipelines/linux/sql-product-build-linux.yml b/build/azure-pipelines/linux/sql-product-build-linux.yml index 5f29358cc5..d97c658fa7 100644 --- a/build/azure-pipelines/linux/sql-product-build-linux.yml +++ b/build/azure-pipelines/linux/sql-product-build-linux.yml @@ -136,48 +136,50 @@ steps: displayName: Run core integration tests condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true'), ne(variables['EXTENSIONS_ONLY'], 'true')) - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the unit tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/azuredatastudio-linux-x64 - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - NO_CLEANUP=1 \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/azuredatastudio-reh-linux-x64" \ - DISPLAY=:10 ./scripts/test-extensions-unit.sh --build --tfs "Extension Unit Tests" - displayName: Run Extension Unit Tests (Continue on Error) - continueOnError: true - condition: and(succeeded(), and(eq(variables['RUN_TESTS'], 'true'), eq(variables['EXTENSION_UNIT_TESTS_FAIL_ON_ERROR'], 'false'))) + # {{SQL CARBON TODO}} - disable extension unit tests while investigating post merge (6/26/2023) + # - script: | + # # Figure out the full absolute path of the product we just built + # # including the remote server and configure the unit tests + # # to run with these builds instead of running out of sources. + # set -e + # APP_ROOT=$(agent.builddirectory)/azuredatastudio-linux-x64 + # APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + # INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + # NO_CLEANUP=1 \ + # VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/azuredatastudio-reh-linux-x64" \ + # DISPLAY=:10 ./scripts/test-extensions-unit.sh --build --tfs "Extension Unit Tests" + # displayName: Run Extension Unit Tests (Continue on Error) + # continueOnError: true + # condition: and(succeeded(), and(eq(variables['RUN_TESTS'], 'true'), eq(variables['EXTENSION_UNIT_TESTS_FAIL_ON_ERROR'], 'false'))) - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the unit tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/azuredatastudio-linux-x64 - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - NO_CLEANUP=1 \ - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/azuredatastudio-reh-linux-x64" \ - DISPLAY=:10 ./scripts/test-extensions-unit.sh --build --tfs "Extension Unit Tests" - displayName: Run Extension Unit Tests (Fail on Error) - condition: and(succeeded(), and(eq(variables['RUN_TESTS'], 'true'), ne(variables['EXTENSION_UNIT_TESTS_FAIL_ON_ERROR'], 'false'))) + # - script: | + # # Figure out the full absolute path of the product we just built + # # including the remote server and configure the unit tests + # # to run with these builds instead of running out of sources. + # set -e + # APP_ROOT=$(agent.builddirectory)/azuredatastudio-linux-x64 + # APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + # INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + # NO_CLEANUP=1 \ + # VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/azuredatastudio-reh-linux-x64" \ + # DISPLAY=:10 ./scripts/test-extensions-unit.sh --build --tfs "Extension Unit Tests" + # displayName: Run Extension Unit Tests (Fail on Error) + # condition: and(succeeded(), and(eq(variables['RUN_TESTS'], 'true'), ne(variables['EXTENSION_UNIT_TESTS_FAIL_ON_ERROR'], 'false'))) - - bash: | - set -e - mkdir -p $(Build.ArtifactStagingDirectory)/logs/linux-x64 - cd /tmp - for folder in adsuser*/ - do - folder=${folder%/} - # Only archive directories we want for debugging purposes - tar -czvf $(Build.ArtifactStagingDirectory)/logs/linux-x64/$folder.tar.gz $folder/User $folder/logs - done - displayName: Archive Logs - continueOnError: true - condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true')) + # - bash: | + # set -e + # mkdir -p $(Build.ArtifactStagingDirectory)/logs/linux-x64 + # cd /tmp + # for folder in adsuser*/ + # do + # folder=${folder%/} + # # Only archive directories we want for debugging purposes + # tar -czvf $(Build.ArtifactStagingDirectory)/logs/linux-x64/$folder.tar.gz $folder/User $folder/logs + # done + # displayName: Archive Logs + # continueOnError: true + # condition: and(succeeded(), eq(variables['RUN_TESTS'], 'true')) + # {{SQL CARBON TODO}} - end disable extension unit tests while investigating post merge (6/26/2023) - script: | set -e diff --git a/build/azure-pipelines/mixin.js b/build/azure-pipelines/mixin.js deleted file mode 100644 index 49dafea34e..0000000000 --- a/build/azure-pipelines/mixin.js +++ /dev/null @@ -1,86 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const json = require("gulp-json-editor"); -const buffer = require('gulp-buffer'); -const filter = require("gulp-filter"); -const es = require("event-stream"); -const vfs = require("vinyl-fs"); -const fancyLog = require("fancy-log"); -const ansiColors = require("ansi-colors"); -const fs = require("fs"); -const path = require("path"); -async function mixinClient(quality) { - const productJsonFilter = filter(f => f.relative === 'product.json', { restore: true }); - fancyLog(ansiColors.blue('[mixin]'), `Mixing in client:`); - return new Promise((c, e) => { - vfs - .src(`quality/${quality}/**`, { base: `quality/${quality}` }) - .pipe(filter(f => !f.isDirectory())) - .pipe(filter(f => f.relative !== 'product.server.json')) - .pipe(productJsonFilter) - .pipe(buffer()) - .pipe(json((o) => { - const originalProduct = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'product.json'), 'utf8')); - let builtInExtensions = originalProduct.builtInExtensions; - if (Array.isArray(o.builtInExtensions)) { - fancyLog(ansiColors.blue('[mixin]'), 'Overwriting built-in extensions:', o.builtInExtensions.map(e => e.name)); - builtInExtensions = o.builtInExtensions; - } - else if (o.builtInExtensions) { - const include = o.builtInExtensions['include'] || []; - const exclude = o.builtInExtensions['exclude'] || []; - fancyLog(ansiColors.blue('[mixin]'), 'OSS built-in extensions:', builtInExtensions.map(e => e.name)); - fancyLog(ansiColors.blue('[mixin]'), 'Including built-in extensions:', include.map(e => e.name)); - fancyLog(ansiColors.blue('[mixin]'), 'Excluding built-in extensions:', exclude); - builtInExtensions = builtInExtensions.filter(ext => !include.find(e => e.name === ext.name) && !exclude.find(name => name === ext.name)); - builtInExtensions = [...builtInExtensions, ...include]; - fancyLog(ansiColors.blue('[mixin]'), 'Final built-in extensions:', builtInExtensions.map(e => e.name)); - } - else { - fancyLog(ansiColors.blue('[mixin]'), 'Inheriting OSS built-in extensions', builtInExtensions.map(e => e.name)); - } - return { webBuiltInExtensions: originalProduct.webBuiltInExtensions, ...o, builtInExtensions }; - })) - .pipe(productJsonFilter.restore) - .pipe(es.mapSync((f) => { - fancyLog(ansiColors.blue('[mixin]'), f.relative, ansiColors.green('✔︎')); - return f; - })) - .pipe(vfs.dest('.')) - .on('end', () => c()) - .on('error', (err) => e(err)); - }); -} -function mixinServer(quality) { - const serverProductJsonPath = `quality/${quality}/product.server.json`; - if (!fs.existsSync(serverProductJsonPath)) { - fancyLog(ansiColors.blue('[mixin]'), `Server product not found`, serverProductJsonPath); - return; - } - fancyLog(ansiColors.blue('[mixin]'), `Mixing in server:`); - const originalProduct = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'product.json'), 'utf8')); - const serverProductJson = JSON.parse(fs.readFileSync(serverProductJsonPath, 'utf8')); - fs.writeFileSync('product.json', JSON.stringify({ ...originalProduct, ...serverProductJson }, undefined, '\t')); - fancyLog(ansiColors.blue('[mixin]'), 'product.json', ansiColors.green('✔︎')); -} -function main() { - const quality = process.env['VSCODE_QUALITY']; - if (!quality) { - console.log('Missing VSCODE_QUALITY, skipping mixin'); - return; - } - if (process.argv[2] === '--server') { - mixinServer(quality); - } - else { - mixinClient(quality).catch(err => { - console.error(err); - process.exit(1); - }); - } -} -main(); diff --git a/build/azure-pipelines/mixin.ts b/build/azure-pipelines/mixin.ts deleted file mode 100644 index 8658d98125..0000000000 --- a/build/azure-pipelines/mixin.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as json from 'gulp-json-editor'; -const buffer = require('gulp-buffer'); -import * as filter from 'gulp-filter'; -import * as es from 'event-stream'; -import * as Vinyl from 'vinyl'; -import * as vfs from 'vinyl-fs'; -import * as fancyLog from 'fancy-log'; -import * as ansiColors from 'ansi-colors'; -import * as fs from 'fs'; -import * as path from 'path'; - -interface IBuiltInExtension { - readonly name: string; - readonly version: string; - readonly repo: string; - readonly metadata: any; -} - -interface OSSProduct { - readonly builtInExtensions: IBuiltInExtension[]; - readonly webBuiltInExtensions?: IBuiltInExtension[]; -} - -interface Product { - readonly builtInExtensions?: IBuiltInExtension[] | { 'include'?: IBuiltInExtension[]; 'exclude'?: string[] }; - readonly webBuiltInExtensions?: IBuiltInExtension[]; -} - -async function mixinClient(quality: string): Promise { - const productJsonFilter = filter(f => f.relative === 'product.json', { restore: true }); - - fancyLog(ansiColors.blue('[mixin]'), `Mixing in client:`); - - return new Promise((c, e) => { - vfs - .src(`quality/${quality}/**`, { base: `quality/${quality}` }) - .pipe(filter(f => !f.isDirectory())) - .pipe(filter(f => f.relative !== 'product.server.json')) - .pipe(productJsonFilter) - .pipe(buffer()) - .pipe(json((o: Product) => { - const originalProduct = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'product.json'), 'utf8')) as OSSProduct; - let builtInExtensions = originalProduct.builtInExtensions; - - if (Array.isArray(o.builtInExtensions)) { - fancyLog(ansiColors.blue('[mixin]'), 'Overwriting built-in extensions:', o.builtInExtensions.map(e => e.name)); - - builtInExtensions = o.builtInExtensions; - } else if (o.builtInExtensions) { - const include = o.builtInExtensions['include'] || []; - const exclude = o.builtInExtensions['exclude'] || []; - - fancyLog(ansiColors.blue('[mixin]'), 'OSS built-in extensions:', builtInExtensions.map(e => e.name)); - fancyLog(ansiColors.blue('[mixin]'), 'Including built-in extensions:', include.map(e => e.name)); - fancyLog(ansiColors.blue('[mixin]'), 'Excluding built-in extensions:', exclude); - - builtInExtensions = builtInExtensions.filter(ext => !include.find(e => e.name === ext.name) && !exclude.find(name => name === ext.name)); - builtInExtensions = [...builtInExtensions, ...include]; - - fancyLog(ansiColors.blue('[mixin]'), 'Final built-in extensions:', builtInExtensions.map(e => e.name)); - } else { - fancyLog(ansiColors.blue('[mixin]'), 'Inheriting OSS built-in extensions', builtInExtensions.map(e => e.name)); - } - - return { webBuiltInExtensions: originalProduct.webBuiltInExtensions, ...o, builtInExtensions }; - })) - .pipe(productJsonFilter.restore) - .pipe(es.mapSync((f: Vinyl) => { - fancyLog(ansiColors.blue('[mixin]'), f.relative, ansiColors.green('✔︎')); - return f; - })) - .pipe(vfs.dest('.')) - .on('end', () => c()) - .on('error', (err: any) => e(err)); - }); -} - -function mixinServer(quality: string) { - const serverProductJsonPath = `quality/${quality}/product.server.json`; - - if (!fs.existsSync(serverProductJsonPath)) { - fancyLog(ansiColors.blue('[mixin]'), `Server product not found`, serverProductJsonPath); - return; - } - - fancyLog(ansiColors.blue('[mixin]'), `Mixing in server:`); - - const originalProduct = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'product.json'), 'utf8')) as OSSProduct; - const serverProductJson = JSON.parse(fs.readFileSync(serverProductJsonPath, 'utf8')); - fs.writeFileSync('product.json', JSON.stringify({ ...originalProduct, ...serverProductJson }, undefined, '\t')); - fancyLog(ansiColors.blue('[mixin]'), 'product.json', ansiColors.green('✔︎')); -} - -function main() { - const quality = process.env['VSCODE_QUALITY']; - - if (!quality) { - console.log('Missing VSCODE_QUALITY, skipping mixin'); - return; - } - - if (process.argv[2] === '--server') { - mixinServer(quality); - } else { - mixinClient(quality).catch(err => { - console.error(err); - process.exit(1); - }); - } -} - -main(); diff --git a/build/azure-pipelines/product-build-pr-cache.yml b/build/azure-pipelines/oss/product-build-pr-cache-linux.yml similarity index 58% rename from build/azure-pipelines/product-build-pr-cache.yml rename to build/azure-pipelines/oss/product-build-pr-cache-linux.yml index 067afa7492..97eba56abc 100644 --- a/build/azure-pipelines/product-build-pr-cache.yml +++ b/build/azure-pipelines/oss/product-build-pr-cache-linux.yml @@ -7,35 +7,41 @@ steps: inputs: versionSpec: "16.x" - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js linux $VSCODE_ARCH > .build/yarnlockhash + displayName: Prepare node_modules cache key - task: Cache@2 inputs: - key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" + key: '"node_modules" | .build/yarnlockhash' path: .build/node_modules_cache cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz + - script: tar -xzf .build/node_modules_cache/cache.tgz condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) displayName: Extract node_modules cache - script: | set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages + npm config set registry "$NPM_REGISTRY" --location=project + npm config set always-auth=true --location=project + yarn config set registry "$NPM_REGISTRY" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication - script: | set -e - for i in {1..3}; do # try 3 times, for Terrapin + for i in {1..5}; do # try 5 times yarn --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 diff --git a/build/azure-pipelines/oss/product-build-pr-cache-win32.yml b/build/azure-pipelines/oss/product-build-pr-cache-win32.yml new file mode 100644 index 0000000000..61b0bf37d2 --- /dev/null +++ b/build/azure-pipelines/oss/product-build-pr-cache-win32.yml @@ -0,0 +1,66 @@ +steps: + - checkout: self + fetchDepth: 1 + retryCountOnTaskFailure: 3 + + - task: NodeTool@0 + inputs: + versionSpec: "16.x" + + - powershell: node build/setup-npm-registry.js $env:NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - pwsh: | + mkdir .build -ea 0 + node build/azure-pipelines/common/computeNodeModulesCacheKey.js win32 $(VSCODE_ARCH) > .build/yarnlockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/yarnlockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - powershell: 7z.exe x .build/node_modules_cache/cache.7z -aoa + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm config set registry "$env:NPM_REGISTRY" --location=project } + exec { npm config set always-auth=true --location=project } + exec { yarn config set registry "$env:NPM_REGISTRY" } + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + . build/azure-pipelines/win32/retry.ps1 + $ErrorActionPreference = "Stop" + $env:npm_config_arch="$(VSCODE_ARCH)" + $env:CHILD_CONCURRENCY="1" + retry { exec { yarn --frozen-lockfile --check-files } } + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt } + exec { mkdir -Force .build/node_modules_cache } + exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml index 8362da25ee..789996060e 100644 --- a/build/azure-pipelines/product-build-pr.yml +++ b/build/azure-pipelines/product-build-pr.yml @@ -11,12 +11,10 @@ variables: value: true - name: skipComponentGovernanceDetection value: true - - name: ENABLE_TERRAPIN - value: false + - name: NPM_REGISTRY + value: "none" - name: VSCODE_CIBUILD value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - - name: VSCODE_PUBLISH - value: false - name: VSCODE_QUALITY value: oss - name: VSCODE_STEP_ON_IT @@ -26,7 +24,7 @@ jobs: - ${{ if ne(variables['VSCODE_CIBUILD'], true) }}: - job: Compile displayName: Compile & Hygiene - pool: vscode-1es-vscode-linux-20.04 + pool: 1es-oss-ubuntu-20.04-x64 timeoutInMinutes: 30 variables: VSCODE_ARCH: x64 @@ -37,69 +35,135 @@ jobs: - job: Linuxx64UnitTest displayName: Linux (Unit Tests) - pool: vscode-1es-vscode-linux-20.04 + pool: 1es-oss-ubuntu-20.04-x64 timeoutInMinutes: 30 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 DISPLAY: ":10" steps: - - template: linux/product-build-linux-client.yml + - template: linux/product-build-linux.yml parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: true VSCODE_RUN_INTEGRATION_TESTS: false VSCODE_RUN_SMOKE_TESTS: false - job: Linuxx64IntegrationTest displayName: Linux (Integration Tests) - pool: vscode-1es-vscode-linux-20.04 + pool: 1es-oss-ubuntu-20.04-x64 timeoutInMinutes: 30 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 DISPLAY: ":10" steps: - - template: linux/product-build-linux-client.yml + - template: linux/product-build-linux.yml parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: true VSCODE_RUN_SMOKE_TESTS: false - job: Linuxx64SmokeTest displayName: Linux (Smoke Tests) - pool: vscode-1es-vscode-linux-20.04 + pool: 1es-oss-ubuntu-20.04-x64 timeoutInMinutes: 30 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 DISPLAY: ":10" steps: - - template: linux/product-build-linux-client.yml + - template: linux/product-build-linux.yml parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} + VSCODE_ARCH: x64 VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} VSCODE_RUN_UNIT_TESTS: false VSCODE_RUN_INTEGRATION_TESTS: false VSCODE_RUN_SMOKE_TESTS: true + - job: LinuxCLI + displayName: Linux (CLI) + pool: 1es-oss-ubuntu-20.04-x64 + timeoutInMinutes: 30 + steps: + - template: cli/test.yml + + - job: Windowsx64UnitTests + displayName: Windows (Unit Tests) + pool: 1es-oss-windows-2019-x64 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + + - job: Windowsx64IntegrationTests + displayName: Windows (Integration Tests) + pool: 1es-oss-windows-2019-x64 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + + # - job: Windowsx64SmokeTests + # displayName: Windows (Smoke Tests) + # pool: 1es-oss-windows-2019-x64 + # timeoutInMinutes: 30 + # variables: + # VSCODE_ARCH: x64 + # NPM_ARCH: x64 + # steps: + # - template: win32/product-build-win32.yml + # parameters: + # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + # VSCODE_RUN_UNIT_TESTS: false + # VSCODE_RUN_INTEGRATION_TESTS: false + # VSCODE_RUN_SMOKE_TESTS: true + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - job: Linuxx64MaintainNodeModulesCache displayName: Linux (Maintain node_modules cache) - pool: vscode-1es-vscode-linux-20.04 + pool: 1es-oss-ubuntu-20.04-x64 timeoutInMinutes: 30 variables: VSCODE_ARCH: x64 steps: - - template: product-build-pr-cache.yml + - template: oss/product-build-pr-cache-linux.yml + + - job: Windowsx64MaintainNodeModulesCache + displayName: Windows (Maintain node_modules cache) + pool: 1es-oss-windows-2019-x64 + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: x64 + steps: + - template: oss/product-build-pr-cache-win32.yml # - job: macOSUnitTest # displayName: macOS (Unit Tests) # pool: - # vmImage: macOS-latest + # vmImage: macOS-11 # timeoutInMinutes: 60 # variables: # BUILDSECMON_OPT_IN: true @@ -107,7 +171,6 @@ jobs: # steps: # - template: darwin/product-build-darwin.yml # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} # VSCODE_RUN_UNIT_TESTS: true # VSCODE_RUN_INTEGRATION_TESTS: false @@ -115,7 +178,7 @@ jobs: # - job: macOSIntegrationTest # displayName: macOS (Integration Tests) # pool: - # vmImage: macOS-latest + # vmImage: macOS-11 # timeoutInMinutes: 60 # variables: # BUILDSECMON_OPT_IN: true @@ -123,7 +186,6 @@ jobs: # steps: # - template: darwin/product-build-darwin.yml # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} # VSCODE_RUN_UNIT_TESTS: false # VSCODE_RUN_INTEGRATION_TESTS: true @@ -131,7 +193,7 @@ jobs: # - job: macOSSmokeTest # displayName: macOS (Smoke Tests) # pool: - # vmImage: macOS-latest + # vmImage: macOS-11 # timeoutInMinutes: 60 # variables: # BUILDSECMON_OPT_IN: true @@ -139,50 +201,6 @@ jobs: # steps: # - template: darwin/product-build-darwin.yml # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: true - - # - job: WindowsUnitTests - # displayName: Windows (Unit Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: true - # VSCODE_RUN_INTEGRATION_TESTS: false - # VSCODE_RUN_SMOKE_TESTS: false - # - job: WindowsIntegrationTests - # displayName: Windows (Integration Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - # VSCODE_RUN_UNIT_TESTS: false - # VSCODE_RUN_INTEGRATION_TESTS: true - # VSCODE_RUN_SMOKE_TESTS: false - # - job: WindowsSmokeTests - # displayName: Windows (Smoke Tests) - # pool: vscode-1es-vscode-windows-2019 - # timeoutInMinutes: 60 - # variables: - # VSCODE_ARCH: x64 - # steps: - # - template: win32/product-build-win32.yml - # parameters: - # VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} # VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} # VSCODE_RUN_UNIT_TESTS: false # VSCODE_RUN_INTEGRATION_TESTS: false diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 958203ec56..5be7f43abf 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -6,17 +6,12 @@ schedules: branches: include: - main - - joao/web trigger: branches: include: ["main", "release/*"] parameters: - - name: VSCODE_DISTRO_REF - displayName: Distro Ref (Private build) - type: string - default: " " - name: VSCODE_QUALITY displayName: Quality type: string @@ -25,10 +20,10 @@ parameters: - exploration - insider - stable - - name: ENABLE_TERRAPIN - displayName: "Enable Terrapin" - type: boolean - default: true + - name: NPM_REGISTRY + displayName: "Custom NPM Registry" + type: string + default: 'https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/' - name: VSCODE_BUILD_WIN32 displayName: "🎯 Windows x64" type: boolean @@ -53,12 +48,12 @@ parameters: displayName: "🎯 Linux armhf" type: boolean default: true - - name: VSCODE_BUILD_LINUX_ALPINE - displayName: "🎯 Alpine Linux x64" + - name: VSCODE_BUILD_ALPINE + displayName: "🎯 Alpine x64" type: boolean default: true - - name: VSCODE_BUILD_LINUX_ALPINE_ARM64 - displayName: "🎯 Alpine Linux arm64" + - name: VSCODE_BUILD_ALPINE_ARM64 + displayName: "🎯 Alpine arm64" type: boolean default: true - name: VSCODE_BUILD_MACOS @@ -99,16 +94,18 @@ parameters: default: false variables: - - name: VSCODE_DISTRO_REF - value: ${{ parameters.VSCODE_DISTRO_REF }} - - name: ENABLE_TERRAPIN - value: ${{ eq(parameters.ENABLE_TERRAPIN, true) }} + - name: VSCODE_PRIVATE_BUILD + value: ${{ ne(variables['Build.Repository.Uri'], 'https://github.com/microsoft/vscode.git') }} + - name: NPM_REGISTRY + value: ${{ parameters.NPM_REGISTRY }} - name: VSCODE_QUALITY value: ${{ parameters.VSCODE_QUALITY }} - name: VSCODE_BUILD_STAGE_WINDOWS value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_LINUX - value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true), eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true)) }} + value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }} + - name: VSCODE_BUILD_STAGE_ALPINE + value: ${{ or(eq(parameters.VSCODE_BUILD_ALPINE, true), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_MACOS value: ${{ or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_WEB @@ -116,7 +113,7 @@ variables: - name: VSCODE_CIBUILD value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - name: VSCODE_PUBLISH - value: ${{ and(eq(parameters.VSCODE_PUBLISH, true), eq(variables.VSCODE_CIBUILD, false)) }} + value: ${{ and(eq(parameters.VSCODE_PUBLISH, true), eq(variables.VSCODE_CIBUILD, false), eq(parameters.VSCODE_COMPILE_ONLY, false)) }} - name: VSCODE_PUBLISH_TO_MOONCAKE value: ${{ eq(parameters.VSCODE_PUBLISH_TO_MOONCAKE, true) }} - name: VSCODE_SCHEDULEDBUILD @@ -138,6 +135,8 @@ variables: - name: Codeql.SkipTaskAutoInjection value: true +name: "$(Date:yyyyMMdd).$(Rev:r) (${{ parameters.VSCODE_QUALITY }})" + resources: containers: - container: vscode-bionic-x64 @@ -145,25 +144,22 @@ resources: endpoint: VSCodeHub options: --user 0:0 --cap-add SYS_ADMIN - container: vscode-arm64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:stretch-arm64 + image: vscodehub.azurecr.io/vscode-linux-build-agent:buster-arm64 endpoint: VSCodeHub options: --user 0:0 --cap-add SYS_ADMIN - container: vscode-armhf - image: vscodehub.azurecr.io/vscode-linux-build-agent:stretch-armhf - endpoint: VSCodeHub - options: --user 0:0 --cap-add SYS_ADMIN - - container: centos7-devtoolset8-x64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-x64 + image: vscodehub.azurecr.io/vscode-linux-build-agent:buster-armhf endpoint: VSCodeHub options: --user 0:0 --cap-add SYS_ADMIN - container: snapcraft - image: snapcore/snapcraft:stable + image: vscodehub.azurecr.io/vscode-linux-build-agent:snapcraft-x64 + endpoint: VSCodeHub stages: - stage: Compile jobs: - job: Compile - pool: vscode-1es-linux + pool: 1es-ubuntu-20.04-x64 variables: VSCODE_ARCH: x64 steps: @@ -171,435 +167,526 @@ stages: parameters: VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - stage: CompileCLI + dependsOn: [] + jobs: + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - job: CLILinuxX64 + pool: 1es-ubuntu-20.04-x64 + steps: + - template: ./linux/cli-build-linux.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_LINUX: ${{ parameters.VSCODE_BUILD_LINUX }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true))) }}: + - job: CLILinuxGnuARM + pool: 1es-ubuntu-20.04-x64 + steps: + - template: ./linux/cli-build-linux.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_LINUX_ARMHF: ${{ parameters.VSCODE_BUILD_LINUX_ARMHF }} + VSCODE_BUILD_LINUX_ARM64: ${{ parameters.VSCODE_BUILD_LINUX_ARM64 }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE, true)) }}: + - job: CLIAlpineX64 + pool: 1es-ubuntu-20.04-x64 + steps: + - template: ./alpine/cli-build-alpine.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ALPINE: ${{ parameters.VSCODE_BUILD_ALPINE }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }}: + - job: CLIAlpineARM64 + pool: 1es-ubuntu-20.04-arm64 + steps: + - bash: sudo apt update && sudo apt install -y unzip + displayName: Install unzip + - template: ./alpine/cli-build-alpine.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_ALPINE_ARM64: ${{ parameters.VSCODE_BUILD_ALPINE_ARM64 }} + + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - job: CLIMacOSX64 + pool: + vmImage: macOS-11 + steps: + - template: ./darwin/cli-build-darwin.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: + - job: CLIMacOSARM64 + pool: + vmImage: macOS-11 + steps: + - template: ./darwin/cli-build-darwin.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} + + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - job: CLIWindowsX64 + pool: 1es-windows-2019-x64 + steps: + - template: ./win32/cli-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - job: CLIWindowsARM64 + pool: 1es-windows-2019-x64 + steps: + - template: ./win32/cli-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true)) }}: + - job: CLIWindowsX86 + pool: 1es-windows-2019-x64 + steps: + - template: ./win32/cli-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_BUILD_WIN32_32BIT: ${{ parameters.VSCODE_BUILD_WIN32_32BIT }} + - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WINDOWS'], true)) }}: - - stage: Windows - dependsOn: - - Compile - pool: vscode-1es-windows - jobs: - - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: WindowsUnitTests - displayName: Unit Tests - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: WindowsIntegrationTests - displayName: Integration Tests - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: WindowsSmokeTests - displayName: Smoke Tests - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - stage: Windows + dependsOn: + - Compile + - CompileCLI + pool: 1es-windows-2019-x64 + jobs: + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: + - job: WindowsUnitTests + displayName: Unit Tests + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: WindowsIntegrationTests + displayName: Integration Tests + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: WindowsSmokeTests + displayName: Smoke Tests + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32, true)) }}: - - job: Windows - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: x64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32, true)) }}: + - job: Windows + timeoutInMinutes: 120 + variables: + VSCODE_ARCH: x64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true)) }}: - - job: Windows32 - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: ia32 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + - job: WindowsCLISign + timeoutInMinutes: 90 + steps: + - template: win32/product-build-win32-cli-sign.yml + parameters: + VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} + VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} + VSCODE_BUILD_WIN32_32BIT: ${{ parameters.VSCODE_BUILD_WIN32_32BIT }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - job: WindowsARM64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: arm64 - steps: - - template: win32/product-build-win32.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_32BIT, true)) }}: + - job: Windows32 + timeoutInMinutes: 120 + variables: + VSCODE_ARCH: ia32 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - job: WindowsARM64 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: arm64 + steps: + - template: win32/product-build-win32.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_LINUX'], true)) }}: - - stage: LinuxServerDependencies - dependsOn: [] # run in parallel to compile stage - pool: vscode-1es-linux - jobs: - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - job: x64 - container: centos7-devtoolset8-x64 - variables: + - stage: Linux + dependsOn: + - Compile + - CompileCLI + pool: 1es-ubuntu-20.04-x64 + jobs: + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: + - job: Linuxx64UnitTest + displayName: Unit Tests + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux.yml + parameters: VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: linux/product-build-linux-server.yml - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: Linuxx64IntegrationTest + displayName: Integration Tests + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux.yml + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: Linuxx64SmokeTest + displayName: Smoke Tests + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux.yml + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - job: arm64 - variables: - VSCODE_ARCH: arm64 - steps: - - template: linux/product-build-linux-server.yml - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}: + - job: Linuxx64 + container: vscode-bionic-x64 + variables: + VSCODE_ARCH: x64 + NPM_ARCH: x64 + DISPLAY: ":10" + steps: + - template: linux/product-build-linux.yml + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_LINUX'], true)) }}: - - stage: Linux - dependsOn: - - Compile - - LinuxServerDependencies - pool: vscode-1es-linux - jobs: - - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: Linuxx64UnitTest - displayName: Unit Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64IntegrationTest - displayName: Integration Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: Linuxx64SmokeTest - displayName: Smoke Tests - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}: + - job: LinuxSnap + dependsOn: + - Linuxx64 + container: snapcraft + variables: + VSCODE_ARCH: x64 + steps: + - template: linux/snap-build-linux.yml - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}: - - job: Linuxx64 - container: vscode-bionic-x64 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true), ne(variables['VSCODE_PUBLISH'], 'false')) }}: - - job: LinuxSnap - dependsOn: - - Linuxx64 - container: snapcraft - variables: - VSCODE_ARCH: x64 - steps: - - template: linux/snap-build-linux.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - - job: LinuxArmhf - container: vscode-armhf - variables: + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: + - job: LinuxArmhf + container: vscode-armhf + variables: + VSCODE_ARCH: armhf + NPM_ARCH: arm + steps: + - template: linux/product-build-linux.yml + parameters: VSCODE_ARCH: armhf - NPM_ARCH: armv7l - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false - # TODO@joaomoreno: We don't ship ARM snaps for now - - ${{ if and(false, eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - - job: LinuxSnapArmhf - dependsOn: - - LinuxArmhf - container: snapcraft - variables: - VSCODE_ARCH: armhf - steps: - - template: linux/snap-build-linux.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - job: LinuxArm64 - container: vscode-arm64 - variables: + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: + - job: LinuxArm64 + container: vscode-arm64 + variables: + VSCODE_ARCH: arm64 + NPM_ARCH: arm64 + steps: + - template: linux/product-build-linux.yml + parameters: VSCODE_ARCH: arm64 - NPM_ARCH: arm64 - steps: - - template: linux/product-build-linux-client.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false - # TODO@joaomoreno: We don't ship ARM snaps for now - - ${{ if and(false, eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - job: LinuxSnapArm64 - dependsOn: - - LinuxArm64 - container: snapcraft - variables: - VSCODE_ARCH: arm64 - steps: - - template: linux/snap-build-linux.yml + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_ALPINE'], true)) }}: + - stage: Alpine + dependsOn: + - Compile + - CompileCLI + pool: 1es-ubuntu-20.04-x64 + jobs: + - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: + - job: LinuxAlpine + variables: + VSCODE_ARCH: x64 + steps: + - template: alpine/product-build-alpine.yml - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ALPINE, true)) }}: - - job: LinuxAlpine - variables: - VSCODE_ARCH: x64 - steps: - - template: linux/product-build-alpine.yml - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ALPINE_ARM64, true)) }}: - - job: LinuxAlpineArm64 - timeoutInMinutes: 120 - variables: - VSCODE_ARCH: arm64 - steps: - - template: linux/product-build-alpine.yml + - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: + - job: LinuxAlpineArm64 + timeoutInMinutes: 120 + variables: + VSCODE_ARCH: arm64 + steps: + - template: alpine/product-build-alpine.yml - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: - - stage: macOS - dependsOn: - - Compile - pool: - vmImage: macOS-latest - variables: - BUILDSECMON_OPT_IN: true - jobs: - - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: macOSUnitTest - displayName: Unit Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: true - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false - - job: macOSIntegrationTest - displayName: Integration Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: true - VSCODE_RUN_SMOKE_TESTS: false - - job: macOSSmokeTest - displayName: Smoke Tests - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: true + - stage: macOS + dependsOn: + - Compile + - CompileCLI + pool: + vmImage: macOS-11 + variables: + BUILDSECMON_OPT_IN: true + jobs: + - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: + - job: macOSUnitTest + displayName: Unit Tests + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: true + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false + - job: macOSIntegrationTest + displayName: Integration Tests + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: true + VSCODE_RUN_SMOKE_TESTS: false + - job: macOSSmokeTest + displayName: Smoke Tests + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: true - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}: - - job: macOS - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}: + - job: macOS + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false - - ${{ if eq(parameters.VSCODE_STEP_ON_IT, false) }}: - - job: macOSTest - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + - ${{ if eq(parameters.VSCODE_STEP_ON_IT, false) }}: + - job: macOSTest + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_INTEGRATION_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_SMOKE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - - ${{ if eq(variables['VSCODE_PUBLISH'], true) }}: - - job: macOSSign - dependsOn: - - macOS - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: x64 - steps: - - template: darwin/product-build-darwin-sign.yml + - job: macOSSign + dependsOn: + - macOS + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: x64 + steps: + - template: darwin/product-build-darwin-sign.yml - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - - job: macOSARM64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: arm64 - steps: - - template: darwin/product-build-darwin.yml - parameters: - VSCODE_PUBLISH: ${{ variables.VSCODE_PUBLISH }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_RUN_UNIT_TESTS: false - VSCODE_RUN_INTEGRATION_TESTS: false - VSCODE_RUN_SMOKE_TESTS: false + - job: macOSCLISign + timeoutInMinutes: 90 + steps: + - template: darwin/product-build-darwin-cli-sign.yml + parameters: + VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} + VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} - - ${{ if eq(variables['VSCODE_PUBLISH'], true) }}: - - job: macOSARM64Sign - dependsOn: - - macOSARM64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: arm64 - steps: - - template: darwin/product-build-darwin-sign.yml + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: + - job: macOSARM64 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: arm64 + steps: + - template: darwin/product-build-darwin.yml + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_UNIT_TESTS: false + VSCODE_RUN_INTEGRATION_TESTS: false + VSCODE_RUN_SMOKE_TESTS: false - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true)) }}: - - job: macOSUniversal - dependsOn: - - macOS - - macOSARM64 - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: universal - steps: - - template: darwin/product-build-darwin-universal.yml + - job: macOSARM64Sign + dependsOn: + - macOSARM64 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: arm64 + steps: + - template: darwin/product-build-darwin-sign.yml - - ${{ if eq(variables['VSCODE_PUBLISH'], true) }}: - - job: macOSUniversalSign - dependsOn: - - macOSUniversal - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: universal - steps: - - template: darwin/product-build-darwin-sign.yml + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true)) }}: + - job: macOSUniversal + dependsOn: + - macOS + - macOSARM64 + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: universal + steps: + - template: darwin/product-build-darwin-universal.yml + + - job: macOSUniversalSign + dependsOn: + - macOSUniversal + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: universal + steps: + - template: darwin/product-build-darwin-sign.yml - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WEB'], true)) }}: - - stage: Web - dependsOn: - - Compile - pool: vscode-1es-linux - jobs: - - ${{ if eq(parameters.VSCODE_BUILD_WEB, true) }}: - - job: Web - variables: - VSCODE_ARCH: x64 - steps: - - template: web/product-build-web.yml - - - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), ne(variables['VSCODE_PUBLISH'], 'false')) }}: - - stage: Publish - dependsOn: - - Compile - pool: vscode-1es-linux - variables: - - name: BUILDS_API_URL - value: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ - jobs: - - job: PublishBuild - timeoutInMinutes: 180 - displayName: Publish Build + - stage: Web + dependsOn: + - Compile + pool: 1es-ubuntu-20.04-x64 + jobs: + - ${{ if eq(parameters.VSCODE_BUILD_WEB, true) }}: + - job: Web + variables: + VSCODE_ARCH: x64 steps: - - template: product-publish.yml + - template: web/product-build-web.yml - - ${{ if or(and(parameters.VSCODE_RELEASE, eq(parameters.VSCODE_DISTRO_REF, ' ')), and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(variables['VSCODE_SCHEDULEDBUILD'], true))) }}: - - stage: Release - dependsOn: - - Publish - pool: vscode-1es-linux - jobs: - - job: ReleaseBuild - displayName: Release Build - steps: - - template: product-release.yml + - ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}: + - stage: Publish + dependsOn: + - Compile + pool: 1es-ubuntu-20.04-x64 + variables: + - name: BUILDS_API_URL + value: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ + jobs: + - job: PublishBuild + timeoutInMinutes: 180 + displayName: Publish Build + steps: + - template: product-publish.yml + + - ${{ if and(parameters.VSCODE_RELEASE, eq(variables['VSCODE_PRIVATE_BUILD'], false)) }}: + - stage: ApproveRelease + dependsOn: [] # run in parallel to compile stage + pool: 1es-ubuntu-20.04-x64 + jobs: + - deployment: ApproveRelease + displayName: "Approve Release" + environment: "vscode" + variables: + skipComponentGovernanceDetection: true + strategy: + runOnce: + deploy: + steps: + - checkout: none + + - ${{ if or(and(parameters.VSCODE_RELEASE, eq(variables['VSCODE_PRIVATE_BUILD'], false)), and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(variables['VSCODE_SCHEDULEDBUILD'], true))) }}: + - stage: Release + dependsOn: + - Publish + - ${{ if and(parameters.VSCODE_RELEASE, eq(variables['VSCODE_PRIVATE_BUILD'], false)) }}: + - ApproveRelease + pool: 1es-ubuntu-20.04-x64 + jobs: + - job: ReleaseBuild + displayName: Release Build + steps: + - template: product-release.yml + parameters: + VSCODE_RELEASE: ${{ parameters.VSCODE_RELEASE }} diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 381d49ee75..94a48b4325 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -8,78 +8,54 @@ steps: versionSpec: "16.x" - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - template: ./distro/download-distro.yml + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js compile > .build/yarnlockhash + displayName: Prepare node_modules cache key - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags - - # using `genericNodeModules` instead of `nodeModules` here to avoid sharing the cache with builds running inside containers - task: Cache@2 inputs: - key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash" + key: '"node_modules" | .build/yarnlockhash' path: .build/node_modules_cache cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz + - script: tar -xzf .build/node_modules_cache/cache.tgz condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) displayName: Extract node_modules cache - script: | set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages + npm config set registry "$NPM_REGISTRY" --location=project + npm config set always-auth=true --location=project + yarn config set registry "$NPM_REGISTRY" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn - - script: | - set -e - sudo apt update -y - sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libsecret-1-dev libnotify-bin + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libsecret-1-dev libnotify-bin displayName: Install build tools condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - script: | set -e - for i in {1..3}; do # try 3 times, for Terrapin + for i in {1..5}; do # try 5 times yarn --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 @@ -94,6 +70,11 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: node build/azure-pipelines/distro/mixin-npm + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + - script: | set -e node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt @@ -102,16 +83,17 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - # 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 + - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: + - script: yarn --cwd build compile && ./.github/workflows/check-clean-git-state.sh + displayName: Check /build/ folder - - script: | - set -e - yarn npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + + - template: common/install-builtin-extensions.yml + + - script: yarn npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check vscode-dts-compile-check tsec-compile-check env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Compile & Hygiene @@ -124,7 +106,6 @@ steps: displayName: Compile test suites condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: AzureCLI@2 inputs: azureSubscription: "vscode-builds-subscription" @@ -136,7 +117,6 @@ steps: Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_SECRET;issecret=true]$env:servicePrincipalKey" - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | set -e AZURE_STORAGE_ACCOUNT="ticino" \ @@ -146,35 +126,26 @@ steps: node build/azure-pipelines/upload-sourcemaps displayName: Upload sourcemaps - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set - - ./build/azure-pipelines/common/extract-telemetry.sh + - script: ./build/azure-pipelines/common/extract-telemetry.sh displayName: Extract Telemetry - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - tar -cz --ignore-failed-read -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz .build out-* test/integration/browser/out test/smoke/out test/automation/out + - script: tar -cz --ignore-failed-read --exclude='.build/node_modules_cache' --exclude='.build/node_modules_list.txt' --exclude='.build/distro' -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz .build out-* test/integration/browser/out test/smoke/out test/automation/out displayName: Compress compilation artifact - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: PublishPipelineArtifact@1 inputs: targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz artifactName: Compilation displayName: Publish compilation artifact - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn download-builtin-extensions-cg + - script: yarn download-builtin-extensions-cg + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Built-in extensions component details - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: "Component Detection" inputs: sourceScanPath: $(Build.SourcesDirectory) + alertWarningLevel: Medium continueOnError: true diff --git a/build/azure-pipelines/product-publish.ps1 b/build/azure-pipelines/product-publish.ps1 index 5006ec61a3..a9170d54f5 100644 --- a/build/azure-pipelines/product-publish.ps1 +++ b/build/azure-pipelines/product-publish.ps1 @@ -45,6 +45,7 @@ New-Item -Path $ARTIFACT_PROCESSED_FILE_PATH -Force | Out-Null $stages = @( if ($env:VSCODE_BUILD_STAGE_WINDOWS -eq 'True') { 'Windows' } if ($env:VSCODE_BUILD_STAGE_LINUX -eq 'True') { 'Linux' } + if ($env:VSCODE_BUILD_STAGE_ALPINE -eq 'True') { 'Alpine' } if ($env:VSCODE_BUILD_STAGE_MACOS -eq 'True') { 'macOS' } if ($env:VSCODE_BUILD_STAGE_WEB -eq 'True') { 'Web' } ) @@ -62,12 +63,15 @@ do { if($set.Add($artifactName)) { Write-Host "Processing artifact: '$artifactName. Downloading from: $($_.resource.downloadUrl)" + $extractPath = "$env:AGENT_TEMPDIRECTORY/$artifactName.zip" try { - Invoke-RestMethod $_.resource.downloadUrl -OutFile "$env:AGENT_TEMPDIRECTORY/$artifactName.zip" -Headers @{ + Invoke-RestMethod $_.resource.downloadUrl -OutFile $extractPath -Headers @{ Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN" - } -MaximumRetryCount 5 -RetryIntervalSec 1 | Out-Null + } -MaximumRetryCount 5 -RetryIntervalSec 1 -TimeoutSec 300 | Out-Null - Expand-Archive -Path "$env:AGENT_TEMPDIRECTORY/$artifactName.zip" -DestinationPath $env:AGENT_TEMPDIRECTORY | Out-Null + Write-Host "Extracting artifact: '$extractPath'" + + Expand-Archive -Path $extractPath -DestinationPath $env:AGENT_TEMPDIRECTORY | Out-Null } catch { Write-Warning $_ $set.Remove($artifactName) | Out-Null @@ -76,6 +80,13 @@ do { $null,$product,$os,$arch,$type = $artifactName -split '_' $asset = Get-ChildItem -rec "$env:AGENT_TEMPDIRECTORY/$artifactName" + + if ($asset.Size -ne $_.resource.properties.artifactsize) { + Write-Warning "Artifact size mismatch for '$artifactName'. Expected: $($_.resource.properties.artifactsize). Actual: $($asset.Size)" + $set.Remove($artifactName) | Out-Null + continue + } + Write-Host "Processing artifact with the following values:" # turning in into an object just to log nicely @{ diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index 80076fd666..2e2d735da0 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -7,38 +7,15 @@ steps: displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - - script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + # allow-any-unicode-next-line + - pwsh: Write-Host "##vso[build.addbuildtag]🚀" + displayName: Add build tag - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling - - - script: | - set -e - git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - pwsh: | - . build/azure-pipelines/win32/exec.ps1 - cd build - exec { yarn } + - pwsh: yarn + workingDirectory: build displayName: Install build dependencies - download: current @@ -75,24 +52,24 @@ steps: return } - $env:AZURE_TENANT_ID = "$(AZURE_TENANT_ID)" - $env:AZURE_CLIENT_ID = "$(AZURE_CLIENT_ID)" - $env:AZURE_CLIENT_SECRET = "$(AZURE_CLIENT_SECRET)" $VERSION = node -p "require('./package.json').version" Write-Host "Creating build with version: $VERSION" exec { node build/azure-pipelines/common/createBuild.js $VERSION } + env: + AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" + AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" + AZURE_CLIENT_SECRET: "$(AZURE_CLIENT_SECRET)" displayName: Create build if it hasn't been created before - - pwsh: | - $env:VSCODE_MIXIN_PASSWORD = "$(github-distro-mixin-password)" - $env:AZURE_TENANT_ID = "$(AZURE_TENANT_ID)" - $env:AZURE_CLIENT_ID = "$(AZURE_CLIENT_ID)" - $env:AZURE_CLIENT_SECRET = "$(AZURE_CLIENT_SECRET)" - $env:AZURE_MOONCAKE_TENANT_ID = "$(AZURE_MOONCAKE_TENANT_ID)" - $env:AZURE_MOONCAKE_CLIENT_ID = "$(AZURE_MOONCAKE_CLIENT_ID)" - $env:AZURE_MOONCAKE_CLIENT_SECRET = "$(AZURE_MOONCAKE_CLIENT_SECRET)" - build/azure-pipelines/product-publish.ps1 + - pwsh: build/azure-pipelines/product-publish.ps1 env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" + AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" + AZURE_CLIENT_SECRET: "$(AZURE_CLIENT_SECRET)" + AZURE_MOONCAKE_TENANT_ID: "$(AZURE_MOONCAKE_TENANT_ID)" + AZURE_MOONCAKE_CLIENT_ID: "$(AZURE_MOONCAKE_CLIENT_ID)" + AZURE_MOONCAKE_CLIENT_SECRET: "$(AZURE_MOONCAKE_CLIENT_SECRET)" SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Process artifacts @@ -108,6 +85,7 @@ steps: $stages = @( if ($env:VSCODE_BUILD_STAGE_WINDOWS -eq 'True') { 'Windows' } if ($env:VSCODE_BUILD_STAGE_LINUX -eq 'True') { 'Linux' } + if ($env:VSCODE_BUILD_STAGE_ALPINE -eq 'True') { 'Alpine' } if ($env:VSCODE_BUILD_STAGE_MACOS -eq 'True') { 'macOS' } if ($env:VSCODE_BUILD_STAGE_WEB -eq 'True') { 'Web' } ) diff --git a/build/azure-pipelines/product-release.yml b/build/azure-pipelines/product-release.yml index a108694559..8be900a9b4 100644 --- a/build/azure-pipelines/product-release.yml +++ b/build/azure-pipelines/product-release.yml @@ -1,3 +1,7 @@ +parameters: + - name: VSCODE_RELEASE + type: boolean + steps: - task: NodeTool@0 inputs: @@ -20,4 +24,4 @@ steps: AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ - node build/azure-pipelines/common/releaseBuild.js + node build/azure-pipelines/common/releaseBuild.js ${{ parameters.VSCODE_RELEASE }} diff --git a/build/azure-pipelines/publish-types/check-version.js b/build/azure-pipelines/publish-types/check-version.js index f787f897ae..3a2ed7222a 100644 --- a/build/azure-pipelines/publish-types/check-version.js +++ b/build/azure-pipelines/publish-types/check-version.js @@ -34,3 +34,4 @@ function isValidTag(t) { } return true; } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2hlY2stdmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImNoZWNrLXZlcnNpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxvQ0FBb0M7QUFFcEMsSUFBSSxHQUFHLEdBQUcsRUFBRSxDQUFDO0FBQ2IsSUFBSTtJQUNILEdBQUcsR0FBRyxFQUFFO1NBQ04sUUFBUSxDQUFDLHlEQUF5RCxDQUFDO1NBQ25FLFFBQVEsRUFBRTtTQUNWLElBQUksRUFBRSxDQUFDO0lBRVQsSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsRUFBRTtRQUNyQixNQUFNLEtBQUssQ0FBQyxlQUFlLEdBQUcsRUFBRSxDQUFDLENBQUM7S0FDbEM7Q0FDRDtBQUFDLE9BQU8sR0FBRyxFQUFFO0lBQ2IsT0FBTyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNuQixPQUFPLENBQUMsS0FBSyxDQUFDLHdCQUF3QixDQUFDLENBQUM7SUFDeEMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztDQUNoQjtBQUVELFNBQVMsVUFBVSxDQUFDLENBQVM7SUFDNUIsSUFBSSxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUU7UUFDOUIsT0FBTyxLQUFLLENBQUM7S0FDYjtJQUVELE1BQU0sQ0FBQyxLQUFLLEVBQUUsS0FBSyxFQUFFLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFekMsb0NBQW9DO0lBQ3BDLElBQUksR0FBRyxLQUFLLEdBQUcsRUFBRTtRQUNoQixPQUFPLEtBQUssQ0FBQztLQUNiO0lBRUQsSUFBSSxLQUFLLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQyxRQUFRLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUU7UUFDN0QsT0FBTyxLQUFLLENBQUM7S0FDYjtJQUVELE9BQU8sSUFBSSxDQUFDO0FBQ2IsQ0FBQyJ9 \ No newline at end of file diff --git a/build/azure-pipelines/publish-types/update-types.js b/build/azure-pipelines/publish-types/update-types.js index 6da5a12a86..be9b3fdd9c 100644 --- a/build/azure-pipelines/publish-types/update-types.js +++ b/build/azure-pipelines/publish-types/update-types.js @@ -74,3 +74,4 @@ function getNewFileHeader(tag) { ].join('\n'); return header; } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBkYXRlLXR5cGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidXBkYXRlLXR5cGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7QUFFaEcseUJBQXlCO0FBQ3pCLG9DQUFvQztBQUNwQyw2QkFBNkI7QUFFN0IsSUFBSSxHQUFHLEdBQUcsRUFBRSxDQUFDO0FBQ2IsSUFBSTtJQUNILEdBQUcsR0FBRyxFQUFFO1NBQ04sUUFBUSxDQUFDLHlEQUF5RCxDQUFDO1NBQ25FLFFBQVEsRUFBRTtTQUNWLElBQUksRUFBRSxDQUFDO0lBRVQsTUFBTSxNQUFNLEdBQUcsK0RBQStELEdBQUcsc0JBQXNCLENBQUMsQ0FBQyxzQ0FBc0M7SUFDL0ksTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEVBQUUseUNBQXlDLENBQUMsQ0FBQyxDQUFDLHNDQUFzQztJQUM5SCxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsTUFBTSxhQUFhLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFFbEQsYUFBYSxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsQ0FBQztJQUU1QixPQUFPLENBQUMsR0FBRyxDQUFDLGdDQUFnQyxPQUFPLEVBQUUsQ0FBQyxDQUFDLENBQUMsc0NBQXNDO0NBQzlGO0FBQUMsT0FBTyxHQUFHLEVBQUU7SUFDYixPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ25CLE9BQU8sQ0FBQyxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztJQUN4QyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO0NBQ2hCO0FBRUQsU0FBUyxhQUFhLENBQUMsT0FBZSxFQUFFLEdBQVc7SUFDbEQsTUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxPQUFPLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDckQsTUFBTSxVQUFVLEdBQUcsaUJBQWlCLENBQUMsVUFBVSxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBRXRELEVBQUUsQ0FBQyxhQUFhLENBQUMsT0FBTyxFQUFFLFVBQVUsQ0FBQyxDQUFDO0FBQ3ZDLENBQUM7QUFFRCxTQUFTLE1BQU0sQ0FBQyxHQUFXLEVBQUUsS0FBYTtJQUN6QyxNQUFNLE1BQU0sR0FBRyxJQUFJLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNoQyxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxFQUFFLENBQUMsRUFBRSxFQUFFO1FBQy9CLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUM7S0FDaEI7SUFDRCxPQUFPLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDeEIsQ0FBQztBQUVELFNBQVMsbUJBQW1CLENBQUMsR0FBVztJQUN2QyxPQUFPLEdBQUcsQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztBQUNuRSxDQUFDO0FBRUQsU0FBUyxpQkFBaUIsQ0FBQyxPQUFlLEVBQUUsR0FBVztJQUN0RCxNQUFNLFNBQVMsR0FBRztRQUNqQixpR0FBaUc7UUFDakcsK0RBQStEO1FBQy9ELGtHQUFrRztRQUNsRyxrR0FBa0c7S0FDbEcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFYixPQUFPLG1CQUFtQixDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxHQUFHLE9BQU8sQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDckYsQ0FBQztBQUVELFNBQVMsZ0JBQWdCLENBQUMsR0FBVztJQUNwQyxNQUFNLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDdEMsTUFBTSxRQUFRLEdBQUcsR0FBRyxLQUFLLElBQUksS0FBSyxFQUFFLENBQUM7SUFFckMseUNBQXlDO0lBQ3pDLE1BQU0sTUFBTSxHQUFHO1FBQ2QsNkNBQTZDLFFBQVEsRUFBRTtRQUN2RCwwREFBMEQ7UUFDMUQsdUVBQXVFO1FBQ3ZFLCtEQUErRDtRQUMvRCxnRUFBZ0U7UUFDaEUsc0VBQXNFO1FBQ3RFLG9FQUFvRTtRQUNwRSxFQUFFO1FBQ0YsaUdBQWlHO1FBQ2pHLCtEQUErRDtRQUMvRCxxQ0FBcUM7UUFDckMscUdBQXFHO1FBQ3JHLGtHQUFrRztRQUNsRyxFQUFFO1FBQ0YsS0FBSztRQUNMLDRDQUE0QyxRQUFRLGdCQUFnQjtRQUNwRSxpR0FBaUc7UUFDakcsS0FBSztLQUNMLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRWIsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDIn0= \ No newline at end of file diff --git a/build/azure-pipelines/sdl-scan.yml b/build/azure-pipelines/sdl-scan.yml index f6a44d4862..8c81ee2040 100644 --- a/build/azure-pipelines/sdl-scan.yml +++ b/build/azure-pipelines/sdl-scan.yml @@ -2,10 +2,10 @@ trigger: none pr: none parameters: - - name: ENABLE_TERRAPIN - displayName: "Enable Terrapin" - type: boolean - default: true + - name: NPM_REGISTRY + displayName: "Custom NPM Registry" + type: string + default: "https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/" - name: SCAN_WINDOWS displayName: "Scan Windows" type: boolean @@ -16,8 +16,8 @@ parameters: default: false variables: - - name: ENABLE_TERRAPIN - value: ${{ eq(parameters.ENABLE_TERRAPIN, true) }} + - name: NPM_REGISTRY + value: ${{ parameters.NPM_REGISTRY }} - name: SCAN_WINDOWS value: ${{ eq(parameters.SCAN_WINDOWS, true) }} - name: SCAN_LINUX @@ -30,12 +30,17 @@ variables: value: x64 - name: VSCODE_ARCH value: x64 + - name: Codeql.enabled + value: true + - name: Codeql.TSAEnabled + value: true + - name: Codeql.TSAOptionsPath + value: '$(Build.SourcesDirectory)\build\azure-pipelines\config\tsaoptions.json' stages: - stage: Windows condition: eq(variables.SCAN_WINDOWS, 'true') - pool: - vmImage: windows-latest + pool: 1es-windows-2019-x64 jobs: - job: WindowsJob timeoutInMinutes: 0 @@ -49,77 +54,89 @@ stages: inputs: versionSpec: "16.x" + - template: ./distro/download-distro.yml + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - 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 { npm config set registry "$env:NPM_REGISTRY" --location=project } + exec { npm config set always-auth=true --location=project } + exec { yarn config set registry "$env:NPM_REGISTRY" } + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn - exec { git config user.email "vscode@microsoft.com" } - exec { git config user.name "VSCode" } - displayName: Prepare tooling - - # - powershell: | - # . build/azure-pipelines/win32/exec.ps1 - # $ErrorActionPreference = "Stop" - - # exec { git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $(VSCODE_DISTRO_REF) } - # exec { git checkout FETCH_HEAD } - # condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - # displayName: Checkout override commit - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") } - displayName: Merge distro - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npx https://aka.ms/enablesecurefeed standAlone } - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages - - - task: Semmle@1 + - task: npmAuthenticate@0 inputs: - sourceCodeDirectory: "$(Build.SourcesDirectory)" - language: "cpp" - buildCommandsString: "yarn --frozen-lockfile --check-files" - querySuite: "Required" - timeout: "1800" - ram: "16384" - addProjectDirToScanningExclusionList: true - env: - npm_config_arch: "$(NPM_ARCH)" - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: CodeQL + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build/setup-npm-registry.js $env:NPM_REGISTRY } + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - task: CodeQL3000Init@0 + displayName: CodeQL Initialize + condition: eq(variables['Codeql.enabled'], 'True') + + - powershell: | + mkdir -Force .build/node-gyp + displayName: Create custom node-gyp directory + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - powershell: | + . ../../build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + # TODO: Should be replaced with upstream URL once https://github.com/nodejs/node-gyp/pull/2825 + # gets merged. + exec { git clone https://github.com/rzhao271/node-gyp.git . } "Cloning rzhao271/node-gyp failed" + exec { git checkout 102b347da0c92c29f9c67df22e864e70249cf086 } "Checking out 102b347 failed" + exec { npm install } "Building rzhao271/node-gyp failed" + displayName: Install custom node-gyp + workingDirectory: .build/node-gyp + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - powershell: | . build/azure-pipelines/win32/exec.ps1 . build/azure-pipelines/win32/retry.ps1 $ErrorActionPreference = "Stop" + $env:npm_config_node_gyp = "$(Join-Path $pwd.Path '.build/node-gyp/bin/node-gyp.js')" + $env:npm_config_arch = "$(NPM_ARCH)" retry { exec { yarn --frozen-lockfile --check-files } } env: - npm_config_arch: "$(NPM_ARCH)" PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" CHILD_CONCURRENCY: 1 displayName: Install dependencies - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn gulp "vscode-symbols-win32-$(VSCODE_ARCH)" } + - script: node build/azure-pipelines/distro/mixin-npm + displayName: Mixin distro node modules + + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + env: + VSCODE_QUALITY: stable + + - powershell: yarn compile + displayName: Compile + + - task: CodeQL3000Finalize@0 + displayName: CodeQL Finalize + condition: eq(variables['Codeql.enabled'], 'True') + + - powershell: yarn gulp "vscode-symbols-win32-$(VSCODE_ARCH)" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Symbols - task: BinSkim@4 @@ -130,10 +147,21 @@ stages: AnalyzeTargetGlob: '$(agent.builddirectory)\scanbin\**.dll;$(agent.builddirectory)\scanbin\**.exe;$(agent.builddirectory)\scanbin\**.node' AnalyzeLocalSymbolDirectories: '$(agent.builddirectory)\scanbin\VSCode-win32-$(VSCODE_ARCH)\pdb' + - task: AntiMalware@4 + inputs: + InputType: Basic + ScanType: CustomScan + FileDirPath: '$(Build.SourcesDirectory)' + EnableServices: true + SupportLogOnError: false + TreatSignatureUpdateFailureAs: 'Warning' + SignatureFreshness: 'OneDay' + TreatStaleSignatureAs: 'Error' + - task: TSAUpload@2 inputs: GdnPublishTsaOnboard: true - GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\build\azure-pipelines\.gdntsa' + GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\build\azure-pipelines\config\tsaoptions.json' - stage: Linux dependsOn: [] @@ -150,49 +178,36 @@ stages: inputs: versionSpec: "16.x" + - template: ./distro/download-distro.yml + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - script: | set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + npm config set registry "$NPM_REGISTRY" --location=project + npm config set always-auth=true --location=project + yarn config set registry "$NPM_REGISTRY" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication - # - script: | - # set -e - # git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - # echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - # git checkout FETCH_HEAD - # condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - # displayName: Checkout override commit + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - script: | set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages - - - script: | - set -e - for i in {1..3}; do # try 3 times, for Terrapin + for i in {1..5}; do # try 5 times yarn --cwd build --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 @@ -219,13 +234,13 @@ stages: # Set compiler toolchain export CC=$PWD/.build/CR_Clang/bin/clang export CXX=$PWD/.build/CR_Clang/bin/clang++ - export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -isystem$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit" + export CXXFLAGS="-std=c++17 -nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr" export LDFLAGS="-stdlib=libc++ -fuse-ld=lld -flto=thin -fsplit-lto-unit -L$PWD/.build/libcxx-objects -lc++abi" export VSCODE_REMOTE_CC=$(which gcc) export VSCODE_REMOTE_CXX=$(which g++) fi - for i in {1..3}; do # try 3 times, for Terrapin + for i in {1..5}; do # try 5 times yarn --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 @@ -238,9 +253,23 @@ stages: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Install dependencies - - script: | - set -e - yarn gulp vscode-symbols-linux-$(VSCODE_ARCH) + - script: yarn --frozen-lockfile --check-files + workingDirectory: .build/distro/npm + env: + npm_config_arch: $(NPM_ARCH) + displayName: Install distro node modules + + - script: node build/azure-pipelines/distro/mixin-npm + displayName: Mixin distro node modules + + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality + env: + VSCODE_QUALITY: stable + + - script: yarn gulp vscode-symbols-linux-$(VSCODE_ARCH) + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build - task: BinSkim@3 @@ -251,4 +280,4 @@ stages: - task: TSAUpload@2 inputs: - GdnPublishTsaConfigFile: '$(Build.SourceDirectory)\build\azure-pipelines\.gdntsa' + GdnPublishTsaConfigFile: '$(Build.SourceDirectory)\build\azure-pipelines\config\tsaoptions.json' diff --git a/build/azure-pipelines/sql-product-build.yml b/build/azure-pipelines/sql-product-build.yml index 0ce7c696d8..b1dfaaa41a 100644 --- a/build/azure-pipelines/sql-product-build.yml +++ b/build/azure-pipelines/sql-product-build.yml @@ -1,7 +1,7 @@ resources: containers: - container: linux-x64 - image: sqltoolscontainers.azurecr.io/linux-build-agent:6 + image: sqltoolscontainers.azurecr.io/linux-build-agent:7 endpoint: SqlToolsContainers stages: diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js index 5bf24c505a..621db49524 100644 --- a/build/azure-pipelines/upload-cdn.js +++ b/build/azure-pipelines/upload-cdn.js @@ -9,50 +9,109 @@ const Vinyl = require("vinyl"); const vfs = require("vinyl-fs"); const filter = require("gulp-filter"); const gzip = require("gulp-gzip"); +const mime = require("mime"); const identity_1 = require("@azure/identity"); const azure = require('gulp-azure-storage'); -const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; +const commit = process.env['BUILD_SOURCEVERSION']; const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); +mime.define({ + 'application/typescript': ['ts'], + 'application/json': ['code-snippets'], +}); +// From default AFD configuration +const MimeTypesToCompress = new Set([ + 'application/eot', + 'application/font', + 'application/font-sfnt', + 'application/javascript', + 'application/json', + 'application/opentype', + 'application/otf', + 'application/pkcs7-mime', + 'application/truetype', + 'application/ttf', + 'application/typescript', + 'application/vnd.ms-fontobject', + 'application/xhtml+xml', + 'application/xml', + 'application/xml+rss', + 'application/x-font-opentype', + 'application/x-font-truetype', + 'application/x-font-ttf', + 'application/x-httpd-cgi', + 'application/x-javascript', + 'application/x-mpegurl', + 'application/x-opentype', + 'application/x-otf', + 'application/x-perl', + 'application/x-ttf', + 'font/eot', + 'font/ttf', + 'font/otf', + 'font/opentype', + 'image/svg+xml', + 'text/css', + 'text/csv', + 'text/html', + 'text/javascript', + 'text/js', + 'text/markdown', + 'text/plain', + 'text/richtext', + 'text/tab-separated-values', + 'text/xml', + 'text/x-script', + 'text/x-component', + 'text/x-java-source' +]); +function wait(stream) { + return new Promise((c, e) => { + stream.on('end', () => c()); + stream.on('error', (err) => e(err)); + }); +} async function main() { const files = []; - const options = { + const options = (compressed) => ({ account: process.env.AZURE_STORAGE_ACCOUNT, credential, container: process.env.VSCODE_QUALITY, prefix: commit + '/', contentSettings: { - contentEncoding: 'gzip', + contentEncoding: compressed ? 'gzip' : undefined, cacheControl: 'max-age=31536000, public' } - }; - await new Promise((c, e) => { - vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) - .pipe(filter(f => !f.isDirectory())) - .pipe(gzip({ append: false })) - .pipe(es.through(function (data) { - console.log('Uploading:', data.relative); // debug - files.push(data.relative); - this.emit('data', data); - })) - .pipe(azure.upload(options)) - .on('end', () => c()) - .on('error', (err) => e(err)); }); - await new Promise((c, e) => { - const listing = new Vinyl({ - path: 'files.txt', - contents: Buffer.from(files.join('\n')), - stat: { mode: 0o666 } - }); - console.log(`Uploading: files.txt (${files.length} files)`); // debug - es.readArray([listing]) - .pipe(gzip({ append: false })) - .pipe(azure.upload(options)) - .on('end', () => c()) - .on('error', (err) => e(err)); + const all = vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) + .pipe(filter(f => !f.isDirectory())); + const compressed = all + .pipe(filter(f => MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + const uncompressed = all + .pipe(filter(f => !MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(azure.upload(options(false))); + const out = es.merge(compressed, uncompressed) + .pipe(es.through(function (f) { + console.log('Uploaded:', f.relative); + files.push(f.relative); + this.emit('data', f); + })); + console.log(`Uploading files to CDN...`); // debug + await wait(out); + const listing = new Vinyl({ + path: 'files.txt', + contents: Buffer.from(files.join('\n')), + stat: { mode: 0o666 } }); + const filesOut = es.readArray([listing]) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + console.log(`Uploading: files.txt (${files.length} files)`); // debug + await wait(filesOut); } main().catch(err => { console.error(err); process.exit(1); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBsb2FkLWNkbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInVwbG9hZC1jZG4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxtQ0FBbUM7QUFDbkMsK0JBQStCO0FBQy9CLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsa0NBQWtDO0FBQ2xDLDZCQUE2QjtBQUM3Qiw4Q0FBeUQ7QUFDekQsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBQUM7QUFFNUMsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0FBQ2xELE1BQU0sVUFBVSxHQUFHLElBQUksaUNBQXNCLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBRSxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLHFCQUFxQixDQUFFLENBQUMsQ0FBQztBQUVySixJQUFJLENBQUMsTUFBTSxDQUFDO0lBQ1gsd0JBQXdCLEVBQUUsQ0FBQyxJQUFJLENBQUM7SUFDaEMsa0JBQWtCLEVBQUUsQ0FBQyxlQUFlLENBQUM7Q0FDckMsQ0FBQyxDQUFDO0FBRUgsaUNBQWlDO0FBQ2pDLE1BQU0sbUJBQW1CLEdBQUcsSUFBSSxHQUFHLENBQUM7SUFDbkMsaUJBQWlCO0lBQ2pCLGtCQUFrQjtJQUNsQix1QkFBdUI7SUFDdkIsd0JBQXdCO0lBQ3hCLGtCQUFrQjtJQUNsQixzQkFBc0I7SUFDdEIsaUJBQWlCO0lBQ2pCLHdCQUF3QjtJQUN4QixzQkFBc0I7SUFDdEIsaUJBQWlCO0lBQ2pCLHdCQUF3QjtJQUN4QiwrQkFBK0I7SUFDL0IsdUJBQXVCO0lBQ3ZCLGlCQUFpQjtJQUNqQixxQkFBcUI7SUFDckIsNkJBQTZCO0lBQzdCLDZCQUE2QjtJQUM3Qix3QkFBd0I7SUFDeEIseUJBQXlCO0lBQ3pCLDBCQUEwQjtJQUMxQix1QkFBdUI7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 \ No newline at end of file diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index d28f20ac52..e6de5e15dd 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -8,53 +8,119 @@ import * as Vinyl from 'vinyl'; import * as vfs from 'vinyl-fs'; import * as filter from 'gulp-filter'; import * as gzip from 'gulp-gzip'; +import * as mime from 'mime'; import { ClientSecretCredential } from '@azure/identity'; const azure = require('gulp-azure-storage'); -const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; +const commit = process.env['BUILD_SOURCEVERSION']; const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); +mime.define({ + 'application/typescript': ['ts'], + 'application/json': ['code-snippets'], +}); + +// From default AFD configuration +const MimeTypesToCompress = new Set([ + 'application/eot', + 'application/font', + 'application/font-sfnt', + 'application/javascript', + 'application/json', + 'application/opentype', + 'application/otf', + 'application/pkcs7-mime', + 'application/truetype', + 'application/ttf', + 'application/typescript', + 'application/vnd.ms-fontobject', + 'application/xhtml+xml', + 'application/xml', + 'application/xml+rss', + 'application/x-font-opentype', + 'application/x-font-truetype', + 'application/x-font-ttf', + 'application/x-httpd-cgi', + 'application/x-javascript', + 'application/x-mpegurl', + 'application/x-opentype', + 'application/x-otf', + 'application/x-perl', + 'application/x-ttf', + 'font/eot', + 'font/ttf', + 'font/otf', + 'font/opentype', + 'image/svg+xml', + 'text/css', + 'text/csv', + 'text/html', + 'text/javascript', + 'text/js', + 'text/markdown', + 'text/plain', + 'text/richtext', + 'text/tab-separated-values', + 'text/xml', + 'text/x-script', + 'text/x-component', + 'text/x-java-source' +]); + +function wait(stream: es.ThroughStream): Promise { + return new Promise((c, e) => { + stream.on('end', () => c()); + stream.on('error', (err: any) => e(err)); + }); +} + async function main(): Promise { const files: string[] = []; - const options = { + const options = (compressed: boolean) => ({ account: process.env.AZURE_STORAGE_ACCOUNT, credential, container: process.env.VSCODE_QUALITY, prefix: commit + '/', contentSettings: { - contentEncoding: 'gzip', + contentEncoding: compressed ? 'gzip' : undefined, cacheControl: 'max-age=31536000, public' } - }; - - await new Promise((c, e) => { - vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) - .pipe(filter(f => !f.isDirectory())) - .pipe(gzip({ append: false })) - .pipe(es.through(function (data: Vinyl) { - console.log('Uploading:', data.relative); // debug - files.push(data.relative); - this.emit('data', data); - })) - .pipe(azure.upload(options)) - .on('end', () => c()) - .on('error', (err: any) => e(err)); }); - await new Promise((c, e) => { - const listing = new Vinyl({ - path: 'files.txt', - contents: Buffer.from(files.join('\n')), - stat: { mode: 0o666 } as any - }); + const all = vfs.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) + .pipe(filter(f => !f.isDirectory())); - console.log(`Uploading: files.txt (${files.length} files)`); // debug - es.readArray([listing]) - .pipe(gzip({ append: false })) - .pipe(azure.upload(options)) - .on('end', () => c()) - .on('error', (err: any) => e(err)); + const compressed = all + .pipe(filter(f => MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + + const uncompressed = all + .pipe(filter(f => !MimeTypesToCompress.has(mime.lookup(f.path)))) + .pipe(azure.upload(options(false))); + + const out = es.merge(compressed, uncompressed) + .pipe(es.through(function (f) { + console.log('Uploaded:', f.relative); + files.push(f.relative); + this.emit('data', f); + })); + + console.log(`Uploading files to CDN...`); // debug + await wait(out); + + const listing = new Vinyl({ + path: 'files.txt', + contents: Buffer.from(files.join('\n')), + stat: { mode: 0o666 } as any }); + + const filesOut = es.readArray([listing]) + .pipe(gzip({ append: false })) + .pipe(azure.upload(options(true))); + + console.log(`Uploading: files.txt (${files.length} files)`); // debug + await wait(filesOut); } main().catch(err => { diff --git a/build/azure-pipelines/upload-configuration.js b/build/azure-pipelines/upload-configuration.js index a8739f80cd..11e706c994 100644 --- a/build/azure-pipelines/upload-configuration.js +++ b/build/azure-pipelines/upload-configuration.js @@ -13,7 +13,7 @@ const util = require("../lib/util"); const identity_1 = require("@azure/identity"); const azure = require('gulp-azure-storage'); const packageJson = require("../../package.json"); -const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; +const commit = process.env['BUILD_SOURCEVERSION']; function generateVSCodeConfigurationTask() { return new Promise((resolve, reject) => { const buildDir = process.env['AGENT_BUILDDIRECTORY']; @@ -109,3 +109,4 @@ if (require.main === module) { process.exit(1); }); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBsb2FkLWNvbmZpZ3VyYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ1cGxvYWQtY29uZmlndXJhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyw2QkFBNkI7QUFDN0IseUJBQXlCO0FBQ3pCLG9DQUFvQztBQUNwQyxnQ0FBZ0M7QUFDaEMsb0NBQW9DO0FBQ3BDLDhDQUF5RDtBQUN6RCxNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsb0JBQW9CLENBQUMsQ0FBQztBQUM1QyxrREFBa0Q7QUFFbEQsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBQyxDQUFDO0FBRWxELFNBQVMsK0JBQStCO0lBQ3ZDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDdEMsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ3JELElBQUksQ0FBQyxRQUFRLEVBQUU7WUFDZCxPQUFPLE1BQU0sQ0FBQyxJQUFJLEtBQUssQ0FBQywrQkFBK0IsQ0FBQyxDQUFDLENBQUM7U0FDMUQ7UUFFRCxJQUFJLENBQUMseUJBQXlCLEVBQUUsRUFBRTtZQUNqQyxPQUFPLENBQUMsR0FBRyxDQUFDLCtDQUErQyxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixFQUFFLENBQUMsQ0FBQztZQUM3RixPQUFPLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztTQUMxQjtRQUVELElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLEtBQUssU0FBUyxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxLQUFLLFFBQVEsRUFBRTtZQUN4RixPQUFPLENBQUMsR0FBRyxDQUFDLGtEQUFrRCxPQUFPLENBQUMsR0FBRyxDQUFDLGNBQWMsRUFBRSxDQUFDLENBQUM7WUFDNUYsT0FBTyxPQUFPLENBQUMsU0FBUyxDQUFD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 \ No newline at end of file diff --git a/build/azure-pipelines/upload-configuration.ts b/build/azure-pipelines/upload-configuration.ts index dbcc1b99b7..ffee530c44 100644 --- a/build/azure-pipelines/upload-configuration.ts +++ b/build/azure-pipelines/upload-configuration.ts @@ -12,7 +12,7 @@ import { ClientSecretCredential } from '@azure/identity'; const azure = require('gulp-azure-storage'); import * as packageJson from '../../package.json'; -const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; +const commit = process.env['BUILD_SOURCEVERSION']; function generateVSCodeConfigurationTask(): Promise { return new Promise((resolve, reject) => { diff --git a/build/azure-pipelines/upload-nlsmetadata.js b/build/azure-pipelines/upload-nlsmetadata.js index df30c0d159..4a73a2f5b3 100644 --- a/build/azure-pipelines/upload-nlsmetadata.js +++ b/build/azure-pipelines/upload-nlsmetadata.js @@ -12,7 +12,7 @@ const identity_1 = require("@azure/identity"); const path = require("path"); const fs_1 = require("fs"); const azure = require('gulp-azure-storage'); -const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; +const commit = process.env['BUILD_SOURCEVERSION']; const credential = new identity_1.ClientSecretCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_CLIENT_SECRET']); function main() { return new Promise((c, e) => { @@ -99,3 +99,4 @@ main().catch(err => { console.error(err); process.exit(1); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBsb2FkLW5sc21ldGFkYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidXBsb2FkLW5sc21ldGFkYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7QUFFaEcsbUNBQW1DO0FBRW5DLGdDQUFnQztBQUNoQyx5Q0FBeUM7QUFDekMsa0NBQWtDO0FBQ2xDLDhDQUF5RDtBQUN6RCw2QkFBOEI7QUFDOUIsMkJBQWtDO0FBQ2xDLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO0FBRTVDLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLENBQUMsQ0FBQztBQUNsRCxNQUFNLFVBQVUsR0FBRyxJQUFJLGlDQUFzQixDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsaUJBQWlCLENBQUUsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFFLEVBQUUsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBRSxDQUFDLENBQUM7QUFRckosU0FBUyxJQUFJO0lBQ1osT0FBTyxJQUFJLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRTtRQUUzQixFQUFFLENBQUMsS0FBSyxDQUNQLEdBQUcsQ0FBQyxHQUFHLENBQUMsc0NBQXNDLEVBQUUsRUFBRSxJQUFJLEVBQUUsb0JBQW9CLEVBQUUsQ0FBQyxFQUMvRSxHQUFHLENBQUMsR0FBRyxDQUFDLHdDQUF3QyxFQUFFLEVBQUUsSUFBSSxFQUFFLG1CQUFtQixFQUFFLENBQUMsRUFDaEYsR0FBRyxDQUFDLEdBQUcsQ0FBQywrQ0FBK0MsRUFBRSxFQUFFLElBQUksRUFBRSxtQkFBbUIsRUFBRSxDQUFDLEVBQ3ZGLEdBQUcsQ0FBQyxHQUFHLENBQUMsdUNBQXVDLEVBQUUsRUFBRSxJQUFJLEVBQUUsbUJBQW1CLEVBQUUsQ0FBQyxDQUFDO2FBQy9FLElBQUksQ0FBQyxLQUFLLENBQUM7WUFDWCxRQUFRLEVB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 \ No newline at end of file diff --git a/build/azure-pipelines/upload-nlsmetadata.ts b/build/azure-pipelines/upload-nlsmetadata.ts index e2a95c782b..46245925df 100644 --- a/build/azure-pipelines/upload-nlsmetadata.ts +++ b/build/azure-pipelines/upload-nlsmetadata.ts @@ -13,7 +13,7 @@ import path = require('path'); import { readFileSync } from 'fs'; const azure = require('gulp-azure-storage'); -const commit = process.env['VSCODE_DISTRO_COMMIT'] || process.env['BUILD_SOURCEVERSION']; +const commit = process.env['BUILD_SOURCEVERSION']; const credential = new ClientSecretCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, process.env['AZURE_CLIENT_SECRET']!); interface NlsMetadata { diff --git a/build/azure-pipelines/upload-sourcemaps.js b/build/azure-pipelines/upload-sourcemaps.js index 7392cd2efe..9983c44c77 100644 --- a/build/azure-pipelines/upload-sourcemaps.js +++ b/build/azure-pipelines/upload-sourcemaps.js @@ -53,3 +53,4 @@ function main() { })); } main(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBsb2FkLXNvdXJjZW1hcHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ1cGxvYWQtc291cmNlbWFwcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLDZCQUE2QjtBQUM3QixtQ0FBbUM7QUFFbkMsZ0NBQWdDO0FBQ2hDLG9DQUFvQztBQUNwQyxhQUFhO0FBQ2IsNENBQTRDO0FBQzVDLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO0FBRTVDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBQ25ELE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLENBQUM7QUFFckMsMkRBQTJEO0FBQzNELE1BQU0sQ0FBQyxFQUFFLEFBQUQsRUFBRyxJQUFJLEVBQUUsSUFBSSxDQUFDLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQztBQUV0QyxTQUFTLEdBQUcsQ0FBQyxJQUFZLEVBQUUsSUFBSSxHQUFHLEdBQUcsSUFBSSxXQUFXO0lBQ25ELE9BQU8sR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQztTQUM1QixJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQVEsRUFBRSxFQUFFO1FBQzdCLENBQUMsQ0FBQyxJQUFJLEdBQUcsR0FBRyxDQUFDLENBQUMsSUFBSSxTQUFTLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUN4QyxPQUFPLENBQUMsQ0FBQztJQUNWLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDO0FBRUQsU0FBUyxJQUFJO0lBQ1osTUFBTSxPQUFPLEdBQUcsRUFBRSxDQUFDO0lBRW5CLCtCQUErQjtJQUMvQixJQUFJLENBQUMsSUFBSSxFQUFFO1FBQ1YsTUFBTSxFQUFFLEdBQUcsR0FBRyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQywwQkFBMEI7UUFDNUQsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUVqQixNQUFNLHNCQUFzQixHQUFzRCxJQUFJLENBQUMseUJBQXlCLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkgsTUFBTSx5QkFBeUIsR0FBRyxzQkFBc0IsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDM0gsTUFBTSxXQUFXLEdBQUcsR0FBRyxDQUFDLEdBQUcsQ0FBQyx5QkFBeUIsRUFBRSxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsQ0FBQzthQUNuRSxJQUFJLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDekUsT0FBTyxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUUxQixNQUFNLGFBQWEsR0FBRyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsK0JBQStCLEVBQUUscUJBQXFCLENBQUMsRUFBRSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQzVHLE9BQU8sQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUM7S0FDNUI7SUFFRCw0QkFBNEI7U0FDdkI7UUFDSixPQUFPLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztLQUM5QjtJQUVELE9BQU8sRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLE9BQU8sQ0FBQztTQUN6QixJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxVQUFVLElBQVc7UUFDckMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxRQUFRO1FBQzNELElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3pCLENBQUMsQ0FBQyxDQUFDO1NBQ0YsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUM7UUFDbEIsT0FBTyxFQUFFLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCO1FBQzFDLEdBQUcsRUFBRSxPQUFPLENBQUMsR0FBRyxDQUFDLHdCQUF3QjtRQUN6QyxTQUFTLEVBQUUsWUFBWTtRQUN2QixNQUFNLEVBQUUsTUFBTSxHQUFHLEdBQUc7S0FDcEIsQ0FBQyxDQUFDLENBQUM7QUFDTixDQUFDO0FBRUQsSUFBSSxFQUFFLENBQUMifQ== \ No newline at end of file diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 376f14c6bc..556f1f23ef 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -3,11 +3,13 @@ steps: inputs: versionSpec: "16.x" + - template: ../distro/download-distro.yml + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - task: DownloadPipelineArtifact@2 @@ -16,65 +18,44 @@ steps: path: $(Build.ArtifactStagingDirectory) displayName: Download compilation output - - script: | - set -e - tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz displayName: Extract compilation output - - script: | - set -e - cat << EOF > ~/.netrc - machine github.com - login vscode - password $(github-distro-mixin-password) - EOF + - script: node build/setup-npm-registry.js $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - git config user.email "vscode@microsoft.com" - git config user.name "VSCode" - displayName: Prepare tooling - - - script: | - set -e - git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $VSCODE_DISTRO_REF - echo "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - git checkout FETCH_HEAD - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - script: | - set -e - git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") - displayName: Merge distro - - - script: | - mkdir -p .build - node build/azure-pipelines/common/computeNodeModulesCacheKey.js "web" $ENABLE_TERRAPIN > .build/yarnlockhash - displayName: Prepare yarn cache flags + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js web > .build/yarnlockhash + displayName: Prepare node_modules cache key - task: Cache@2 inputs: - key: "nodeModules | $(Agent.OS) | .build/yarnlockhash" + key: '"node_modules" | .build/yarnlockhash' path: .build/node_modules_cache cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache - - script: | - set -e - tar -xzf .build/node_modules_cache/cache.tgz + - script: tar -xzf .build/node_modules_cache/cache.tgz condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) displayName: Extract node_modules cache - script: | set -e - npx https://aka.ms/enablesecurefeed standAlone - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages + npm config set registry "$NPM_REGISTRY" --location=project + npm config set always-auth=true --location=project + yarn config set registry "$NPM_REGISTRY" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication - script: | set -e - for i in {1..3}; do # try 3 times, for Terrapin + for i in {1..5}; do # try 5 times yarn --frozen-lockfile --check-files && break if [ $i -eq 3 ]; then echo "Yarn failed too many times" >&2 @@ -89,6 +70,10 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: node build/azure-pipelines/distro/mixin-npm + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + - script: | set -e node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt @@ -97,15 +82,14 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Create node_modules archive - - script: | - set -e - node build/azure-pipelines/mixin - displayName: Mix in quality + - script: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality - - script: | - set -e - VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \ - yarn gulp vscode-web-min-ci + - template: ../common/install-builtin-extensions.yml + + - script: yarn gulp vscode-web-min-ci + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build - task: AzureCLI@2 @@ -148,7 +132,6 @@ steps: AZURE_CLIENT_SECRET="$(AZURE_CLIENT_SECRET)" \ node build/azure-pipelines/upload-nlsmetadata displayName: Upload NLS Metadata - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - script: | set -e @@ -163,9 +146,7 @@ steps: cd $ROOT && tar --owner=0 --group=0 -czf $WEB_TARBALL_PATH $WEB_BUILD_NAME displayName: Prepare for publish - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) - publish: $(Agent.BuildDirectory)/vscode-web.tar.gz artifact: vscode_web_linux_standalone_archive-unsigned displayName: Publish web archive - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) diff --git a/build/azure-pipelines/win32/cli-build-win32.yml b/build/azure-pipelines/win32/cli-build-win32.yml new file mode 100644 index 0000000000..2e600bc18c --- /dev/null +++ b/build/azure-pipelines/win32/cli-build-win32.yml @@ -0,0 +1,83 @@ +parameters: + - name: VSCODE_BUILD_WIN32 + type: boolean + default: false + - name: VSCODE_BUILD_WIN32_32BIT + type: boolean + default: false + - name: VSCODE_BUILD_WIN32_ARM64 + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +steps: + - task: NodeTool@0 + inputs: + versionSpec: "16.x" + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - template: ../distro/download-distro.yml + - pwsh: node build/azure-pipelines/distro/apply-cli-patches + displayName: Apply distro patches + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.8 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - powershell: | + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.8.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + - powershell: node build/azure-pipelines/cli/prepare.js + displayName: Prepare CLI build + env: + VSCODE_CLI_PREPARE_ROOT: $(Build.SourcesDirectory)/.build/distro + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + GITHUB_TOKEN: "$(github-distro-mixin-password)" + + - template: ../cli/install-rust-win32.yml + parameters: + targets: + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - x86_64-pc-windows-msvc + - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: + - aarch64-pc-windows-msvc + - ${{ if eq(parameters.VSCODE_BUILD_WIN32_32BIT, true) }}: + - i686-pc-windows-msvc + + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - template: ../cli/cli-compile-and-publish.yml + parameters: + VSCODE_CLI_TARGET: x86_64-pc-windows-msvc + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_x64_cli + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static-md/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static-md/include + RUSTFLAGS: "-C target-feature=+crt-static" + + - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: + - template: ../cli/cli-compile-and-publish.yml + parameters: + VSCODE_CLI_TARGET: aarch64-pc-windows-msvc + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_arm64_cli + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static-md/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static-md/include + RUSTFLAGS: "-C target-feature=+crt-static" + + - ${{ if eq(parameters.VSCODE_BUILD_WIN32_32BIT, true) }}: + - template: ../cli/cli-compile-and-publish.yml + parameters: + VSCODE_CLI_TARGET: i686-pc-windows-msvc + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_ia32_cli + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x86-windows-static-md/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x86-windows-static-md/include + RUSTFLAGS: "-C target-feature=+crt-static" diff --git a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml new file mode 100644 index 0000000000..31bffda478 --- /dev/null +++ b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml @@ -0,0 +1,52 @@ +parameters: + - name: VSCODE_BUILD_WIN32 + type: boolean + - name: VSCODE_BUILD_WIN32_ARM64 + type: boolean + - name: VSCODE_BUILD_WIN32_32BIT + type: boolean + +steps: + - task: NodeTool@0 + displayName: "Use Node.js" + inputs: + versionSpec: "16.x" + + - powershell: node build/setup-npm-registry.js $env:NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - powershell: | + . azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm config set registry "$env:NPM_REGISTRY" --location=project } + exec { npm config set always-auth=true --location=project } + exec { yarn config set registry "$env:NPM_REGISTRY" } + workingDirectory: build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: build/.npmrc + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - powershell: | + . azure-pipelines/win32/exec.ps1 + . azure-pipelines/win32/retry.ps1 + $ErrorActionPreference = "Stop" + $env:CHILD_CONCURRENCY="1" + retry { exec { yarn --frozen-lockfile --check-files } } + workingDirectory: build + displayName: Install build dependencies + + - template: ../cli/cli-win32-sign.yml + parameters: + VSCODE_CLI_ARTIFACTS: + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - unsigned_vscode_cli_win32_x64_cli + - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: + - unsigned_vscode_cli_win32_arm64_cli + - ${{ if eq(parameters.VSCODE_BUILD_WIN32_32BIT, true) }}: + - unsigned_vscode_cli_win32_ia32_cli diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index 59c91cd2b1..19d5e8feac 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -9,58 +9,36 @@ parameters: type: boolean steps: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" } + - powershell: yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn electron $(VSCODE_ARCH) } - exec { .\scripts\test.bat --tfs "Unit Tests" } + - powershell: .\scripts\test.bat --tfs "Unit Tests" displayName: Run unit tests (Electron) timeoutInMinutes: 15 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-node } + - powershell: yarn test-node displayName: Run unit tests (node.js) timeoutInMinutes: 15 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node test/unit/browser/index.js --sequential --browser chromium --browser firefox --tfs "Browser Unit Tests" } - displayName: Run unit tests (Browser, Chromium & Firefox) + - powershell: node test/unit/browser/index.js --sequential --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn electron $(VSCODE_ARCH) } - exec { .\scripts\test.bat --build --tfs "Unit Tests" } + - powershell: .\scripts\test.bat --build --tfs "Unit Tests" displayName: Run unit tests (Electron) timeoutInMinutes: 15 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-node --build } + - powershell: yarn test-node --build displayName: Run unit tests (node.js) timeoutInMinutes: 15 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn test-browser-no-install --sequential --build --browser chromium --browser firefox --tfs "Browser Unit Tests" } - displayName: Run unit tests (Browser, Chromium & Firefox) + - powershell: yarn test-browser-no-install --sequential --build --browser chromium --tfs "Browser Unit Tests" + displayName: Run unit tests (Browser, Chromium) timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}: @@ -83,30 +61,20 @@ steps: compile-extension:typescript-language-features ` compile-extension:vscode-api-tests ` compile-extension:vscode-colorize-tests ` - compile-extension:vscode-notebook-tests ` compile-extension:vscode-test-resolver ` } displayName: Build integration tests - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { .\scripts\test-integration.bat --tfs "Integration Tests" } + - powershell: .\scripts\test-integration.bat --tfs "Integration Tests" displayName: Run integration tests (Electron) timeoutInMinutes: 20 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { .\scripts\test-web-integration.bat --browser firefox } + - powershell: .\scripts\test-web-integration.bat --browser firefox displayName: Run integration tests (Browser, Firefox) timeoutInMinutes: 20 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { .\scripts\test-remote-integration.bat } + - powershell: .\scripts\test-remote-integration.bat displayName: Run integration tests (Remote) timeoutInMinutes: 20 @@ -142,57 +110,44 @@ steps: timeoutInMinutes: 20 - ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - exec {.\build\azure-pipelines\win32\listprocesses.bat } + - powershell: .\build\azure-pipelines\win32\listprocesses.bat displayName: Diagnostics before smoke test run continueOnError: true condition: succeededOrFailed() - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn --cwd test/smoke compile } + - powershell: yarn --cwd test/smoke compile displayName: Compile smoke tests - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn smoketest-no-compile --tracing } + - powershell: yarn gulp compile-extension-media + displayName: Build extensions for smoke tests + + - powershell: yarn smoketest-no-compile --tracing displayName: Run smoke tests (Electron) timeoutInMinutes: 20 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --tracing --build "$AppRoot" } + - powershell: yarn smoketest-no-compile --tracing --build "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" displayName: Run smoke tests (Electron) timeoutInMinutes: 20 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --web --tracing --headless } + - powershell: yarn smoketest-no-compile --web --tracing --headless + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH) displayName: Run smoke tests (Browser, Chromium) timeoutInMinutes: 20 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" - exec { yarn gulp compile-extension:vscode-test-resolver } - exec { yarn smoketest-no-compile --tracing --remote --build "$AppRoot" } + - powershell: yarn gulp compile-extension:vscode-test-resolver + displayName: Compile test resolver extension + timeoutInMinutes: 20 + + - powershell: yarn smoketest-no-compile --tracing --remote --build "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH) displayName: Run smoke tests (Remote) timeoutInMinutes: 20 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - exec {.\build\azure-pipelines\win32\listprocesses.bat } + - powershell: .\build\azure-pipelines\win32\listprocesses.bat displayName: Diagnostics after smoke test run continueOnError: true condition: succeededOrFailed() diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 41f0a8da8c..ab985554bb 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -1,8 +1,8 @@ parameters: - - name: VSCODE_PUBLISH - type: boolean - name: VSCODE_QUALITY type: string + - name: VSCODE_CIBUILD + type: boolean - name: VSCODE_RUN_UNIT_TESTS type: boolean - name: VSCODE_RUN_INTEGRATION_TESTS @@ -26,89 +26,84 @@ steps: addToPath: true - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - template: ../distro/download-distro.yml + - task: AzureKeyVault@1 displayName: "Azure Key Vault: Get Secrets" inputs: azureSubscription: "vscode-builds-subscription" - KeyVaultName: vscode + KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password" - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: DownloadPipelineArtifact@2 inputs: artifact: Compilation path: $(Build.ArtifactStagingDirectory) displayName: Download compilation output - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - task: ExtractFiles@1 displayName: Extract compilation output inputs: archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz" cleanDestinationFolder: false - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - 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: node build/setup-npm-registry.js $env:NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - exec { git config user.email "vscode@microsoft.com" } - exec { git config user.name "VSCode" } - displayName: Prepare tooling - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - - exec { git fetch https://github.com/$(VSCODE_MIXIN_REPO).git $(VSCODE_DISTRO_REF) } - Write-Host "##vso[task.setvariable variable=VSCODE_DISTRO_COMMIT;]$(git rev-parse FETCH_HEAD)" - exec { git checkout FETCH_HEAD } - condition: and(succeeded(), ne(variables.VSCODE_DISTRO_REF, ' ')) - displayName: Checkout override commit - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") } - displayName: Merge distro - - - powershell: | - if (!(Test-Path ".build")) { New-Item -Path ".build" -ItemType Directory } - "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch - "$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin - node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash - displayName: Prepare yarn cache flags + - pwsh: | + mkdir .build -ea 0 + node build/azure-pipelines/common/computeNodeModulesCacheKey.js win32 $(VSCODE_ARCH) > .build/yarnlockhash + displayName: Prepare node_modules cache key - task: Cache@2 inputs: - key: "nodeModules | $(Agent.OS) | .build/arch, .build/terrapin, .build/yarnlockhash" + key: '"node_modules" | .build/yarnlockhash' path: .build/node_modules_cache cacheHitVar: NODE_MODULES_RESTORED displayName: Restore node_modules cache - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { 7z.exe x .build/node_modules_cache/cache.7z -aos } + - powershell: 7z.exe x .build/node_modules_cache/cache.7z -aoa condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) displayName: Extract node_modules cache - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - exec { npx https://aka.ms/enablesecurefeed standAlone } - timeoutInMinutes: 5 - retryCountOnTaskFailure: 3 - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['ENABLE_TERRAPIN'], 'true')) - displayName: Switch to Terrapin packages + exec { npm config set registry "$env:NPM_REGISTRY" --location=project } + exec { npm config set always-auth=true --location=project } + exec { yarn config set registry "$env:NPM_REGISTRY" } + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM & Yarn + + - task: npmAuthenticate@0 + inputs: + workingFile: .npmrc + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - powershell: | + mkdir -Force .build/node-gyp + displayName: Create custom node-gyp directory + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - powershell: | + . ../../build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + # TODO: Should be replaced with upstream URL once https://github.com/nodejs/node-gyp/pull/2825 + # gets merged. + exec { git clone https://github.com/rzhao271/node-gyp.git . } "Cloning rzhao271/node-gyp failed" + exec { git checkout 102b347da0c92c29f9c67df22e864e70249cf086 } "Checking out 102b347 failed" + exec { npm install } "Building rzhao271/node-gyp failed" + displayName: Install custom node-gyp + workingDirectory: .build/node-gyp + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - powershell: | . build/azure-pipelines/win32/exec.ps1 . build/azure-pipelines/win32/retry.ps1 $ErrorActionPreference = "Stop" + $env:npm_config_node_gyp="$(Join-Path $pwd.Path '.build/node-gyp/bin/node-gyp.js')" $env:npm_config_arch="$(VSCODE_ARCH)" $env:CHILD_CONCURRENCY="1" retry { exec { yarn --frozen-lockfile --check-files } } @@ -119,6 +114,11 @@ steps: displayName: Install dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: node build/azure-pipelines/distro/mixin-npm + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" @@ -129,61 +129,50 @@ steps: displayName: Create node_modules archive - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/mixin } - displayName: Mix in quality + - powershell: node build/azure-pipelines/distro/mixin-quality + displayName: Mixin distro quality - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build\lib\policies } + - template: ../common/install-builtin-extensions.yml + + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: node build\lib\policies displayName: Generate Group Policy definitions + retryCountOnTaskFailure: 3 - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn gulp "transpile-client" "transpile-extensions" } + - powershell: yarn gulp "transpile-client-swc" "transpile-extensions" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Transpile - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - ${{ else }}: + - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: + - powershell: node build/win32/explorer-appx-fetcher .build/win32/appx + displayName: Download Explorer Sparse Package + - 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" } echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Build - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" - exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-inno-updater" } - displayName: Prepare Package + - powershell: yarn gulp "vscode-win32-$(VSCODE_ARCH)-inno-updater" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Prepare Setup Package - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - powershell: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/mixin --server } - displayName: Mix in quality - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - 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 + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build Servers condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}: @@ -194,110 +183,108 @@ steps: VSCODE_RUN_INTEGRATION_TESTS: ${{ parameters.VSCODE_RUN_INTEGRATION_TESTS }} VSCODE_RUN_SMOKE_TESTS: ${{ parameters.VSCODE_RUN_SMOKE_TESTS }} - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - task: UseDotNet@2 - inputs: - version: 3.x - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + patterns: "**" + path: $(Build.ArtifactStagingDirectory)/cli + displayName: Download VS Code CLI - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - task: EsrpClientTool@1 - displayName: Download ESRPClient + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $ArtifactName = (gci -Path "$(Build.ArtifactStagingDirectory)/cli" | Select-Object -last 1).FullName + Expand-Archive -Path $ArtifactName -DestinationPath "$(Build.ArtifactStagingDirectory)/cli" + $AppProductJson = Get-Content -Raw -Path "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)\resources\app\product.json" | ConvertFrom-Json + $CliAppName = $AppProductJson.tunnelApplicationName + $AppName = $AppProductJson.applicationName + Move-Item -Path "$(Build.ArtifactStagingDirectory)/cli/$AppName.exe" -Destination "$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)/bin/$CliAppName.exe" + displayName: Move VS Code CLI - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpClientTool = (gci -directory -filter EsrpClientTool_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName - $EsrpCliZip = (gci -recurse -filter esrpcli.*.zip $EsrpClientTool | Select-Object -last 1).FullName - mkdir -p $(Agent.TempDirectory)\esrpcli - Expand-Archive -Path $EsrpCliZip -DestinationPath $(Agent.TempDirectory)\esrpcli - $EsrpCliDllPath = (gci -recurse -filter esrpcli.dll $(Agent.TempDirectory)\esrpcli | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$EsrpCliDllPath" - displayName: Find ESRP CLI + - task: UseDotNet@2 + inputs: + version: 6.x - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build\azure-pipelines\common\sign $env:EsrpCliDllPath windows $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.dll,*.exe,*.node' } - displayName: Codesign + - task: EsrpClientTool@1 + displayName: Download ESRPClient - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-archive" } - displayName: Package archive + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpClientTool = (gci -directory -filter EsrpClientTool_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName + $EsrpCliZip = (gci -recurse -filter esrpcli.*.zip $EsrpClientTool | Select-Object -last 1).FullName + mkdir -p $(Agent.TempDirectory)\esrpcli + Expand-Archive -Path $EsrpCliZip -DestinationPath $(Agent.TempDirectory)\esrpcli + $EsrpCliDllPath = (gci -recurse -filter esrpcli.dll $(Agent.TempDirectory)\esrpcli | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$EsrpCliDllPath" + displayName: Find ESRP CLI - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:ESRPPKI = "$(ESRP-PKI)" - $env:ESRPAADUsername = "$(esrp-aad-username)" - $env:ESRPAADPassword = "$(esrp-aad-password)" - exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-system-setup" --sign } - exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-user-setup" --sign } - displayName: Package setups + - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath windows $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.dll,*.exe,*.node' + displayName: Codesign - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - .\build\azure-pipelines\win32\prepare-publish.ps1 - displayName: Publish + - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: + - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath windows-appx $(ESRP-PKI) $(esrp-aad-username) $(esrp-aad-password) $(CodeSigningFolderPath) '*.appx' + displayName: Codesign context menu appx package - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 - displayName: Generate SBOM (client) - inputs: - BuildDropPath: $(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH) - PackageName: Visual Studio Code + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - powershell: yarn gulp "vscode-win32-$(VSCODE_ARCH)-archive" + displayName: Package archive - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - publish: $(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)/_manifest - displayName: Publish SBOM (client) - artifact: vscode_client_win32_$(VSCODE_ARCH)_sbom + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:ESRPPKI = "$(ESRP-PKI)" + $env:ESRPAADUsername = "$(esrp-aad-username)" + $env:ESRPAADPassword = "$(esrp-aad-password)" + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-system-setup" --sign } + exec { yarn gulp "vscode-win32-$(VSCODE_ARCH)-user-setup" --sign } + displayName: Package setups - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 - displayName: Generate SBOM (server) - inputs: - BuildDropPath: $(agent.builddirectory)/vscode-server-win32-$(VSCODE_ARCH) - PackageName: Visual Studio Code Server - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + - powershell: .\build\azure-pipelines\win32\prepare-publish.ps1 + displayName: Publish - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - publish: $(agent.builddirectory)/vscode-server-win32-$(VSCODE_ARCH)/_manifest - displayName: Publish SBOM (server) - artifact: vscode_server_win32_$(VSCODE_ARCH)_sbom - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: Generate SBOM (client) + inputs: + BuildDropPath: $(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH) + PackageName: Visual Studio Code - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - publish: $(System.DefaultWorkingDirectory)\.build\win32-$(VSCODE_ARCH)\archive\$(ARCHIVE_NAME) - artifact: vscode_client_win32_$(VSCODE_ARCH)_archive - displayName: Publish archive + - publish: $(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)/_manifest + displayName: Publish SBOM (client) + artifact: vscode_client_win32_$(VSCODE_ARCH)_sbom - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - publish: $(System.DefaultWorkingDirectory)\.build\win32-$(VSCODE_ARCH)\system-setup\$(SYSTEM_SETUP_NAME) - artifact: vscode_client_win32_$(VSCODE_ARCH)_setup - displayName: Publish system setup + - task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0 + displayName: Generate SBOM (server) + inputs: + BuildDropPath: $(agent.builddirectory)/vscode-server-win32-$(VSCODE_ARCH) + PackageName: Visual Studio Code Server + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - publish: $(System.DefaultWorkingDirectory)\.build\win32-$(VSCODE_ARCH)\user-setup\$(USER_SETUP_NAME) - artifact: vscode_client_win32_$(VSCODE_ARCH)_user-setup - displayName: Publish user setup - condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false')) + - publish: $(agent.builddirectory)/vscode-server-win32-$(VSCODE_ARCH)/_manifest + displayName: Publish SBOM (server) + artifact: vscode_server_win32_$(VSCODE_ARCH)_sbom + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - publish: $(System.DefaultWorkingDirectory)\.build\vscode-server-win32-$(VSCODE_ARCH).zip - artifact: vscode_server_win32_$(VSCODE_ARCH)_archive - displayName: Publish server archive - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + - publish: $(System.DefaultWorkingDirectory)\.build\win32-$(VSCODE_ARCH)\archive\$(ARCHIVE_NAME) + artifact: vscode_client_win32_$(VSCODE_ARCH)_archive + displayName: Publish archive - - ${{ if eq(parameters.VSCODE_PUBLISH, true) }}: - - publish: $(System.DefaultWorkingDirectory)\.build\vscode-server-win32-$(VSCODE_ARCH)-web.zip - artifact: vscode_web_win32_$(VSCODE_ARCH)_archive - displayName: Publish web server archive - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + - publish: $(System.DefaultWorkingDirectory)\.build\win32-$(VSCODE_ARCH)\system-setup\$(SYSTEM_SETUP_NAME) + artifact: vscode_client_win32_$(VSCODE_ARCH)_setup + displayName: Publish system setup + + - publish: $(System.DefaultWorkingDirectory)\.build\win32-$(VSCODE_ARCH)\user-setup\$(USER_SETUP_NAME) + artifact: vscode_client_win32_$(VSCODE_ARCH)_user-setup + displayName: Publish user setup + + - publish: $(System.DefaultWorkingDirectory)\.build\vscode-server-win32-$(VSCODE_ARCH).zip + artifact: vscode_server_win32_$(VSCODE_ARCH)_archive + displayName: Publish server archive + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) + + - publish: $(System.DefaultWorkingDirectory)\.build\vscode-server-win32-$(VSCODE_ARCH)-web.zip + artifact: vscode_web_win32_$(VSCODE_ARCH)_archive + displayName: Publish web server archive + condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) diff --git a/build/azure-pipelines/win32/retry.ps1 b/build/azure-pipelines/win32/retry.ps1 index 002a5e274b..0cc67f58c0 100644 --- a/build/azure-pipelines/win32/retry.ps1 +++ b/build/azure-pipelines/win32/retry.ps1 @@ -6,7 +6,7 @@ function Retry ) $retry = 0 - while ($retry++ -lt 3) { + while ($retry++ -lt 5) { try { & $cmd return diff --git a/build/builtin/index.html b/build/builtin/index.html index 13c84e0375..dc2a7ca6d0 100644 --- a/build/builtin/index.html +++ b/build/builtin/index.html @@ -5,7 +5,6 @@ - Manage Built-in Extensions @@ -43,4 +42,4 @@
- \ No newline at end of file + diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js index 24476a1fbd..81f11812f6 100644 --- a/build/darwin/create-universal-app.js +++ b/build/darwin/create-universal-app.js @@ -4,19 +4,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +const path = require("path"); +const fs = require("fs-extra"); // {{SQL CARBON EDIT}} - use fs-extra instead of fs const vscode_universal_bundler_1 = require("vscode-universal-bundler"); const cross_spawn_promise_1 = require("@malept/cross-spawn-promise"); -const fs = require("fs-extra"); -const path = require("path"); -const product = require("../../product.json"); const glob = require("glob"); // {{SQL CARBON EDIT}} -async function main() { - const buildDir = process.env['AGENT_BUILDDIRECTORY']; +const root = path.dirname(path.dirname(__dirname)); +async function main(buildDir) { const arch = process.env['VSCODE_ARCH']; if (!buildDir) { - throw new Error('$AGENT_BUILDDIRECTORY not set'); + throw new Error('Build dir not provided'); } - // {{SQL CARBON EDIT}} + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const x64AppNameBase = 'azuredatastudio-darwin-x64'; const arm64AppNameBase = 'azuredatastudio-darwin-arm64'; // {{SQL CARBON EDIT}} - END @@ -71,11 +70,11 @@ async function main() { outAppPath, force: true }); - const productJson = await fs.readJson(productJsonPath); + const productJson = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); Object.assign(productJson, { darwinUniversalAssetId: 'darwin-universal' }); - await fs.writeJson(productJsonPath, productJson); + fs.writeFileSync(productJsonPath, JSON.stringify(productJson, null, '\t')); // Verify if native module architecture is correct // {{SQL CARBON EDIT}} Some of our extensions have their own keytar so lookup // only in core modules since this code doesn't work with multiple found modules. @@ -92,8 +91,9 @@ async function main() { await fs.copy(path.join(tempSTSDir, 'OSX_ARM64'), path.join(outAppPath, stsPath, 'OSX_ARM64'), { overwrite: true }); } if (require.main === module) { - main().catch(err => { + main(process.argv[2]).catch(err => { console.error(err); process.exit(1); }); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLXVuaXZlcnNhbC1hcHAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjcmVhdGUtdW5pdmVyc2FsLWFwcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLDZCQUE2QjtBQUM3QiwrQkFBK0IsQ0FBQyxtREFBbUQ7QUFDbkYsdUVBQTREO0FBQzVELHFFQUFvRDtBQUNwRCw2QkFBNkIsQ0FBQyxzQkFBc0I7QUFFcEQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFFbkQsS0FBSyxVQUFVLElBQUksQ0FBQyxRQUFpQjtJQUNwQyxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBRXhDLElBQUksQ0FBQyxRQUFRLEVBQUU7UUFDZCxNQUFNLElBQUksS0FBSyxDQUFDLHdCQUF3QixDQUFDLENBQUM7S0FDMUM7SUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztJQUNyRixNQUFNLGNBQWMsR0FBRyw0QkFBNEIsQ0FBQztJQUNwRCxNQUFNLGdCQUFnQixHQUFHLDhCQUE4QixDQUFDO0lBQ3hELDRCQUE0QjtJQUU1QixNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsUUFBUSxHQUFHLE1BQU0sQ0FBQztJQUMxQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxjQUFjLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyx5REFBeUQ7SUFDMUgsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsZ0JBQWdCLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyx5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 \ No newline at end of file diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 56fc1722db..5e36207f36 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -3,22 +3,22 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'path'; +import * as fs from 'fs-extra'; // {{SQL CARBON EDIT}} - use fs-extra instead of fs import { makeUniversalApp } from 'vscode-universal-bundler'; import { spawn } from '@malept/cross-spawn-promise'; -import * as fs from 'fs-extra'; -import * as path from 'path'; -import * as product from '../../product.json'; import * as glob from 'glob'; // {{SQL CARBON EDIT}} -async function main() { - const buildDir = process.env['AGENT_BUILDDIRECTORY']; +const root = path.dirname(path.dirname(__dirname)); + +async function main(buildDir?: string) { const arch = process.env['VSCODE_ARCH']; if (!buildDir) { - throw new Error('$AGENT_BUILDDIRECTORY not set'); + throw new Error('Build dir not provided'); } - // {{SQL CARBON EDIT}} + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const x64AppNameBase = 'azuredatastudio-darwin-x64'; const arm64AppNameBase = 'azuredatastudio-darwin-arm64'; // {{SQL CARBON EDIT}} - END @@ -78,11 +78,11 @@ async function main() { force: true }); - const productJson = await fs.readJson(productJsonPath); + const productJson = JSON.parse(fs.readFileSync(productJsonPath, 'utf8')); Object.assign(productJson, { darwinUniversalAssetId: 'darwin-universal' }); - await fs.writeJson(productJsonPath, productJson); + fs.writeFileSync(productJsonPath, JSON.stringify(productJson, null, '\t')); // Verify if native module architecture is correct // {{SQL CARBON EDIT}} Some of our extensions have their own keytar so lookup @@ -102,7 +102,7 @@ async function main() { } if (require.main === module) { - main().catch(err => { + main(process.argv[2]).catch(err => { console.error(err); process.exit(1); }); diff --git a/build/darwin/sign.js b/build/darwin/sign.js index 243b8d9b07..5879d1a0f9 100644 --- a/build/darwin/sign.js +++ b/build/darwin/sign.js @@ -4,21 +4,27 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -const codesign = require("electron-osx-sign"); +const fs = require("fs"); const path = require("path"); -const util = require("../lib/util"); -const product = require("../../product.json"); +const codesign = require("electron-osx-sign"); const cross_spawn_promise_1 = require("@malept/cross-spawn-promise"); -async function main() { - const buildDir = process.env['AGENT_BUILDDIRECTORY']; +const root = path.dirname(path.dirname(__dirname)); +function getElectronVersion() { + const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); + const target = /^target "(.*)"$/m.exec(yarnrc)[1]; + return target; +} +async function main(buildDir) { const tempDir = process.env['AGENT_TEMPDIRECTORY']; const arch = process.env['VSCODE_ARCH']; + const identity = process.env['CODESIGN_IDENTITY']; if (!buildDir) { throw new Error('$AGENT_BUILDDIRECTORY not set'); } if (!tempDir) { throw new Error('$AGENT_TEMPDIRECTORY not set'); } + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const baseDir = path.dirname(__dirname); const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`); const appName = product.nameLong + '.app'; @@ -26,6 +32,7 @@ async function main() { const helperAppBaseName = product.nameShort; const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app'; const rendererHelperAppName = helperAppBaseName + ' Helper (Renderer).app'; + const pluginHelperAppName = helperAppBaseName + ' Helper (Plugin).app'; const infoPlistPath = path.resolve(appRoot, appName, 'Contents', 'Info.plist'); const defaultOpts = { app: path.join(appRoot, appName), @@ -36,8 +43,8 @@ async function main() { 'pre-auto-entitlements': false, 'pre-embed-provisioning-profile': false, keychain: path.join(tempDir, 'buildagent.keychain'), - version: util.getElectronVersion(), - identity: '99FM488X57', + version: getElectronVersion(), + identity, 'gatekeeper-assess': false }; const appOpts = { @@ -45,7 +52,8 @@ async function main() { // TODO(deepak1556): Incorrectly declared type in electron-osx-sign ignore: (filePath) => { return filePath.includes(gpuHelperAppName) || - filePath.includes(rendererHelperAppName); + filePath.includes(rendererHelperAppName) || + filePath.includes(pluginHelperAppName); } }; const gpuHelperOpts = { @@ -60,6 +68,12 @@ async function main() { entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), }; + const pluginHelperOpts = { + ...defaultOpts, + app: path.join(appFrameworkPath, pluginHelperAppName), + entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'), + 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'), + }; // Only overwrite plist entries for x64 and arm64 builds, // universal will get its copy from the x64 build. if (arch !== 'universal') { @@ -87,11 +101,13 @@ async function main() { } await codesign.signAsync(gpuHelperOpts); await codesign.signAsync(rendererHelperOpts); + await codesign.signAsync(pluginHelperOpts); await codesign.signAsync(appOpts); } if (require.main === module) { - main().catch(err => { + main(process.argv[2]).catch(err => { console.error(err); process.exit(1); }); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2lnbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInNpZ24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLDhDQUE4QztBQUM5QyxxRUFBb0Q7QUFFcEQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFFbkQsU0FBUyxrQkFBa0I7SUFDMUIsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxTQUFTLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNuRSxNQUFNLE1BQU0sR0FBRyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDbkQsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDO0FBRUQsS0FBSyxVQUFVLElBQUksQ0FBQyxRQUFpQjtJQUNwQyxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLHFCQUFxQixDQUFDLENBQUM7SUFDbkQsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUN4QyxNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLG1CQUFtQixDQUFDLENBQUM7SUFFbEQsSUFBSSxDQUFDLFFBQVEsRUFBRTtRQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsK0JBQStCLENBQUMsQ0FBQztLQUNqRDtJQUVELElBQUksQ0FBQyxPQUFPLEVBQUU7UUFDYixNQUFNLElBQUksS0FBSyxDQUFDLDhCQUE4QixDQUFDLENBQUM7S0FDaEQ7SUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVB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 \ No newline at end of file diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index 03bc801677..189646dec4 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -3,16 +3,23 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as codesign from 'electron-osx-sign'; +import * as fs from 'fs'; import * as path from 'path'; -import * as util from '../lib/util'; -import * as product from '../../product.json'; +import * as codesign from 'electron-osx-sign'; import { spawn } from '@malept/cross-spawn-promise'; -async function main(): Promise { - const buildDir = process.env['AGENT_BUILDDIRECTORY']; +const root = path.dirname(path.dirname(__dirname)); + +function getElectronVersion(): string { + const yarnrc = fs.readFileSync(path.join(root, '.yarnrc'), 'utf8'); + const target = /^target "(.*)"$/m.exec(yarnrc)![1]; + return target; +} + +async function main(buildDir?: string): Promise { const tempDir = process.env['AGENT_TEMPDIRECTORY']; const arch = process.env['VSCODE_ARCH']; + const identity = process.env['CODESIGN_IDENTITY']; if (!buildDir) { throw new Error('$AGENT_BUILDDIRECTORY not set'); @@ -22,6 +29,7 @@ async function main(): Promise { throw new Error('$AGENT_TEMPDIRECTORY not set'); } + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const baseDir = path.dirname(__dirname); const appRoot = path.join(buildDir, `VSCode-darwin-${arch}`); const appName = product.nameLong + '.app'; @@ -29,6 +37,7 @@ async function main(): Promise { const helperAppBaseName = product.nameShort; const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app'; const rendererHelperAppName = helperAppBaseName + ' Helper (Renderer).app'; + const pluginHelperAppName = helperAppBaseName + ' Helper (Plugin).app'; const infoPlistPath = path.resolve(appRoot, appName, 'Contents', 'Info.plist'); const defaultOpts: codesign.SignOptions = { @@ -40,8 +49,8 @@ async function main(): Promise { 'pre-auto-entitlements': false, 'pre-embed-provisioning-profile': false, keychain: path.join(tempDir, 'buildagent.keychain'), - version: util.getElectronVersion(), - identity: '99FM488X57', + version: getElectronVersion(), + identity, 'gatekeeper-assess': false }; @@ -50,7 +59,8 @@ async function main(): Promise { // TODO(deepak1556): Incorrectly declared type in electron-osx-sign ignore: (filePath: string) => { return filePath.includes(gpuHelperAppName) || - filePath.includes(rendererHelperAppName); + filePath.includes(rendererHelperAppName) || + filePath.includes(pluginHelperAppName); } }; @@ -68,6 +78,13 @@ async function main(): Promise { 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'), }; + const pluginHelperOpts: codesign.SignOptions = { + ...defaultOpts, + app: path.join(appFrameworkPath, pluginHelperAppName), + entitlements: path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'), + 'entitlements-inherit': path.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'), + }; + // Only overwrite plist entries for x64 and arm64 builds, // universal will get its copy from the x64 build. if (arch !== 'universal') { @@ -96,11 +113,12 @@ async function main(): Promise { await codesign.signAsync(gpuHelperOpts); await codesign.signAsync(rendererHelperOpts); + await codesign.signAsync(pluginHelperOpts); await codesign.signAsync(appOpts as any); } if (require.main === module) { - main().catch(err => { + main(process.argv[2]).catch(err => { console.error(err); process.exit(1); }); diff --git a/build/eslint.js b/build/eslint.js index 81c4305834..a553eedb8e 100644 --- a/build/eslint.js +++ b/build/eslint.js @@ -13,8 +13,7 @@ function eslint() { .src(eslintFilter, { base: '.', follow: true, allowEmpty: true }) .pipe( gulpeslint({ - configFile: '.eslintrc.json', - rulePaths: ['./build/lib/eslint'], + configFile: '.eslintrc.json' }) ) .pipe(gulpeslint.formatEach('compact')) diff --git a/build/filters.js b/build/filters.js index 0ac2d907a2..ed11bbfb7e 100644 --- a/build/filters.js +++ b/build/filters.js @@ -22,6 +22,7 @@ module.exports.all = [ 'scripts/**/*', 'src/**/*', 'test/**/*', + '!cli/**/*', '!out*/**', '!test/**/out/**', '!**/node_modules/**', @@ -43,13 +44,14 @@ module.exports.unicodeFilter = [ '!**/test/**', '!**/*.test.ts', '!**/*.{d.ts,json,md}', + '!**/*.mp3', '!build/win32/**', '!extensions/markdown-language-features/notebook-out/*.js', '!extensions/markdown-math/notebook-out/**', + '!extensions/ipynb/notebook-out/**', '!extensions/notebook-renderers/renderer-out/**', '!extensions/php-language-features/src/features/phpGlobalFunctions.ts', - '!extensions/typescript-language-features/test-workspace/**', '!extensions/vscode-api-tests/testWorkspace/**', '!extensions/vscode-api-tests/testWorkspace2/**', '!extensions/**/dist/**', @@ -70,6 +72,7 @@ module.exports.indentationFilter = [ '!**/LICENSE.{txt,rtf}', '!LICENSES.chromium.html', '!**/LICENSE', + '!**/*.mp3', '!src/vs/nls.js', '!src/vs/nls.build.js', '!src/vs/css.js', @@ -88,8 +91,6 @@ module.exports.indentationFilter = [ '!test/automation/out/**', '!test/monaco/out/**', '!test/smoke/out/**', - '!extensions/typescript-language-features/test-workspace/**', - '!extensions/typescript-language-features/resources/walkthroughs/**', '!extensions/markdown-math/notebook-out/**', '!extensions/ipynb/notebook-out/**', '!extensions/vscode-api-tests/testWorkspace/**', @@ -131,6 +132,7 @@ module.exports.indentationFilter = [ '!extensions/markdown-language-features/media/*.js', '!extensions/markdown-language-features/notebook-out/*.js', '!extensions/markdown-math/notebook-out/*.js', + '!extensions/ipynb/notebook-out/**', '!extensions/notebook-renderers/renderer-out/*.js', '!extensions/simple-browser/media/*.js', @@ -171,10 +173,12 @@ module.exports.copyrightFilter = [ '!**/*.cmd', '!**/*.ico', '!**/*.opus', + '!**/*.mp3', '!**/*.icns', '!**/*.xml', '!**/*.sh', '!**/*.zsh', + '!**/*.fish', '!**/*.txt', '!**/*.xpm', '!**/*.opts', @@ -189,6 +193,7 @@ module.exports.copyrightFilter = [ '!extensions/configuration-editing/build/inline-allOf.ts', '!extensions/markdown-language-features/media/highlight.css', '!extensions/markdown-math/notebook-out/**', + '!extensions/ipynb/notebook-out/**', '!extensions/html-language-features/server/src/modes/typescript/*', '!extensions/*/server/bin/*', '!src/vs/editor/test/node/classification/typescript-test.ts', @@ -266,3 +271,7 @@ module.exports.eslintFilter = [ .filter(line => !!line) .map(line => `!${line}`) ]; + +module.exports.stylelintFilter = [ + 'src/**/*.css' +]; diff --git a/build/gulpfile.compile.js b/build/gulpfile.compile.js index 604fbbca9e..e5a25ffecb 100644 --- a/build/gulpfile.compile.js +++ b/build/gulpfile.compile.js @@ -9,13 +9,16 @@ const gulp = require('gulp'); const util = require('./lib/util'); const task = require('./lib/task'); const compilation = require('./lib/compilation'); +const optimize = require('./lib/optimize'); // Full compile, including nls and inline sources in sourcemaps, for build const compileBuildTask = task.define('compile-build', task.series( util.rimraf('out-build'), util.buildWebNodePaths('out-build'), - compilation.compileTask('src', 'out-build', true) + compilation.compileApiProposalNamesTask, + compilation.compileTask('src', 'out-build', true), + optimize.optimizeLoaderTask('out-build', 'out-build', true) ) ); gulp.task(compileBuildTask); diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 9e2759c05d..613d02e795 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -6,8 +6,9 @@ const gulp = require('gulp'); const path = require('path'); const util = require('./lib/util'); +const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); -const common = require('./lib/optimize'); +const optimize = require('./lib/optimize'); const es = require('event-stream'); const File = require('vinyl'); const i18n = require('./lib/i18n'); @@ -18,7 +19,7 @@ const monacoapi = require('./lib/monaco-api'); const fs = require('fs'); const root = path.dirname(__dirname); -const sha1 = util.getVersion(root); +const sha1 = getVersion(root); const semver = require('./monaco/package.json').version; const headerVersion = semver + '(' + sha1 + ')'; @@ -84,28 +85,32 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { }); }); -const compileEditorAMDTask = task.define('compile-editor-amd', compilation.compileTask('out-editor-src', 'out-editor-build', true)); +// Disable mangling for the editor, as it complicates debugging & quite a few users rely on private/protected fields. +const compileEditorAMDTask = task.define('compile-editor-amd', compilation.compileTask('out-editor-src', 'out-editor-build', true, { disableMangle: true })); -const optimizeEditorAMDTask = task.define('optimize-editor-amd', common.optimizeTask({ - src: 'out-editor-build', - entryPoints: editorEntryPoints, - resources: editorResources, - loaderConfig: { - paths: { - 'vs': 'out-editor-build/vs', - 'vs/css': 'out-editor-build/vs/css.build', - 'vs/nls': 'out-editor-build/vs/nls.build', - 'vscode': 'empty:' +const optimizeEditorAMDTask = task.define('optimize-editor-amd', optimize.optimizeTask( + { + out: 'out-editor', + amd: { + src: 'out-editor-build', + entryPoints: editorEntryPoints, + resources: editorResources, + loaderConfig: { + paths: { + 'vs': 'out-editor-build/vs', + 'vs/css': 'out-editor-build/vs/css.build', + 'vs/nls': 'out-editor-build/vs/nls.build', + 'vscode': 'empty:' + } + }, + header: BUNDLED_FILE_HEADER, + bundleInfo: true, + languages } - }, - bundleLoader: false, - header: BUNDLED_FILE_HEADER, - bundleInfo: true, - out: 'out-editor', - languages: languages -})); + } +)); -const minifyEditorAMDTask = task.define('minify-editor-amd', common.minifyTask('out-editor')); +const minifyEditorAMDTask = task.define('minify-editor-amd', optimize.minifyTask('out-editor')); const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => { standalone.createESMSourcesAndResources2({ diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index f161c88632..71f35088ef 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -12,15 +12,15 @@ const nodeUtil = require('util'); const es = require('event-stream'); const filter = require('gulp-filter'); const util = require('./lib/util'); +const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); const watcher = require('./lib/watch'); const createReporter = require('./lib/reporter').createReporter; const glob = require('glob'); const root = path.dirname(__dirname); -const commit = util.getVersion(root); +const commit = getVersion(root); const plumber = require('gulp-plumber'); const ext = require('./lib/extensions'); -const product = require('../product.json'); const extensionsPath = path.join(path.dirname(__dirname), 'extensions'); @@ -50,7 +50,6 @@ const compilations = [ 'gulp/tsconfig.json', 'html-language-features/client/tsconfig.json', 'html-language-features/server/tsconfig.json', - 'image-preview/tsconfig.json', 'ipynb/tsconfig.json', 'jake/tsconfig.json', 'json-language-features/client/tsconfig.json', @@ -59,18 +58,17 @@ const compilations = [ 'markdown-language-features/server/tsconfig.json', 'markdown-language-features/tsconfig.json', 'markdown-math/tsconfig.json', + 'media-preview/tsconfig.json', 'merge-conflict/tsconfig.json', 'microsoft-authentication/tsconfig.json', + 'notebook-renderers/tsconfig.json', 'npm/tsconfig.json', 'php-language-features/tsconfig.json', 'search-result/tsconfig.json', 'references-view/tsconfig.json', 'simple-browser/tsconfig.json', - 'typescript-language-features/test-workspace/tsconfig.json', - 'typescript-language-features/tsconfig.json', 'vscode-api-tests/tsconfig.json', 'vscode-colorize-tests/tsconfig.json', - 'vscode-notebook-tests/tsconfig.json', 'vscode-test-resolver/tsconfig.json' ]; */ @@ -114,7 +112,7 @@ const tasks = compilations.map(function (tsconfigFile) { overrideOptions.inlineSources = Boolean(build); overrideOptions.base = path.dirname(absolutePath); - const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false, transpileOnly, transpileOnlyIncludesDts: transpileOnly }, err => reporter(err.toString())); + const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false, transpileOnly, transpileOnlyIncludesDts: transpileOnly, transpileWithSwc: true }, err => reporter(err.toString())); const pipeline = function () { const input = es.through(); @@ -240,8 +238,8 @@ exports.compileExtensionMediaBuildTask = compileExtensionMediaBuildTask; const cleanExtensionsBuildTask = task.define('clean-extensions-build', util.rimraf('.build/extensions')); const compileExtensionsBuildTask = task.define('compile-extensions-build', task.series( cleanExtensionsBuildTask, + task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))), task.define('bundle-extensions-build', () => ext.packageLocalExtensionsStream(false).pipe(gulp.dest('.build'))), - task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false, product.extensionsGallery?.serviceUrl).pipe(gulp.dest('.build'))), )); gulp.task(compileExtensionsBuildTask); diff --git a/build/gulpfile.js b/build/gulpfile.js index 06b1268cf7..c0a8bef361 100644 --- a/build/gulpfile.js +++ b/build/gulpfile.js @@ -11,7 +11,7 @@ require('events').EventEmitter.defaultMaxListeners = 100; const gulp = require('gulp'); const util = require('./lib/util'); const task = require('./lib/task'); -const { transpileTask, compileTask, watchTask, compileApiProposalNamesTask, watchApiProposalNamesTask } = require('./lib/compilation'); +const { transpileClientSWC, transpileTask, compileTask, watchTask, compileApiProposalNamesTask, watchApiProposalNamesTask } = require('./lib/compilation'); const { monacoTypecheckTask/* , monacoTypecheckWatchTask */ } = require('./gulpfile.editor'); const { compileExtensionsTask, watchExtensionsTask, compileExtensionMediaTask } = require('./gulpfile.extensions'); @@ -19,6 +19,10 @@ const { compileExtensionsTask, watchExtensionsTask, compileExtensionMediaTask } gulp.task(compileApiProposalNamesTask); gulp.task(watchApiProposalNamesTask); +// SWC Client Transpile +const transpileClientSWCTask = task.define('transpile-client-swc', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), transpileTask('src', 'out', true))); +gulp.task(transpileClientSWCTask); + // Transpile only const transpileClientTask = task.define('transpile-client', task.series(util.rimraf('out'), util.buildWebNodePaths('out'), transpileTask('src', 'out'))); gulp.task(transpileClientTask); diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js index 87fcfe27df..ed4d138ac1 100644 --- a/build/gulpfile.reh.js +++ b/build/gulpfile.reh.js @@ -9,14 +9,15 @@ const gulp = require('gulp'); const path = require('path'); const es = require('event-stream'); const util = require('./lib/util'); +const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); -const common = require('./lib/optimize'); +const optimize = require('./lib/optimize'); const product = require('../product.json'); const rename = require('gulp-rename'); const replace = require('gulp-replace'); const filter = require('gulp-filter'); -const _ = require('underscore'); const { getProductionDependencies } = require('./lib/dependencies'); +const { assetFromGithub } = require('./lib/github'); const vfs = require('vinyl-fs'); const packageJson = require('../package.json'); const flatmap = require('gulp-flatmap'); @@ -28,10 +29,11 @@ const { compileBuildTask } = require('./gulpfile.compile'); const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); const { vscodeWebEntryPoints, vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web'); const cp = require('child_process'); +const log = require('fancy-log'); const { rollupAngular } = require('./lib/rollup'); const REPO_ROOT = path.dirname(__dirname); -const commit = util.getVersion(REPO_ROOT); +const commit = getVersion(REPO_ROOT); const BUILD_ROOT = path.dirname(REPO_ROOT); const REMOTE_FOLDER = path.join(REPO_ROOT, 'remote'); @@ -59,15 +61,10 @@ const serverResources = [ 'out-build/bootstrap-fork.js', 'out-build/bootstrap-amd.js', 'out-build/bootstrap-node.js', - 'out-build/paths.js', // Performance 'out-build/vs/base/common/performance.js', - // main entry points - 'out-build/server-cli.js', - 'out-build/server-main.js', - // Watcher 'out-build/vs/platform/files/**/*.exe', 'out-build/vs/platform/files/**/*.md', @@ -82,6 +79,8 @@ const serverResources = [ 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh', 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh', + 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.fish', + 'out-build/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish', '!**/test/**' ]; @@ -173,7 +172,7 @@ if (defaultNodeTask) { } function nodejs(platform, arch) { - const remote = require('gulp-remote-retry-src'); + const { remote } = require('./lib/gulpRemoteSource'); const untar = require('gulp-untar'); if (arch === 'ia32') { @@ -181,12 +180,19 @@ function nodejs(platform, arch) { } if (platform === 'win32') { - return remote(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org' }) + if (product.nodejsRepository) { + log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`); + return assetFromGithub(product.nodejsRepository, nodeVersion, name => name === `win-${arch}-node-patched.exe`) + .pipe(rename('node.exe')); + } + log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from https://nodejs.org`); + return remote(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org', verbose: true }) .pipe(rename('node.exe')); } if (arch === 'alpine' || platform === 'alpine') { const imageName = arch === 'arm64' ? 'arm64v8/node' : 'node'; + log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from docker image ${imageName}`); const contents = cp.execSync(`docker run --rm ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' }); return es.readArray([new File({ path: 'node', contents, stat: { mode: parseInt('755', 8) } })]); } @@ -194,8 +200,8 @@ function nodejs(platform, arch) { if (arch === 'armhf') { arch = 'armv7l'; } - - return remote(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org' }) + log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from https://nodejs.org`); + return remote(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', verbose: true }) .pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) .pipe(filter('**/node')) .pipe(util.setExecutableBit('**')) @@ -268,316 +274,14 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa const date = new Date().toISOString(); const productJsonStream = gulp.src(['product.json'], { base: '.' }) - .pipe(json({ commit, date })); - - const license = gulp.src(['remote/LICENSE'], { base: 'remote' }); - - const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); - - const productionDependencies = getProductionDependencies(REMOTE_FOLDER); - const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`])); - const deps = gulp.src(dependenciesSrc, { base: 'remote', dot: true }) - // filter out unnecessary files, no source maps in server build - .pipe(filter(['**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) - .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) - .pipe(jsFilter) - .pipe(util.stripSourceMappingURL()) - .pipe(jsFilter.restore); - - const nodePath = `.build/node/v${nodeVersion}/${platform}-${platform === 'darwin' ? 'x64' : arch}`; - const node = gulp.src(`${nodePath}/**`, { base: nodePath, dot: true }); - - let web = []; - if (type === 'reh-web') { - web = [ - 'resources/server/favicon.ico', - 'resources/server/code-192.png', - 'resources/server/code-512.png', - 'resources/server/manifest.json' - ].map(resource => gulp.src(resource, { base: '.' }).pipe(rename(resource))); - } - - let all = es.merge( - packageJsonStream, - productJsonStream, - license, - sources, - deps, - node, - ...web - ); - - let result = all - .pipe(util.skipDirectories()) - .pipe(util.fixWin32DirectoryPermissions()); - - if (platform === 'win32') { - result = es.merge(result, - gulp.src('resources/server/bin/code.cmd', { base: '.' }) - .pipe(replace('@@VERSION@@', version)) - .pipe(replace('@@COMMIT@@', commit)) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename(`bin/${product.applicationName}.cmd`)), - // gulp.src('resources/server/bin/helpers/browser.cmd', { base: '.' }) - // .pipe(replace('@@VERSION@@', version)) - // .pipe(replace('@@COMMIT@@', commit)) - // .pipe(replace('@@APPNAME@@', product.applicationName)) - // .pipe(rename(`bin/helpers/browser.cmd`)), - gulp.src('resources/server/bin/server.cmd', { base: '.' }) - .pipe(rename(`server.cmd`)) - ); - } else if (platform === 'linux' || platform === 'darwin') { - result = es.merge(result, - gulp.src('resources/server/bin/code.sh', { base: '.' }) - .pipe(replace('@@VERSION@@', version)) - .pipe(replace('@@COMMIT@@', commit)) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename(`bin/${product.applicationName}`)) - .pipe(util.setExecutableBit()), - // gulp.src('resources/server/bin/helpers/browser.sh', { base: '.' }) - // .pipe(replace('@@VERSION@@', version)) - // .pipe(replace('@@COMMIT@@', commit)) - // .pipe(replace('@@APPNAME@@', product.applicationName)) - // .pipe(rename(`bin/helpers/browser.sh`)) - // .pipe(util.setExecutableBit()), - gulp.src('resources/server/bin/server.sh', { base: '.' }) - .pipe(rename(`server.sh`)) - .pipe(util.setExecutableBit()) - ); - } - - return result.pipe(vfs.dest(destination)); - }; -} - -function copyConfigTask(folder) { - const destination = path.join(BUILD_ROOT, folder); - return () => { - const json = require('gulp-json-editor'); - - return gulp.src(['remote/pkg-package.json'], { base: 'remote' }) - .pipe(rename(path => path.basename += '.' + folder)) - .pipe(json(obj => { - const pkg = obj.pkg; - pkg.scripts = pkg.scripts && pkg.scripts.map(p => path.join(destination, p)); - pkg.assets = pkg.assets && pkg.assets.map(p => path.join(destination, p)); - return obj; - })) - .pipe(vfs.dest('out-vscode-reh-pkg')); - }; -} - -function copyNativeTask(folder) { - const destination = path.join(BUILD_ROOT, folder); - return () => { - const nativeLibraries = gulp.src(['remote/node_modules/**/*.node']); - const license = gulp.src(['remote/LICENSE']); - - const result = es.merge( - nativeLibraries, - license - ); - - return result - .pipe(rename({ dirname: '' })) - .pipe(vfs.dest(destination)); - }; -} - -function packagePkgTask(platform, arch, pkgTarget) { - const folder = path.join(BUILD_ROOT, 'vscode-reh') + (platform ? '-' + platform : '') + (arch ? '-' + arch : ''); - return () => { - const cwd = process.cwd(); - const config = path.join(cwd, 'out-vscode-reh-pkg', 'pkg-package.vscode-reh-' + platform + '-' + arch + '.json'); - process.chdir(folder); - console.log(`TODO`, pkgTarget, config); - return null; - // return pkg.exec(['-t', pkgTarget, '-d', '-c', config, '-o', path.join(folder + '-pkg', platform === 'win32' ? 'vscode-reh.exe' : 'vscode-reh'), './out/remoteExtensionHostAgent.js']) - // .then(() => process.chdir(cwd)); - }; -} - -['reh', 'reh-web'].forEach(type => { - const optimizeTask = task.define(`optimize-vscode-${type}`, task.series( - util.rimraf(`out-vscode-${type}`), - common.optimizeTask({ - src: 'out-build', - entryPoints: _.flatten(type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints), - otherSources: [], - resources: type === 'reh' ? serverResources : serverWithWebResources, - loaderConfig: common.loaderConfig(), - out: `out-vscode-${type}`, - inlineAmdImages: true, - bundleInfo: undefined, - fileContentMapper: createVSCodeWebFileContentMapper ? createVSCodeWebFileContentMapper('.build/extensions') : undefined - }) - )); - - const minifyTask = task.define(`minify-vscode-${type}`, task.series( - optimizeTask, - util.rimraf(`out-vscode-${type}-min`), - common.minifyTask(`out-vscode-${type}`, `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) - )); - gulp.task(minifyTask); - - BUILD_TARGETS.forEach(buildTarget => { - const dashed = (str) => (str ? `-${str}` : ``); - const platform = buildTarget.platform; - const arch = buildTarget.arch; - const pkgTarget = buildTarget.pkgTarget; - - const copyPkgConfigTask = task.define(`copy-pkg-config${dashed(platform)}${dashed(arch)}`, task.series( - util.rimraf(`out-vscode-${type}-pkg`), - copyConfigTask(`vscode-${type}${dashed(platform)}${dashed(arch)}`) - )); - - const copyPkgNativeTask = task.define(`copy-pkg-native${dashed(platform)}${dashed(arch)}`, task.series( - util.rimraf(path.join(BUILD_ROOT, `vscode-${type}${dashed(platform)}${dashed(arch)}-pkg`)), - copyNativeTask(`vscode-${type}${dashed(platform)}${dashed(arch)}-pkg`) - )); - - ['', 'min'].forEach(minified => { - const sourceFolderName = `out-vscode-${type}${dashed(minified)}`; - const destinationFolderName = `vscode-${type}${dashed(platform)}${dashed(arch)}`; - - const rollupAngularTask = task.define(`vscode-web-${type}${dashed(platform)}${dashed(arch)}-angular-rollup`, () => { - return rollupAngular(REMOTE_FOLDER); - }); - gulp.task(rollupAngularTask); - - // rebuild extensions that contain native npm modules or have conditional webpack rules - // when building with the web .yarnrc settings (e.g. runtime=node, etc.) - // this is needed to have correct module set published with desired ABI - const rebuildExtensions = ['mssql', 'notebook']; - const EXTENSIONS = path.join(REPO_ROOT, 'extensions'); - function exec(cmdLine, cwd) { - console.log(cmdLine); - cp.execSync(cmdLine, { stdio: 'inherit', cwd: cwd }); - } - const tasks = []; - rebuildExtensions.forEach(scope => { - const root = path.join(EXTENSIONS, scope); - tasks.push( - () => gulp.src(path.join(REMOTE_FOLDER, '.yarnrc')).pipe(gulp.dest(root)), - util.rimraf(path.join(root, 'node_modules')), - () => exec('yarn', root) - ); - }); - const yarnrcExtensions = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}-yarnrc-extensions`, task.series(...tasks)); - gulp.task(yarnrcExtensions); - - const cleanupExtensions = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}-cleanup-extensions`, () => { - return Promise.all(rebuildExtensions.map(scope => { - const root = path.join(EXTENSIONS, scope); - return util.rimraf(path.join(root, '.yarnrc'))(); - })); - }); - gulp.task(cleanupExtensions); - - const serverTaskCI = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series( - gulp.task(`node-${platform}-${platform === 'darwin' ? 'x64' : arch}`), - yarnrcExtensions, - compileExtensionsBuildTask, - cleanupExtensions, - rollupAngularTask, - util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), - packageTask(type, platform, arch, sourceFolderName, destinationFolderName) - )); - gulp.task(serverTaskCI); - - const serverTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( - compileBuildTask, - compileExtensionsBuildTask, - minified ? minifyTask : optimizeTask, - serverTaskCI - )); - gulp.task(serverTask); - - const serverPkgTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-pkg`, task.series( - task.parallel( - serverTask, - copyPkgConfigTask, - copyPkgNativeTask - ), - packagePkgTask(platform, arch, pkgTarget) - )); - gulp.task(serverPkgTask); - }); - }); -}); - -function packageTask(type, platform, arch, sourceFolderName, destinationFolderName) { - const destination = path.join(BUILD_ROOT, destinationFolderName); - - return () => { - const json = require('gulp-json-editor'); - - const src = gulp.src(sourceFolderName + '/**', { base: '.' }) - .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); })) - .pipe(util.setExecutableBit(['**/*.sh'])) - .pipe(filter(['**', '!**/*.js.map'])); - - const workspaceExtensionPoints = ['debuggers', 'jsonValidation']; - const isUIExtension = (manifest) => { - switch (manifest.extensionKind) { - case 'ui': return true; - case 'workspace': return false; - default: { - if (manifest.main) { - return false; - } - if (manifest.contributes && Object.keys(manifest.contributes).some(key => workspaceExtensionPoints.indexOf(key) !== -1)) { - return false; - } - // Default is UI Extension - return true; - } - } - }; - const localWorkspaceExtensions = glob.sync('extensions/*/package.json') - .filter((extensionPath) => { - if (type === 'reh-web') { - return true; // web: ship all extensions for now - } - - const manifest = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, extensionPath)).toString()); - return !isUIExtension(manifest); - }).map((extensionPath) => path.basename(path.dirname(extensionPath))) - .filter(name => name !== 'vscode-api-tests' && name !== 'vscode-test-resolver'); // Do not ship the test extensions - const marketplaceExtensions = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'product.json'), 'utf8')).builtInExtensions - .filter(entry => !entry.platforms || new Set(entry.platforms).has(platform)) - .filter(entry => !entry.clientOnly) - .map(entry => entry.name); - const extensionPaths = [...localWorkspaceExtensions, ...marketplaceExtensions] - .map(name => `.build/extensions/${name}/**`); - - const extensions = gulp.src(extensionPaths, { base: '.build', dot: true }); - const extensionsCommonDependencies = gulp.src('.build/extensions/node_modules/**', { base: '.build', dot: true }); - const sources = es.merge(src, extensions, extensionsCommonDependencies) - .pipe(filter(['**', '!**/*.js.map'], { dot: true })); - - let version = packageJson.version; - const quality = product.quality; - - if (quality && quality !== 'stable') { - version += '-' + quality; - } - - const name = product.nameShort; - const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' }) - .pipe(json({ name, version })); - - const date = new Date().toISOString(); - - const productJsonStream = gulp.src(['product.json'], { base: '.' }) - .pipe(json({ commit, date })); + .pipe(json({ commit, date, version })); const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true }); const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); const productionDependencies = getProductionDependencies(REMOTE_FOLDER); - const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`])); + const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); const deps = gulp.src(dependenciesSrc, { base: 'remote', dot: true }) // filter out unnecessary files, no source maps in server build .pipe(filter(['**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) @@ -599,7 +303,7 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa ].map(resource => gulp.src(resource, { base: '.' }).pipe(rename(resource))); } - let all = es.merge( + const all = es.merge( packageJsonStream, productJsonStream, license, @@ -625,8 +329,6 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa .pipe(replace('@@COMMIT@@', commit)) .pipe(replace('@@APPNAME@@', product.applicationName)) .pipe(rename(`bin/helpers/browser.cmd`)), - gulp.src('resources/server/bin/server-old.cmd', { base: '.' }) - .pipe(rename(`server.cmd`)), gulp.src('resources/server/bin/code-server.cmd', { base: '.' }) .pipe(rename(`bin/${product.serverApplicationName}.cmd`)), ); @@ -648,13 +350,6 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa .pipe(rename(`bin/${product.serverApplicationName}`)) .pipe(util.setExecutableBit()) ); - if (type !== 'reh-web') { - result = es.merge(result, - gulp.src('resources/server/bin/server-old.sh', { base: '.' }) - .pipe(rename(`server.sh`)) - .pipe(util.setExecutableBit()), - ); - } } return result.pipe(vfs.dest(destination)); @@ -669,27 +364,47 @@ function tweakProductForServerWeb(product) { delete result.webEndpointUrlTemplate; return result; } - +/* // {{SQL CARBON EDIT}} - turn off web/remote build tasks ['reh', 'reh-web'].forEach(type => { const optimizeTask = task.define(`optimize-vscode-${type}`, task.series( util.rimraf(`out-vscode-${type}`), - common.optimizeTask({ - src: 'out-build', - entryPoints: _.flatten(type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints), - otherSources: [], - resources: type === 'reh' ? serverResources : serverWithWebResources, - loaderConfig: common.loaderConfig(), - out: `out-vscode-${type}`, - inlineAmdImages: true, - bundleInfo: undefined, - fileContentMapper: createVSCodeWebFileContentMapper('.build/extensions', type === 'reh-web' ? tweakProductForServerWeb(product) : product) - }) + optimize.optimizeTask( + { + out: `out-vscode-${type}`, + amd: { + src: 'out-build', + entryPoints: (type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints).flat(), + otherSources: [], + resources: type === 'reh' ? serverResources : serverWithWebResources, + loaderConfig: optimize.loaderConfig(), + inlineAmdImages: true, + bundleInfo: undefined, + fileContentMapper: createVSCodeWebFileContentMapper('.build/extensions', type === 'reh-web' ? tweakProductForServerWeb(product) : product) + }, + commonJS: { + src: 'out-build', + entryPoints: [ + 'out-build/server-main.js', + 'out-build/server-cli.js' + ], + platform: 'node', + external: [ + 'minimist', + // TODO: we cannot inline `product.json` because + // it is being changed during build time at a later + // point in time (such as `checksums`) + '../product.json', + '../package.json' + ] + } + } + ) )); const minifyTask = task.define(`minify-vscode-${type}`, task.series( optimizeTask, util.rimraf(`out-vscode-${type}-min`), - common.minifyTask(`out-vscode-${type}`, `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) + optimize.minifyTask(`out-vscode-${type}`, `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) )); gulp.task(minifyTask); @@ -720,3 +435,4 @@ function tweakProductForServerWeb(product) { }); }); }); +*/ diff --git a/build/gulpfile.scan.js b/build/gulpfile.scan.js index a86cb62c5b..acee1687b7 100644 --- a/build/gulpfile.scan.js +++ b/build/gulpfile.scan.js @@ -9,8 +9,7 @@ const gulp = require('gulp'); const path = require('path'); const task = require('./lib/task'); const util = require('./lib/util'); -const _ = require('underscore'); -const electron = require('gulp-atom-electron'); +const electron = require('@vscode/gulp-electron'); const { config } = require('./lib/electron'); const filter = require('gulp-filter'); const deps = require('./lib/dependencies'); @@ -22,7 +21,6 @@ const BUILD_TARGETS = [ { platform: 'win32', arch: 'x64' }, { platform: 'win32', arch: 'arm64' }, { platform: 'darwin', arch: null, opts: { stats: true } }, - { platform: 'linux', arch: 'ia32' }, { platform: 'linux', arch: 'x64' }, { platform: 'linux', arch: 'armhf' }, { platform: 'linux', arch: 'arm64' }, @@ -42,19 +40,19 @@ BUILD_TARGETS.forEach(buildTarget => { tasks.push(util.rimraf(destinationExe), util.rimraf(destinationPdb)); // electron - tasks.push(() => electron.dest(destinationExe, _.extend({}, config, { platform, arch: arch === 'armhf' ? 'arm' : arch }))); + tasks.push(() => electron.dest(destinationExe, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch })); // pdbs for windows if (platform === 'win32') { tasks.push( - () => electron.dest(destinationPdb, _.extend({}, config, { platform, arch: arch === 'armhf' ? 'arm' : arch, pdbs: true })), + () => electron.dest(destinationPdb, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, pdbs: true }), util.rimraf(path.join(destinationExe, 'swiftshader')), util.rimraf(path.join(destinationExe, 'd3dcompiler_47.dll'))); } if (platform === 'linux') { tasks.push( - () => electron.dest(destinationPdb, _.extend({}, config, { platform, arch: arch === 'armhf' ? 'arm' : arch, symbols: true })) + () => electron.dest(destinationPdb, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, symbols: true }) ); } @@ -72,11 +70,17 @@ BUILD_TARGETS.forEach(buildTarget => { function nodeModules(destinationExe, destinationPdb, platform) { const productionDependencies = deps.getProductionDependencies(root); - const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`])); + const dependenciesSrc = productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat(); const exe = () => { return gulp.src(dependenciesSrc, { base: '.', dot: true }) - .pipe(filter(['**/*.node'])) + .pipe(filter([ + '**/*.node', + // Exclude these paths. + // We don't build the prebuilt node files so we don't scan them + '!**/prebuilds/**/*.node', + // These are 3rd party modules that we should ignore + '!**/@parcel/watcher/**/*'])) .pipe(gulp.dest(destinationExe)); }; diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index b51b2bf7de..d5d2065caa 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -6,6 +6,7 @@ 'use strict'; const gulp = require('gulp'); +const merge = require('gulp-merge-json'); const fs = require('fs'); const os = require('os'); const cp = require('child_process'); @@ -15,13 +16,13 @@ const vfs = require('vinyl-fs'); const rename = require('gulp-rename'); const replace = require('gulp-replace'); const filter = require('gulp-filter'); -const _ = require('underscore'); const util = require('./lib/util'); +const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); const buildfile = require('../src/buildfile'); -const common = require('./lib/optimize'); +const optimize = require('./lib/optimize'); const root = path.dirname(__dirname); -const commit = util.getVersion(root); +const commit = getVersion(root); const packageJson = require('../package.json'); const product = require('../product.json'); const crypto = require('crypto'); @@ -29,44 +30,46 @@ const i18n = require('./lib/i18n'); const { getProductionDependencies } = require('./lib/dependencies'); const { config } = require('./lib/electron'); const createAsar = require('./lib/asar').createAsar; +const minimist = require('minimist'); const { compileBuildTask } = require('./gulpfile.compile'); const { compileExtensionsBuildTask, compileLocalizationExtensionsBuildTask } = require('./gulpfile.extensions'); // {{SQL CARBON EDIT}} Must handle localization code. +const { getSettingsSearchBuildId, shouldSetupSettingsSearch } = require('./azure-pipelines/upload-configuration'); +const { promisify } = require('util'); +const glob = promisify(require('glob')); +const rcedit = promisify(require('rcedit')); // Build -const vscodeEntryPoints = _.flatten([ +const vscodeEntryPoints = [ buildfile.entrypoint('vs/workbench/workbench.desktop.main'), buildfile.base, buildfile.workerExtensionHost, buildfile.workerNotebook, buildfile.workerLanguageDetection, - buildfile.workerSharedProcess, buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, buildfile.workbenchDesktop, buildfile.code -]); +].flat(); const vscodeResources = [ - 'out-build/main.js', - 'out-build/cli.js', 'out-build/driver.js', 'out-build/bootstrap.js', 'out-build/bootstrap-fork.js', 'out-build/bootstrap-amd.js', 'out-build/bootstrap-node.js', 'out-build/bootstrap-window.js', - 'out-build/vs/**/*.{svg,png,html,jpg,opus}', + 'out-build/vs/**/*.{svg,png,html,jpg,mp3}', '!out-build/vs/code/browser/**/*.html', + '!out-build/vs/code/**/*-dev.html', '!out-build/vs/editor/standalone/**/*.svg', 'out-build/vs/base/common/performance.js', - 'out-build/vs/base/common/stripComments.js', - 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', 'out-build/vs/base/browser/ui/codicons/codicon/**', - 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', - 'out-build/vs/platform/environment/node/userDataPath.js', + 'out-build/vs/base/parts/sandbox/electron-sandbox/preload.js', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + 'out-build/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/*.fish', 'out-build/vs/workbench/contrib/terminal/browser/media/*.ps1', 'out-build/vs/workbench/contrib/terminal/browser/media/*.sh', 'out-build/vs/workbench/contrib/terminal/browser/media/*.zsh', @@ -75,9 +78,6 @@ const vscodeResources = [ 'out-build/vs/workbench/contrib/tasks/**/*.json', 'out-build/vs/platform/files/**/*.exe', 'out-build/vs/platform/files/**/*.md', - 'out-build/vs/code/electron-sandbox/workbench/**', - 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', - 'out-build/vs/code/electron-sandbox/issue/issueReporter.js', 'out-build/sql/**/*.{svg,png,cur,html}', 'out-build/sql/base/browser/ui/table/media/*.{gif,png,svg}', 'out-build/sql/base/browser/ui/checkbox/media/*.{gif,png,svg}', @@ -96,20 +96,58 @@ const vscodeResources = [ 'out-build/sql/media/icons/*.svg', 'out-build/sql/workbench/parts/notebook/media/**/*.svg', 'out-build/sql/setup.js', // {{SQL CARBON EDIT}} end - 'out-build/vs/code/electron-sandbox/processExplorer/processExplorer.js', '!**/test/**' ]; +// Do not change the order of these files! They will +// be inlined into the target window file in this order +// and they depend on each other in this way. +const windowBootstrapFiles = [ + 'out-build/bootstrap.js', + 'out-build/vs/loader.js', + 'out-build/bootstrap-window.js' +]; + const optimizeVSCodeTask = task.define('optimize-vscode', task.series( util.rimraf('out-vscode'), - common.optimizeTask({ - src: 'out-build', - entryPoints: vscodeEntryPoints, - resources: vscodeResources, - loaderConfig: common.loaderConfig(), - out: 'out-vscode', - bundleInfo: undefined - }) + // Optimize: bundles source files automatically based on + // AMD and CommonJS import statements based on the passed + // in entry points. In addition, concat window related + // bootstrap files into a single file. + optimize.optimizeTask( + { + out: 'out-vscode', + amd: { + src: 'out-build', + entryPoints: vscodeEntryPoints, + resources: vscodeResources, + loaderConfig: optimize.loaderConfig(), + bundleInfo: undefined + }, + commonJS: { + src: 'out-build', + entryPoints: [ + 'out-build/main.js', + 'out-build/cli.js' + ], + platform: 'node', + external: [ + 'electron', + 'minimist', + // TODO: we cannot inline `product.json` because + // it is being changed during build time at a later + // point in time (such as `checksums`) + '../product.json', + '../package.json', + ] + }, + manual: [ + { src: [...windowBootstrapFiles, 'out-build/vs/code/electron-sandbox/workbench/workbench.js'], out: 'vs/code/electron-sandbox/workbench/workbench.js' }, + { src: [...windowBootstrapFiles, 'out-build/vs/code/electron-sandbox/issue/issueReporter.js'], out: 'vs/code/electron-sandbox/issue/issueReporter.js' }, + { src: [...windowBootstrapFiles, 'out-build/vs/code/electron-sandbox/processExplorer/processExplorer.js'], out: 'vs/code/electron-sandbox/processExplorer/processExplorer.js' } + ] + } + ) )); gulp.task(optimizeVSCodeTask); @@ -162,7 +200,7 @@ const sourceMappingURLBase = `https://sqlopsbuilds.blob.core.windows.net/sourcem const minifyVSCodeTask = task.define('minify-vscode', task.series( optimizeVSCodeTask, util.rimraf('out-vscode-min'), - common.minifyTask('out-vscode', `${sourceMappingURLBase}/core`) + optimize.minifyTask('out-vscode', `${sourceMappingURLBase}/core`) )); gulp.task(minifyVSCodeTask); @@ -170,8 +208,8 @@ const core = task.define('core-ci', task.series( gulp.task('compile-build'), task.parallel( gulp.task('minify-vscode'), - gulp.task('minify-vscode-reh'), - gulp.task('minify-vscode-reh-web'), + // gulp.task('minify-vscode-reh'), // {{SQL CARBON EDIT}} - turn off web/remote build + // gulp.task('minify-vscode-reh-web'), ) )); gulp.task(core); @@ -217,13 +255,13 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op platform = platform || process.platform; return () => { - const electron = require('gulp-atom-electron'); + const electron = require('@vscode/gulp-electron'); const json = require('gulp-json-editor'); const out = sourceFolderName; const checksums = computeChecksums(out, [ - 'vs/base/parts/sandbox/electron-browser/preload.js', + 'vs/base/parts/sandbox/electron-sandbox/preload.js', 'vs/workbench/workbench.desktop.main.js', 'vs/workbench/workbench.desktop.main.css', 'vs/workbench/api/node/extensionHostProcess.js', @@ -235,7 +273,15 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + out), 'out'); })) .pipe(util.setExecutableBit(['**/*.sh'])); - const extensions = gulp.src(['.build/extensions/**', '!.build/extensions/node_modules/**'], { base: '.build', dot: true }); // {{SQL CARBON EDIT}} - don't package the node_modules directory + const platformSpecificBuiltInExtensionsExclusions = product.builtInExtensions.filter(ext => { + if (!ext.platforms) { + return false; + } + + const set = new Set(ext.platforms); + return !set.has(platform); + }).map(ext => `!.build/extensions/${ext.name}/**`); + const extensions = gulp.src(['.build/extensions/**', ...platformSpecificBuiltInExtensionsExclusions, '!.build/extensions/node_modules/**'], { base: '.build', dot: true }); // {{SQL CARBON EDIT}} - don't package the node_modules directory const sources = es.merge(src, extensions) .pipe(filter(['**', '!**/*.js.map'], { dot: true })); @@ -260,7 +306,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(json(packageJsonUpdates)); const date = new Date().toISOString(); - const productJsonUpdate = { commit, date, checksums }; + const productJsonUpdate = { commit, date, checksums, version }; if (shouldSetupSettingsSearch()) { productJsonUpdate.settingsSearchBuildId = getSettingsSearchBuildId(packageJson); @@ -269,7 +315,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const productJsonStream = gulp.src(['product.json'], { base: '.' }) .pipe(json(productJsonUpdate)); - const license = gulp.src(['LICENSES.chromium.html', 'LICENSE.txt', 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.', allowEmpty: true }); + const license = gulp.src([product.licenseFileName, 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.', allowEmpty: true }); // TODO the API should be copied to `out` during compile, not here const api = gulp.src('src/vscode-dts/vscode.d.ts').pipe(rename('out/vscode-dts/vscode.d.ts')); @@ -281,7 +327,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); const root = path.resolve(path.join(__dirname, '..')); const productionDependencies = getProductionDependencies(root); - const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`])); + const dependenciesSrc = productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat(); const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) @@ -296,6 +342,7 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op '**/node-pty/lib/worker/conoutSocketWorker.js', '**/node-pty/lib/shared/conout.js', '**/*.wasm', + '**/node-vsce-sign/bin/*', ], 'node_modules.asar')); let all = es.merge( @@ -332,8 +379,8 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op .pipe(util.skipDirectories()) .pipe(util.fixWin32DirectoryPermissions()) .pipe(filter(['**', '!**/.github/**'], { dot: true })) // https://github.com/microsoft/vscode/issues/116523 - .pipe(electron(_.extend({}, config, { platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: true }))) - .pipe(filter(['**', '!LICENSE', '!LICENSES.chromium.html', '!version'], { dot: true })); + .pipe(electron({ ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: false })) + .pipe(filter(['**', '!LICENSE', '!version'], { dot: true })); if (platform === 'linux') { result = es.merge(result, gulp.src('resources/completions/bash/code', { base: '.' }) @@ -368,6 +415,9 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op result = es.merge(result, gulp.src('.build/policies/win32/**', { base: '.build/policies/win32' }) .pipe(rename(f => f.dirname = `policies/${f.dirname}`))); + if (quality === 'insider') { + result = es.merge(result, gulp.src('.build/win32/appx/**', { base: '.build/win32' })); + } } else if (platform === 'linux') { result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) .pipe(replace('@@PRODNAME@@', product.nameLong)) @@ -379,6 +429,35 @@ function packageTask(platform, arch, sourceFolderName, destinationFolderName, op }; } +function patchWin32DependenciesTask(destinationFolderName) { + const cwd = path.join(path.dirname(root), destinationFolderName); + + return async () => { + const deps = await glob('**/*.node', { cwd }); + const packageJson = JSON.parse(await fs.promises.readFile(path.join(cwd, 'resources', 'app', 'package.json'), 'utf8')); + const product = JSON.parse(await fs.promises.readFile(path.join(cwd, 'resources', 'app', 'product.json'), 'utf8')); + const baseVersion = packageJson.version.replace(/-.*$/, ''); + + await Promise.all(deps.map(async dep => { + const basename = path.basename(dep); + + await rcedit(path.join(cwd, dep), { + 'file-version': baseVersion, + 'version-string': { + 'CompanyName': 'Microsoft Corporation', + 'FileDescription': product.nameLong, + 'FileVersion': packageJson.version, + 'InternalName': basename, + 'LegalCopyright': 'Copyright (C) 2022 Microsoft. All rights reserved', + 'OriginalFilename': basename, + 'ProductName': product.nameLong, + 'ProductVersion': packageJson.version, + } + }); + })); + }; +} + const fileLengthFilter = filter([ '**', '!extensions/import/*.docx', @@ -425,10 +504,16 @@ BUILD_TARGETS.forEach(buildTarget => { const sourceFolderName = `out-vscode${dashed(minified)}`; const destinationFolderName = `azuredatastudio${dashed(platform)}${dashed(arch)}`; - const vscodeTaskCI = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series( + const tasks = [ util.rimraf(path.join(buildRoot, destinationFolderName)), packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) - )); + ]; + + if (platform === 'win32') { + tasks.push(patchWin32DependenciesTask(destinationFolderName)); + } + + const vscodeTaskCI = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series(...tasks)); gulp.task(vscodeTaskCI); const vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( @@ -448,6 +533,8 @@ BUILD_TARGETS.forEach(buildTarget => { } }); +// #region nls + const innoSetupConfig = { 'zh-cn': { codePage: 'CP936', defaultInfo: { name: 'Simplified Chinese', id: '$0804', } }, 'zh-tw': { codePage: 'CP950', defaultInfo: { name: 'Traditional Chinese', id: '$0404' } }, @@ -463,12 +550,6 @@ const innoSetupConfig = { 'tr': { codePage: 'CP1254' } }; -// Transifex Localizations - -const apiHostname = process.env.TRANSIFEX_API_URL; -const apiName = process.env.TRANSIFEX_API_NAME; -const apiToken = process.env.TRANSIFEX_API_TOKEN; - gulp.task(task.define( 'vscode-translations-push', task.series( @@ -494,16 +575,20 @@ gulp.task(task.define( const vscodeTranslationsExport = task.define( 'vscode-translations-export', task.series( - compileBuildTask, + core, compileLocalizationExtensionsBuildTask, // {{SQL CARBON EDIT}} now include all extensions in ADS, not just a subset. (replaces 'compileExtensionsBuildTask' here). - optimizeVSCodeTask, function () { const pathToMetadata = './out-vscode/nls.metadata.json'; + const pathToRehWebMetadata = './out-vscode-reh-web/nls.metadata.json'; const pathToExtensions = '.build/extensions/*'; const pathToSetup = 'build/win32/i18n/messages.en.isl'; return es.merge( - gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), + gulp.src([pathToMetadata, pathToRehWebMetadata]).pipe(merge({ + fileName: 'nls.metadata.json', + jsonSpace: '', + concatArrays: true + })).pipe(i18n.createXlfFilesForCoreBundle()), gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) ).pipe(vfs.dest('./vscode-translations-export')); // {{SQL CARBON EDIT}} move vscode-translations-export into ADS (for safely deleting after use). @@ -523,13 +608,6 @@ gulp.task(task.define( )); // {{SQL CARBON EDIT}} end -gulp.task('vscode-translations-pull', function () { - return es.merge([...i18n.defaultLanguages, ...i18n.extraLanguages].map(language => { - const includeDefault = !!innoSetupConfig[language.id].defaultInfo; - return i18n.pullSetupXlfFiles(apiHostname, apiName, apiToken, language, includeDefault).pipe(vfs.dest(`../vscode-translations-import/${language.id}/setup`)); - })); -}); - gulp.task('vscode-translations-import', function () { // {{SQL CARBON EDIT}} - Replace function body with our own return new Promise(function (resolve) { @@ -628,25 +706,3 @@ gulp.task(task.define( ) )); -function shouldSetupSettingsSearch() { - const branch = process.env.BUILD_SOURCEBRANCH; - return branch && (/\/main$/.test(branch) || branch.indexOf('/release/') >= 0); -} - -function getSettingsSearchBuildId(packageJson) { - try { - const branch = process.env.BUILD_SOURCEBRANCH; - const branchId = branch.indexOf('/release/') >= 0 ? 0 : - /\/main$/.test(branch) ? 1 : - 2; // Some unexpected branch - - const out = cp.execSync(`git rev-list HEAD --count`); - const count = parseInt(out.toString()); - - // - // 1.25.1, 1,234,567 commits, main = 1250112345671 - return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId; - } catch (e) { - throw new Error('Could not determine build number: ' + e.toString()); - } -} diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index dc1b101c85..0e2648f843 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -12,19 +12,22 @@ const shell = require('gulp-shell'); const es = require('event-stream'); const vfs = require('vinyl-fs'); const util = require('./lib/util'); +const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); const packageJson = require('../package.json'); const product = require('../product.json'); -const rpmDependenciesGenerator = require('./linux/rpm/dependencies-generator'); -const debianDependenciesGenerator = require('./linux/debian/dependencies-generator'); +const dependenciesGenerator = require('./linux/dependencies-generator'); const sysrootInstaller = require('./linux/debian/install-sysroot'); const debianRecommendedDependencies = require('./linux/debian/dep-lists').recommendedDeps; const path = require('path'); const root = path.dirname(__dirname); -const commit = util.getVersion(root); +const commit = getVersion(root); const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); +/** + * @param {string} arch + */ function getDebPackageArch(arch) { return { x64: 'amd64', armhf: 'armhf', arm64: 'arm64' }[arch]; } @@ -81,7 +84,7 @@ function prepareDebPackage(arch) { async function () { const that = this; const sysroot = await sysrootInstaller.getSysroot(debArch); - const dependencies = debianDependenciesGenerator.getDependencies(binaryDir, product.applicationName, debArch, sysroot); + const dependencies = dependenciesGenerator.getDependencies('deb', binaryDir, product.applicationName, debArch, sysroot); gulp.src('resources/linux/debian/control.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision)) @@ -164,7 +167,7 @@ function prepareRpmPackage(arch) { .pipe(replace('@@NAME_LONG@@', product.nameLong)) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@LICENSE@@', product.licenseName)) - .pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml')); + .pipe(rename('BUILD/usr/share/appdata/' + product.applicationName + '.appdata.xml')); const workspaceMime = gulp.src('resources/linux/code-workspace.xml', { base: '.' }) .pipe(replace('@@NAME_LONG@@', product.nameLong)) @@ -185,7 +188,7 @@ function prepareRpmPackage(arch) { const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) .pipe(rename(function (p) { p.dirname = 'BUILD/usr/share/' + product.applicationName + '/' + p.dirname; })); - const dependencies = rpmDependenciesGenerator.getDependencies(binaryDir, product.applicationName, rpmArch); + const dependencies = dependenciesGenerator.getDependencies('rpm', binaryDir, product.applicationName, rpmArch); const spec = gulp.src('resources/linux/rpm/code.spec.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@NAME_LONG@@', product.nameLong)) diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js index 9a00be9971..2613a8d638 100644 --- a/build/gulpfile.vscode.web.js +++ b/build/gulpfile.vscode.web.js @@ -9,12 +9,12 @@ const gulp = require('gulp'); const path = require('path'); const es = require('event-stream'); const util = require('./lib/util'); +const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); -const common = require('./lib/optimize'); +const optimize = require('./lib/optimize'); const product = require('../product.json'); const rename = require('gulp-rename'); const filter = require('gulp-filter'); -const _ = require('underscore'); const { getProductionDependencies } = require('./lib/dependencies'); const vfs = require('vinyl-fs'); const replace = require('gulp-replace'); @@ -26,13 +26,13 @@ const REPO_ROOT = path.dirname(__dirname); const BUILD_ROOT = path.dirname(REPO_ROOT); const WEB_FOLDER = path.join(REPO_ROOT, 'remote', 'web'); -const commit = util.getVersion(REPO_ROOT); +const commit = getVersion(REPO_ROOT); const quality = product.quality; const version = (quality && quality !== 'stable') ? `${packageJson.version}-${quality}` : packageJson.version; const vscodeWebResourceIncludes = [ // Workbench - 'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png,jpg,opus}', + 'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png,jpg,mp3}', 'out-build/vs/code/browser/workbench/*.html', 'out-build/vs/base/browser/ui/codicons/codicon/**/*.ttf', 'out-build/vs/**/markdown.css', @@ -55,7 +55,7 @@ const vscodeWebResources = [ ...vscodeWebResourceIncludes, // Excludes - '!out-build/vs/**/{node,electron-browser,electron-main}/**', + '!out-build/vs/**/{node,electron-sandbox,electron-main}/**', '!out-build/vs/editor/standalone/**', '!out-build/vs/workbench/**/*-tb.png', '!**/test/**' @@ -63,16 +63,17 @@ const vscodeWebResources = [ const buildfile = require('../src/buildfile'); -const vscodeWebEntryPoints = _.flatten([ +const vscodeWebEntryPoints = [ buildfile.entrypoint('vs/workbench/workbench.web.main'), buildfile.base, buildfile.workerExtensionHost, buildfile.workerNotebook, buildfile.workerLanguageDetection, buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, buildfile.keyboardMaps, buildfile.workbenchWeb -]); +].flat(); exports.vscodeWebEntryPoints = vscodeWebEntryPoints; const buildDate = new Date().toISOString(); @@ -82,7 +83,7 @@ const buildDate = new Date().toISOString(); */ const createVSCodeWebProductConfigurationPatcher = (product) => { /** - * @param content {string} The contens of the file + * @param content {string} The contents of the file * @param path {string} The absolute file path, always using `/`, even on Windows */ const result = (content, path) => { @@ -94,7 +95,7 @@ const createVSCodeWebProductConfigurationPatcher = (product) => { commit, date: buildDate }); - return content.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', productConfiguration.substr(1, productConfiguration.length - 2) /* without { and }*/); + return content.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', () => productConfiguration.substr(1, productConfiguration.length - 2) /* without { and }*/); } return content; @@ -107,14 +108,14 @@ const createVSCodeWebProductConfigurationPatcher = (product) => { */ const createVSCodeWebBuiltinExtensionsPatcher = (extensionsRoot) => { /** - * @param content {string} The contens of the file + * @param content {string} The contents of the file * @param path {string} The absolute file path, always using `/`, even on Windows */ const result = (content, path) => { // (2) Patch builtin extensions if (path.endsWith('vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.js')) { const builtinExtensions = JSON.stringify(extensions.scanBuiltinExtensions(extensionsRoot)); - return content.replace('/*BUILD->INSERT_BUILTIN_EXTENSIONS*/', builtinExtensions.substr(1, builtinExtensions.length - 2) /* without [ and ]*/); + return content.replace('/*BUILD->INSERT_BUILTIN_EXTENSIONS*/', () => builtinExtensions.substr(1, builtinExtensions.length - 2) /* without [ and ]*/); } return content; @@ -127,7 +128,7 @@ const createVSCodeWebBuiltinExtensionsPatcher = (extensionsRoot) => { */ const combineContentPatchers = (...patchers) => { /** - * @param content {string} The contens of the file + * @param content {string} The contents of the file * @param path {string} The absolute file path, always using `/`, even on Windows */ const result = (content, path) => { @@ -153,24 +154,28 @@ exports.createVSCodeWebFileContentMapper = createVSCodeWebFileContentMapper; const optimizeVSCodeWebTask = task.define('optimize-vscode-web', task.series( util.rimraf('out-vscode-web'), - common.optimizeTask({ - src: 'out-build', - entryPoints: _.flatten(vscodeWebEntryPoints), - otherSources: [], - resources: vscodeWebResources, - loaderConfig: common.loaderConfig(), - externalLoaderInfo: util.createExternalLoaderConfig(product.webEndpointUrl, commit, quality), - out: 'out-vscode-web', - inlineAmdImages: true, - bundleInfo: undefined, - fileContentMapper: createVSCodeWebFileContentMapper('.build/web/extensions', product) - }) + optimize.optimizeTask( + { + out: 'out-vscode-web', + amd: { + src: 'out-build', + entryPoints: vscodeWebEntryPoints.flat(), + otherSources: [], + resources: vscodeWebResources, + loaderConfig: optimize.loaderConfig(), + externalLoaderInfo: util.createExternalLoaderConfig(product.webEndpointUrl, commit, quality), + inlineAmdImages: true, + bundleInfo: undefined, + fileContentMapper: createVSCodeWebFileContentMapper('.build/web/extensions', product) + } + } + ) )); const minifyVSCodeWebTask = task.define('minify-vscode-web', task.series( optimizeVSCodeWebTask, util.rimraf('out-vscode-web-min'), - common.minifyTask('out-vscode-web', `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) + optimize.minifyTask('out-vscode-web', `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) )); gulp.task(minifyVSCodeWebTask); @@ -195,7 +200,7 @@ function packageTask(sourceFolderName, destinationFolderName) { const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true }); const productionDependencies = getProductionDependencies(WEB_FOLDER); - const dependenciesSrc = _.flatten(productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`])); + const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); const deps = gulp.src(dependenciesSrc, { base: 'remote/web', dot: true }) .pipe(filter(['**', '!**/package-lock.json'])) @@ -229,7 +234,7 @@ function packageTask(sourceFolderName, destinationFolderName) { const compileWebExtensionsBuildTask = task.define('compile-web-extensions-build', task.series( task.define('clean-web-extensions-build', util.rimraf('.build/web/extensions')), task.define('bundle-web-extensions-build', () => extensions.packageLocalExtensionsStream(true).pipe(gulp.dest('.build/web'))), - task.define('bundle-marketplace-web-extensions-build', () => extensions.packageMarketplaceExtensionsStream(true, product.extensionsGallery?.serviceUrl).pipe(gulp.dest('.build/web'))), + task.define('bundle-marketplace-web-extensions-build', () => extensions.packageMarketplaceExtensionsStream(true).pipe(gulp.dest('.build/web'))), task.define('bundle-web-extension-media-build', () => extensions.buildExtensionMedia(false, '.build/web/extensions')), )); gulp.task(compileWebExtensionsBuildTask); diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index bdaecb5689..c5f3ac119d 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -88,6 +88,7 @@ function buildWin32Setup(arch, target) { productJson['target'] = target; fs.writeFileSync(productJsonPath, JSON.stringify(productJson, undefined, '\t')); + const quality = product.quality || 'dev'; const definitions = { NameLong: product.nameLong, NameShort: product.nameShort, @@ -99,6 +100,9 @@ function buildWin32Setup(arch, target) { RegValueName: product.win32RegValueName, ShellNameShort: product.win32ShellNameShort, AppMutex: product.win32MutexName, + TunnelMutex: product.win32TunnelMutex, + TunnelServiceMutex: product.win32TunnelServiceMutex, + ApplicationName: product.applicationName, Arch: arch, AppId: { 'ia32': ia32AppId, 'x64': x64AppId, 'arm64': arm64AppId }[arch], IncompatibleTargetAppId: { 'ia32': product.win32AppId, 'x64': product.win32x64AppId, 'arm64': product.win32arm64AppId }[arch], @@ -110,9 +114,15 @@ function buildWin32Setup(arch, target) { RepoDir: repoPath, OutputDir: outputPath, InstallTarget: target, - ProductJsonPath: productJsonPath + ProductJsonPath: productJsonPath, + Quality: quality }; + if (quality === 'insider') { + definitions['AppxPackage'] = `code_insiders_explorer_${arch === 'ia32' ? 'x86' : arch}.appx`; + definitions['AppxPackageFullname'] = `Microsoft.${product.win32RegValueName}_1.0.0.0_neutral__8wekyb3d8bbwe`; + } + packageInnoSetup(issPath, { definitions }, cb); }; } diff --git a/build/hygiene.js b/build/hygiene.js index a12329e5a8..260572a459 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -10,7 +10,8 @@ const vfs = require('vinyl-fs'); const path = require('path'); const fs = require('fs'); const pall = require('p-all'); -const { all, copyrightFilter, unicodeFilter, indentationFilter, tsFormattingFilter, eslintFilter } = require('./filters'); + +const { all, copyrightFilter, unicodeFilter, indentationFilter, tsFormattingFilter, eslintFilter, stylelintFilter } = require('./filters'); const copyrightHeaderLines = [ '/*---------------------------------------------------------------------------------------------', @@ -21,6 +22,7 @@ const copyrightHeaderLines = [ function hygiene(some, linting = true) { const gulpeslint = require('gulp-eslint'); + const gulpstylelint = require('./stylelint'); const tsfmt = require('typescript-formatter'); let errorCount = 0; @@ -172,8 +174,7 @@ function hygiene(some, linting = true) { .pipe(filter(eslintFilter)) .pipe( gulpeslint({ - configFile: '.eslintrc.json', - rulePaths: ['./build/lib/eslint'], + configFile: '.eslintrc.json' }) ) .pipe(gulpeslint.formatEach('compact')) @@ -184,6 +185,16 @@ function hygiene(some, linting = true) { }) ) ); + streams.push( + result.pipe(filter(stylelintFilter)).pipe(gulpstylelint(((message, isError) => { + if (isError) { + console.error(message); + errorCount++; + } else { + console.warn(message); + } + }))) + ); } let count = 0; @@ -291,7 +302,7 @@ if (require.main === module) { .then( (vinyls) => new Promise((c, e) => - hygiene(es.readArray(vinyls)) + hygiene(es.readArray(vinyls).pipe(filter(all))) .on('end', () => c()) .on('error', e) ) diff --git a/build/lib/asar.js b/build/lib/asar.js index 2a25780d44..2311e10624 100644 --- a/build/lib/asar.js +++ b/build/lib/asar.js @@ -116,3 +116,4 @@ function createAsar(folderPath, unpackGlobs, destFilename) { }); } exports.createAsar = createAsar; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXNhci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImFzYXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsNkJBQTZCO0FBQzdCLG1DQUFtQztBQUNuQyxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsb0JBQW9CLENBQUMsQ0FBQztBQUM3QyxNQUFNLFVBQVUsR0FBMEIsT0FBTyxDQUFDLHFCQUFxQixDQUFDLENBQUM7QUFDekUsbUNBQW1DO0FBQ25DLHVDQUF1QztBQVN2QyxTQUFnQixVQUFVLENBQUMsVUFBa0IsRUFBRSxXQUFxQixFQUFFLFlBQW9CO0lBRXpGLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxJQUFlLEVBQVcsRUFBRTtRQUNyRCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsV0FBVyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUM1QyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFO2dCQUM3QyxPQUFPLElBQUksQ0FBQzthQUNaO1NBQ0Q7UUFDRCxPQUFPLEtBQUssQ0FBQztJQUNkLENBQUMsQ0FBQztJQUVGLE1BQU0sVUFBVSxHQUFHLElBQUksVUFBVSxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBQzlDLE1BQU0sR0FBRyxHQUFhLEVBQUUsQ0FBQztJQUV6QixnQ0FBZ0M7SUFDaEMsSUFBSSxjQUFjLEdBQUcsQ0FBQyxDQUFDO0lBQ3ZCLElBQUksY0FBYyxHQUFHLEdBQUcsRUFBRSxHQUFHLGNBQWMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRWpELHlDQUF5QztJQUN6QyxNQUFNLE9BQU8sR0FBK0IsRUFBRSxDQUFDO0lBQy9DLE1BQU0sd0JBQXdCLEdBQUcsQ0FBQyxHQUFXLEVBQUUsRUFBRTtRQUNoRCxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsRUFBRTtZQUNqQixPQUFPO1NBQ1A7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 \ No newline at end of file diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index dcdd0d83ea..1ec6ceb502 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -4,7 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.getBuiltInExtensions = void 0; +exports.getBuiltInExtensions = exports.getExtensionStream = void 0; const fs = require("fs"); const path = require("path"); const os = require("os"); @@ -44,6 +44,21 @@ function isUpToDate(extension) { return false; } } +function getExtensionDownloadStream(extension) { + const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; + return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); +} +function getExtensionStream(extension) { + // if the extension exists on disk, use those files instead of downloading anew + if (isUpToDate(extension)) { + log('[extensions]', `${extension.name}@${extension.version} up to date`, ansiColors.green('✔︎')); + return vfs.src(['**'], { cwd: getExtensionPath(extension), dot: true }) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); + } + return getExtensionDownloadStream(extension); +} +exports.getExtensionStream = getExtensionStream; function syncMarketplaceExtension(extension) { const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; const source = ansiColors.blue(galleryServiceUrl ? '[marketplace]' : '[github]'); @@ -52,8 +67,7 @@ function syncMarketplaceExtension(extension) { return es.readArray([]); } rimraf.sync(getExtensionPath(extension)); - return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) - .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)) + return getExtensionDownloadStream(extension) .pipe(vfs.dest('.build/builtInExtensions')) .on('end', () => log(source, extension.name, ansiColors.green('✔︎'))); } @@ -120,3 +134,4 @@ if (require.main === module) { process.exit(1); }); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbHRJbkV4dGVuc2lvbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJidWlsdEluRXh0ZW5zaW9ucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLHlCQUF5QjtBQUN6QixpQ0FBaUM7QUFDakMsbUNBQW1DO0FBQ25DLHNDQUFzQztBQUN0QyxnQ0FBZ0M7QUFDaEMsb0NBQW9DO0FBQ3BDLHNDQUFzQztBQUN0QywwQ0FBMEM7QUFHMUMsTUFBTSxNQUFNLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBbUJqQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUNuRCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsb0JBQW9CLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0FBQ3BHLE1BQU0saUJBQWlCLEdBQTJCLFdBQVcsQ0FBQyxpQkFBaUIsSUFBSSxFQUFFLENBQUM7QUFDdEYsTUFBTSxvQkFBb0IsR0FBMkIsV0FBVyxDQUFDLG9CQUFvQixJQUFJLEVBQUUsQ0FBQztBQUM1RixNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsRUFBRSxpQkFBaUIsRUFBRSxZQUFZLEVBQUUsY0FBYyxDQUFDLENBQUM7QUFDakcsTUFBTSxjQUFjLEdBQUcsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGdEQUFnRCxDQUFDLENBQUM7QUFFdEYsU0FBUyxHQUFHLENBQUMsR0FBRyxRQUFrQjtJQUNqQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixRQUFRLENBQUMsR0FBRyxRQUFRLENBQUMsQ0FBQztLQUN0QjtBQUNGLENB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 \ No newline at end of file diff --git a/build/lib/builtInExtensions.ts b/build/lib/builtInExtensions.ts index 2373db4738..d4495a4710 100644 --- a/build/lib/builtInExtensions.ts +++ b/build/lib/builtInExtensions.ts @@ -68,10 +68,26 @@ function isUpToDate(extension: IExtensionDefinition): boolean { } } +function getExtensionDownloadStream(extension: IExtensionDefinition) { + const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; + return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); +} + +export function getExtensionStream(extension: IExtensionDefinition) { + // if the extension exists on disk, use those files instead of downloading anew + if (isUpToDate(extension)) { + log('[extensions]', `${extension.name}@${extension.version} up to date`, ansiColors.green('✔︎')); + return vfs.src(['**'], { cwd: getExtensionPath(extension), dot: true }) + .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)); + } + + return getExtensionDownloadStream(extension); +} + function syncMarketplaceExtension(extension: IExtensionDefinition): Stream { const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; const source = ansiColors.blue(galleryServiceUrl ? '[marketplace]' : '[github]'); - if (isUpToDate(extension)) { log(source, `${extension.name}@${extension.version}`, ansiColors.green('✔︎')); return es.readArray([]); @@ -79,8 +95,7 @@ function syncMarketplaceExtension(extension: IExtensionDefinition): Stream { rimraf.sync(getExtensionPath(extension)); - return (galleryServiceUrl ? ext.fromMarketplace(galleryServiceUrl, extension) : ext.fromGithub(extension)) - .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)) + return getExtensionDownloadStream(extension) .pipe(vfs.dest('.build/builtInExtensions')) .on('end', () => log(source, extension.name, ansiColors.green('✔︎'))); } diff --git a/build/lib/builtInExtensionsCG.js b/build/lib/builtInExtensionsCG.js index 1bcf757891..6d66a440f7 100644 --- a/build/lib/builtInExtensionsCG.js +++ b/build/lib/builtInExtensionsCG.js @@ -4,7 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -const got_1 = require("got"); +const node_fetch_1 = require("node-fetch"); const fs = require("fs"); const path = require("path"); const url = require("url"); @@ -14,30 +14,31 @@ const rootCG = path.join(root, 'extensionsCG'); const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')); const builtInExtensions = productjson.builtInExtensions || []; const webBuiltInExtensions = productjson.webBuiltInExtensions || []; -const token = process.env['VSCODE_MIXIN_PASSWORD'] || process.env['GITHUB_TOKEN'] || undefined; +const token = process.env['GITHUB_TOKEN']; const contentBasePath = 'raw.githubusercontent.com'; const contentFileNames = ['package.json', 'package-lock.json', 'yarn.lock']; async function downloadExtensionDetails(extension) { const extensionLabel = `${extension.name}@${extension.version}`; const repository = url.parse(extension.repo).path.substr(1); const repositoryContentBaseUrl = `https://${token ? `${token}@` : ''}${contentBasePath}/${repository}/v${extension.version}`; - const promises = []; - for (const fileName of contentFileNames) { - promises.push(new Promise(resolve => { - (0, got_1.default)(`${repositoryContentBaseUrl}/${fileName}`) - .then(response => { - resolve({ fileName, body: response.rawBody }); - }) - .catch(error => { - if (error.response.statusCode === 404) { - resolve({ fileName, body: undefined }); - } - else { - resolve({ fileName, body: null }); - } - }); - })); + async function getContent(fileName) { + try { + const response = await (0, node_fetch_1.default)(`${repositoryContentBaseUrl}/${fileName}`); + if (response.ok) { + return { fileName, body: await response.buffer() }; + } + else if (response.status === 404) { + return { fileName, body: undefined }; + } + else { + return { fileName, body: null }; + } + } + catch (e) { + return { fileName, body: null }; + } } + const promises = contentFileNames.map(getContent); console.log(extensionLabel); const results = await Promise.all(promises); for (const result of results) { @@ -76,3 +77,4 @@ main().then(() => { console.error(err); process.exit(1); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbHRJbkV4dGVuc2lvbnNDRy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImJ1aWx0SW5FeHRlbnNpb25zQ0cudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRywyQ0FBK0I7QUFDL0IseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QiwyQkFBMkI7QUFDM0IsMENBQTJDO0FBRzNDLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBQ25ELE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxDQUFDO0FBQy9DLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxvQkFBb0IsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDcEcsTUFBTSxpQkFBaUIsR0FBMkIsV0FBVyxDQUFDLGlCQUFpQixJQUFJLEVBQUUsQ0FBQztBQUN0RixNQUFNLG9CQUFvQixHQUEyQixXQUFXLENBQUMsb0JBQW9CLElBQUksRUFBRSxDQUFDO0FBQzVGLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLENBQUM7QUFFMUMsTUFBTSxlQUFlLEdBQUcsMkJBQTJCLENBQUM7QUFDcEQsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLGNBQWMsRUFBRSxtQkFBbUIsRUFBRSxXQUFXLENBQUMsQ0FBQztBQUU1RSxLQUFLLFVBQVUsd0JBQXdCLENBQUMsU0FBK0I7SUFDdEUsTUFBTSxjQUFjLEdBQUcsR0FBRyxTQUFTLENBQUMsSUFBSSxJQUFJLFNBQVMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUNoRSxNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxJ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 \ No newline at end of file diff --git a/build/lib/builtInExtensionsCG.ts b/build/lib/builtInExtensionsCG.ts index ac19e0f23a..9e6fad359d 100644 --- a/build/lib/builtInExtensionsCG.ts +++ b/build/lib/builtInExtensionsCG.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import got from 'got'; +import fetch from 'node-fetch'; import * as fs from 'fs'; import * as path from 'path'; import * as url from 'url'; @@ -15,7 +15,7 @@ const rootCG = path.join(root, 'extensionsCG'); const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')); const builtInExtensions = productjson.builtInExtensions || []; const webBuiltInExtensions = productjson.webBuiltInExtensions || []; -const token = process.env['VSCODE_MIXIN_PASSWORD'] || process.env['GITHUB_TOKEN'] || undefined; +const token = process.env['GITHUB_TOKEN']; const contentBasePath = 'raw.githubusercontent.com'; const contentFileNames = ['package.json', 'package-lock.json', 'yarn.lock']; @@ -25,23 +25,24 @@ async function downloadExtensionDetails(extension: IExtensionDefinition): Promis const repository = url.parse(extension.repo).path!.substr(1); const repositoryContentBaseUrl = `https://${token ? `${token}@` : ''}${contentBasePath}/${repository}/v${extension.version}`; - const promises = []; - for (const fileName of contentFileNames) { - promises.push(new Promise<{ fileName: string; body: Buffer | undefined | null }>(resolve => { - got(`${repositoryContentBaseUrl}/${fileName}`) - .then(response => { - resolve({ fileName, body: response.rawBody }); - }) - .catch(error => { - if (error.response.statusCode === 404) { - resolve({ fileName, body: undefined }); - } else { - resolve({ fileName, body: null }); - } - }); - })); + + async function getContent(fileName: string): Promise<{ fileName: string; body: Buffer | undefined | null }> { + try { + const response = await fetch(`${repositoryContentBaseUrl}/${fileName}`); + if (response.ok) { + return { fileName, body: await response.buffer() }; + } else if (response.status === 404) { + return { fileName, body: undefined }; + } else { + return { fileName, body: null }; + } + } catch (e) { + return { fileName, body: null }; + } } + const promises = contentFileNames.map(getContent); + console.log(extensionLabel); const results = await Promise.all(promises); for (const result of results) { diff --git a/build/lib/bundle.js b/build/lib/bundle.js index d83f8a91d2..d18b332ed4 100644 --- a/build/lib/bundle.js +++ b/build/lib/bundle.js @@ -483,3 +483,4 @@ function topologicalSort(graph) { } return L; } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiYnVuZGxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0IseUJBQXlCO0FBcUd6Qjs7R0FFRztBQUNILFNBQWdCLE1BQU0sQ0FBQyxXQUEwQixFQUFFLE1BQXFCLEVBQUUsUUFBMEQ7SUFDbkksTUFBTSxjQUFjLEdBQW1CLEVBQUUsQ0FBQztJQUMxQyxXQUFXLENBQUMsT0FBTyxDQUFDLENBQUMsTUFBbUIsRUFBRSxFQUFFO1FBQzNDLElBQUksY0FBYyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUNoQyxNQUFNLElBQUksS0FBSyxDQUFDLG9EQUFvRCxNQUFNLENBQUMsSUFBSSxHQUFHLENBQUMsQ0FBQztTQUNwRjtRQUNELGNBQWMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsTUFBTSxDQUFDO0lBQ3RDLENBQUMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxzQkFBc0IsR0FBbUMsRUFBRSxDQUFDO0lBQ2xFLFdBQVcsQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFtQixFQUFFLEVBQUU7UUFDM0Msc0JBQXNCLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQztRQUMzQyxNQUFNLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxVQUFVLGNBQWM7WUFDL0Msc0JBQXNCLENBQUMsY0FBYyxDQUFDLEdBQUcsSUFBSSxDQUFDO1FBQy9DLENBQUMsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxDQUFDLE9BQU8sRUFBRSxPQUFPLENBQUMsVUFBVSxjQUFjO1lBQy9DLHNCQUFzQixDQUFDLGNBQWMsQ0FBQyxHQUFHLElBQUksQ0FBQztRQUMvQyxDQUFDLENBQUMsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0lBR0gsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSx3QkFBd0IsQ0FBQyxDQUFDLENBQUM7SUFDeEYsTUFBTSxDQUFDLEdBQWtCLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyx3Q0FBd0MsR0FBRyxJQUFJLEdBQUcsT0FBTyxDQUFDLENBQUM7SUFDeEcsTUFBTSxZQUFZLEdBQUcsRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFLENBQUM7SUFDckMsQ0FBQyxDQUFDLElBQUksQ0FBQyxFQUFFLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRSxZQUFZLENBQUMsT0FBTyxDQUFDLENBQUM7SUFFeEQsTUFBTSxNQUFNLEdBQVEsWUFBWSxDQUFDLE9BQU8sQ0FBQztJQUN6QyxNQUFNLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztJQUN0QixNQUFNLENBQUMsS0FBSyxHQUFHLE1BQU0sQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDO0lBQ2xDLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxFQUFFO1FBQzVCLE1BQU0sQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsd0JBQXdCLENBQUM7S0FDbEQ7SUFDRCxJQUFJLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsRUFBRTtRQUM1QixNQUFNLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxHQUFHLHdCQUF3QixDQUFDO0tBQ2xEO0lBQ0QsTUFBTSxDQUFDLHVCQUF1QixHQUFHLE1BQU0sQ0FBQyx1QkFBdUIsSUFBSSxFQUFFLENBQUM7SUFDdEUsTUFBTSxDQUFDLHVCQUF1QixDQUFDLFFBQVEsQ0FBQyxHQUFHLElBQUksQ0FBQztJQUNoRCxNQUFNLENBQUMsdUJBQXVCLENBQUMsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDO0lBQ2hELE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUM7SUFFdEIsTUFBTSxDQUFDLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxZQUFpQixFQUFFLEVBQUU7UUFDekMsTUFBTSxXQUFXLEdBQUcsQ0FBQyxLQUFpQixFQUFFLEVBQUU7WUFDekMsSUFBSSxDQUFDLEdBQUcsWUFBWSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDdkMsSUFBSSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLEVBQUU7Z0JBQ3ZCLENBQUMsSUFBSSxLQUFLLENBQUM7YUFDWDtZQUNELGdEQUFnRDtZQUNoRCxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsRUFBRSxXQUFXLENBQUMsQ0FBQztZQUM5QyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsRUFBRSxXQUFXLENBQUMsQ0FBQztZQUM5QyxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxXQUFXLEVBQUUsS0FBSyxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ3BELENBQUMsQ0FBQztRQUNGLEtBQUssTUFBTSxRQUFRLElBQUksY0FBYyxFQUFFO1lBQ3RDLE1BQU0sVUFBVSxHQUFHLGNBQWMsQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUM1QyxJQUFJLFVBQVUsQ0FBQyxNQUFNLEVBQUU7Z0JBQ3RCLFVBQVUsQ0FBQyxNQUFNLEdBQUcsVUFBVSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUM7YUFDdkQ7WUFDRCxJQUFJLFVBQVUsQ0FBQyxPQUFPLEVBQUU7Z0JBQ3ZCLFVBQVUsQ0FBQyxPQUFPLEdBQUcsVUFBVSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUM7YUFDekQ7U0FDRDtJQUNGLENBQUMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsc0JBQXNCLENBQUMsRUFBRSxHQUFHLEVBQUU7UUFDaEQsTUFBTSxPQUFPLEdBQXVCLE1BQU0sQ0FBQyxZQUFZLEVBQUUsQ0FBQztRQUMxRCxNQUFNLGFBQWEsR0FBRyxlQUFlLENBQUMsT0FBTyxFQUFFLGNBQWMsQ0FBQyxDQUFDO1FBQy9ELE1BQU0sbUJBQW1CLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDLG1CQUFtQixFQUFFLENBQUM7UUFDbkUsUUFBUSxDQUFDLElBQUksRUFBRTtZQUNkLEtBQUssRUFBRSxhQUFhLENBQUMsS0FBSztZQUMxQixtQkFBbUIsRUFBRSxtQkFBbUI7WUFDeEMsVUFBVSxFQUFFLGFBQWEsQ0FBQyxVQUFVO1NBQ3BDLENBQUMsQ0FBQztJQUNKLENBQUMsRUFBRSxDQUFDLEdBQVEsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0FBQ3ZDLENBQUM7QUF4RUQsd0JBd0VDO0FBRUQsU0FBUyxlQUFlLENBQUMsT0FBMkIsRUFBRSxXQUEyQjtJQUNoRixNQUFNLFVBQVUsR0FBd0IsRUFBRSxDQUFDO0lBQzNDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFtQixFQUFFLEVBQUU7UUFDdkMsVUFBVSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDdEIsQ0FBQyxDQUFDLENBQUM7SUFFSCxNQUFNLFlBQVksR0FBVyxFQUFFLENBQUM7SUFDaEMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQW1CLEVBQUUsRUFBRTtRQUN2QyxZQUFZLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxZQUFZLENBQUM7SUFDckMsQ0FBQyxDQUFDLENBQUM7SUFFSCxNQUFNLGFBQWEsR0FBRyxlQUFlLENBQUMsWUFBWSxDQUFDLENBQUM7SUFFcEQsSUFBSSxNQUFNLEdBQWtCLEVBQUUsQ0FBQztJQUMvQixNQUFNLFdBQVcsR0FBZSxFQUFFLENBQUM7SUFDbkMsTUFBTSxVQUFVLEdBQWdCO1FBQy9CLEtBQUssRUFBRSxZQUFZO1FBQ25CLE9BQU8sRUFBRSxFQUFFO0tBQ1gsQ0FBQztJQUVGLE1BQU0sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsY0FBc0IsRUFBRSxFQUFFO1FBQzNELE1BQU0sSUFBSSxHQUFHLFdBQVcsQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUN6QyxNQUFNLFNBQVMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzlELE1BQU0sZUFBZSxHQUFHLEtBQUssQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFDdkQsTUFBTSxRQUFRLEdBQWEsQ0FBQyxTQUFTLEVBQUUsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBRXZGLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxXQUFtQixFQUFFLEVBQUU7WUFDeEMsTUFBTSxXQUFXLEdBQUcsS0FBSyxDQUFDLENBQUMsV0FBVyxDQUFDLEVBQUUsWUFBWSxDQUFDLENBQUM7WUFDdkQsTUFBTSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxPQUFlLEVBQUUsRUFBRTtnQkFDcEQsT0FBTyxlQUFlLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDakMsQ0FBQyxDQUFDLENBQUM7UUFDSixDQUFDLENBQUMsQ0FBQztRQUVILE1BQU0sZUFBZSxHQUFHLGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFjLEVBQUUsRUFBRTtZQUMvRCxPQUFPLGVBQWUsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQyxDQUFDLENBQUMsQ0FBQztRQUVILFVBQVUsQ0FBQyxPQUFPLENBQUMsY0FBYyxDQUFDLEdBQUcsZUFBZSxDQUFDO1FBRXJELE1BQU0sR0FBRyxHQUFHLGNBQWMsQ0FDekIsVUFBVSxFQUNWLFlBQVksRUFDWixjQUFjLEVBQ2QsZUFBZSxFQUNmLElBQUksQ0FBQyxPQUFPLElBQUksRUFBRSxFQUNsQixJQUFJLENBQUMsTUFBTSxJQUFJLEVBQUUsRUFDakIsSUFBSSxDQUFDLElBQUksQ0FDVCxDQUFDO1FBRUYsTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2xDLEtBQUssTUFBTSxVQUFVLElBQUksR0FBRyxDQUFDLFdBQVcsRUFBRTtZQUN6QyxXQUFXLENBQUMsVUFBVSxDQUFDLEdBQUcsV0FBVyxDQUFDLFVBQVUsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7U0FDakY7SUFDRixDQUFDLENBQUMsQ0FBQztJQUVILE1BQU0sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsVUFBa0IsRUFBRSxFQUFFO1FBQ3ZELE1BQU0sTUFBTSxHQUFHLFdBQVcsQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUN2QyxJQUFJLE9BQU8sTUFBTSxDQUFDLFdBQVcsS0FBSyxVQUFVLEVBQUU7WUFDN0MsTUFBTSxLQUFLLEdBQUcsQ0FBQyxRQUFnQixFQUFFLFFBQWdCLEVBQUUsRUFBRTtnQkFDcEQsTUFBTSxDQUFDLElBQUksQ0FBQztvQkFDWCxJQUFJLEVBQUUsUUFBUTtvQkFDZCxPQUFPLEVBQUUsQ0FBQzs0QkFDVCxJQUFJLEVBQUUsSUFBSTs0QkFDVixRQUFRLEVBQUUsUUFBUTt5QkFDbEIsQ0FBQztpQkFDRixDQUFDLENBQUM7WUFDSixDQUFDLENBQUM7WUFDRixNQUFNLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDO1NBQzFCO0lBQ0YsQ0FBQyxDQUFDLENBQUM7SUFFSCxPQUFPO1FBQ04sZ0JBQWdCO1FBQ2hCLEtBQUssRUFBRSxjQUFjLENBQUMsNEJBQTRCLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDM0QsVUFBVSxFQUFFLFVBQVU7S0FDdEIsQ0FBQztBQUNILENBQUM7QUFFRCxTQUFTLGNBQWMsQ0FBQyxTQUF3QjtJQUMvQyxNQUFNLGVBQWUsR0FBRyxDQUFDLFdBQW1CLEVBQUUsU0FBaUIsRUFBRSxFQUFFO1FBQ2xFLE1BQU0sTUFBTSxHQUFHLFdBQVcsQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ2pELElBQUksSUFBSSxHQUFHLFNBQVMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDaEMsSUFBSSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRTtZQUN2QixHQUFHLEdBQUcsR0FBRyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ2pCLEdBQUcsR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLFFBQVEsRUFBRSxFQUFFLENBQUMsQ0FBQztZQUNoQyxHQUFHLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDaEMsSUFBSSxNQUFNLEdBQWtCLElBQUksQ0FBQztZQUNqQyxJQUFJLEtBQUssR0FBa0IsSUFBSSxDQUFDO1lBQ2hDLE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDOUIsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtnQkFDdEIsTUFBTSxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUM7Z0JBQ3pCLEtBQUssR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7YUFDbEI7aUJBQU07Z0JBQ04sTUFBTSxHQUFHLEVBQUUsQ0FBQztnQkFDWixLQUFLLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO2FBQ2xCO1lBRUQsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLFNBQVMsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUU7Z0JBQ2pELE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO2dCQUN2RSxPQUFPLE1BQU0sR0FBRyxHQUFHLENBQUM7YUFDcEI7WUFDRCxPQUFPLE1BQU0sR0FBRyxLQUFLLENBQUM7UUFDdkIsQ0FBQyxDQUFDLENBQUM7UUFDSCxPQUFPO1lBQ04sTUFBTSxFQUFFLE1BQU07WUFDZCxJQUFJLEVBQUUsSUFBSTtTQUNWLENBQUM7SUFDSCxDQUFDLENBQUM7SUFFRixTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7UUFDOUIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFO1lBQ2pDLE9BQU87U0FDUDtRQUNELElBQUksWUFBWSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFDckMsT0FBTztTQUNQO1FBRUQsNERBQTREO1FBQzVELE1BQU0sU0FBUyxHQUFtQyxFQUFFLENBQUM7UUFDckQsUUFBUSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtZQUNuQyxNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxxREFBcUQsQ0FBQyxDQUFDO1lBQzdGLElBQUksQ0FBQyxPQUFPLEVBQUU7Z0JBQ2IsT0FBTzthQUNQO1lBRUQsTUFBTSxVQUFVLEdBQUcsZUFBZSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUMzRCxTQUFTLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDdkUsVUFBVSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxHQUFHLEVBQUUsRUFBRTtnQkFDL0IsU0FBUyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUM1QyxDQUFDLENBQUMsQ0FBQztRQUNKLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxrQkFBa0IsR0FBRyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ2xELGtCQUFrQixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUNoQyxPQUFPLFNBQVMsQ0FBQyxDQUFDLENBQUMsR0FBRyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDcEMsQ0FBQyxDQUFDLENBQUM7UUFFSCxNQUFNLGNBQWMsR0FBbUMsRUFBRSxDQUFDO1FBQzFELGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsRUFBRTtZQUM1QyxjQUFjLENBQUMsTUFBTSxDQUFDLEdBQUcsS0FBSyxDQUFDO1FBQ2hDLENBQUMsQ0FBQyxDQUFDO1FBRUgsUUFBUSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtZQUNuQyxNQUFNLENBQUMsUUFBUSxHQUFHLE1BQU0sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLHFEQUFxRCxFQUFFLENBQUMsQ0FBQyxFQUFFLFdBQVcsRUFBRSxTQUFTLEVBQUUsRUFBRTtnQkFDOUgsTUFBTSxVQUFVLEdBQUcsZUFBZSxDQUFDLFdBQVcsRUFBRSxTQUFTLENBQUMsQ0FBQztnQkFDM0QsT0FBTyxjQUFjLGNBQWMsQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLEtBQUssVUFBVSxDQUFDLE1BQU0sYUFBYSxVQUFVLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsR0FBRyxJQUFJLEdBQUcsR0FBRyxHQUFHLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDO1lBQzVLLENBQUMsQ0FBQyxDQUFDO1FBQ0osQ0FBQyxDQUFDLENBQUM7UUFFSCxRQUFRLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQztZQUN4QixJQUFJLEVBQUUsSUFBSTtZQUNWLFFBQVEsRUFBRTtnQkFDVCxlQUFlO2dCQUNmLGFBQWEsSUFBSSxDQUFDLFNBQVMsQ0FBQyxrQkFBa0IsQ0FBQyxHQUFHO2dCQUNsRCw0QkFBNEI7Z0JBQzVCLG9CQUFvQjtnQkFDcEIsc0RBQXNEO2dCQUN0RCwrQkFBK0I7Z0JBQy9CLEtBQUs7Z0JBQ0wsa0JBQWtCO2dCQUNsQixJQUFJO2FBQ0osQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO1NBQ1osQ0FBQyxDQUFDO1FBRUgsUUFBUSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUM7WUFDckIsSUFBSSxFQUFFLElBQUk7WUFDVixRQUFRLEVBQUUsZ0JBQWdCO1NBQzFCLENBQUMsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxTQUFTLENBQUM7QUFDbEIsQ0FBQztBQUVELFNBQVMsNEJBQTRCLENBQUMsU0FBd0I7SUFDN0QsOENBQThDO0lBQzlDLE1BQU0sV0FBVyxHQUFHO1FBQ25CLEVBQUUsS0FBSyxFQUFFLGdCQUFnQixFQUFFLEdBQUcsRUFBRSxZQUFZLEVBQUU7UUFDOUMsRUFBRSxLQUFLLEVBQUUsZUFBZSxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUU7UUFDdkMsRUFBRSxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRTtRQUN6QyxFQUFFLEtBQUssRUFBRSxpQkFBaUIsRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFO1FBQ3pDLEVBQUUsS0FBSyxFQUFFLGNBQWMsRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFO1FBQ3RDLEVBQUUsS0FBSyxFQUFFLGdCQUFnQixFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUU7UUFDeEMsRUFBRSxLQUFLLEVBQUUsa0JBQWtCLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRTtLQUMxQyxDQUFDO0lBRUYsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDLFFBQVEsRUFBRSxFQUFFO1FBQzlCLE1BQU0sZ0JBQWdCLEdBQWMsRUFBRSxDQUFDO1FBQ3ZDLFFBQVEsQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUU7WUFDbkMsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUM7WUFDbEQsTUFBTSxRQUFRLEdBQWEsRUFBRSxDQUFDO1lBQzlCLElBQUksdUJBQXVCLEdBQUcsS0FBSyxFQUFFLGVBQXVCLENBQUM7WUFFN0QsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEtBQUssQ0FBQyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUU7Z0JBQ3RDLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDdEIsSUFBSSx1QkFBdUIsRUFBRTtvQkFDNUIsUUFBUSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztvQkFDbEIsSUFBSSxlQUFnQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRTt3QkFDaEMsdUJBQXVCLEdBQUcsS0FBSyxDQUFDO3FCQUNoQztpQkFDRDtxQkFBTTtvQkFDTixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsV0FBVyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTt3QkFDNUMsTUFBTSxXQUFXLEdBQUcsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDO3dCQUNuQyxJQUFJLFdBQVcsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFOzRCQUNqQyxJQUFJLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxFQUFFO2dDQUN4Qix1QkFBdUIsR0FBRyxJQUFJLENBQUM7Z0NBQy9CLGVBQWUsR0FBRyxXQUFXLENBQUMsR0FBRyxDQUFDOzZCQUNsQztpQ0FBTTtnQ0FDTixnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsR0FBRyxJQUFJLENBQUM7NkJBQzNCO3lCQUNEO3FCQUNEO29CQUNELElBQUksdUJBQXVCLEVBQUU7d0JBQzVCLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7cUJBQ2xCO3lCQUFNO3dCQUNOLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7cUJBQ3BCO2lCQUNEO2FBQ0Q7WUFDRCxNQUFNLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkMsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDLENBQUMsQ0FBQztJQUVILE9BQU8sU0FBUyxDQUFDO0FBQ2xCLENBQUM7QUFXRCxTQUFTLGNBQWMsQ0FDdEIsVUFBK0IsRUFDL0IsSUFBWSxFQUNaLFVBQWtCLEVBQ2xCLGVBQXlCLEVBQ3pCLE9BQXFCLEVBQ3JCLE1BQW9CLEVBQ3BCLElBQXdCO0lBRXhCLElBQUksQ0FBQyxJQUFJLEVBQUU7UUFDVixJQUFJLEdBQUcsVUFBVSxHQUFHLEtBQUssQ0FBQztLQUMxQjtJQUNELE1BQU0sVUFBVSxHQUFnQjtRQUMvQixPQUFPLEVBQUUsRUFBRTtRQUNYLElBQUksRUFBRSxJQUFJO0tBQ1YsRUFDQSxPQUFPLEdBQWtCLENBQUMsVUFBVSxDQUFDLENBQUM7SUFFdkMsTUFBTSxXQUFXLEdBQWUsRUFBRSxDQUFDO0lBQ25DLE1BQU0sZUFBZSxHQUFHLENBQUMsVUFBa0IsRUFBaUIsRUFBRTtRQUM3RCxJQUFJLENBQUMsV0FBVyxDQUFDLFVBQVUsQ0FBQyxFQUFFO1lBQzdCLFdBQVcsQ0FBQyxVQUFVLENBQUMsR0FBRyxVQUFVLENBQUMsVUFBVSxDQUFDLENBQUMsT0FBTyxDQUFDO1NBQ3pEO1FBQ0QsT0FBTyxXQUFXLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDaEMsQ0FBQyxDQUFDO0lBRUYsZUFBZSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQVMsRUFBRSxFQUFFO1FBQ3JDLE1BQU0sU0FBUyxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFakMsSUFBSSxTQUFTLElBQUksQ0FBQyxFQUFFO1lBQ25CLE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDO1lBQzFDLE1BQU0sTUFBTSxHQUFHLGVBQWUsQ0FBQyxVQUFVLENBQUMsQ0FBQztZQUMzQyxVQUFVLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsVUFBVSxFQUFFLE1BQU0sRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzdGLE9BQU87U0FDUDtRQUVELE1BQU0sTUFBTSxHQUFHLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUU3QixJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssUUFBUSxFQUFFO1lBQzdCLE9BQU87U0FDUDtRQUVELE1BQU0sUUFBUSxHQUFHLG9CQUFvQixDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVuRCxJQUFJLE1BQU0sQ0FBQyxJQUFJLEVBQUU7WUFDaEIsVUFBVSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUMsQ0FBQztTQUMzRjthQUFNLElBQUksTUFBTSxDQUFDLGNBQWMsRUFBRTtZQUNqQyxVQUFVLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxlQUFlLENBQUMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxjQUFjLEVBQUUsTUFBTSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDO1NBQzFGO2FBQU07WUFDTixNQUFNLFVBQVUsR0FBRztnQkFDbEIsRUFBRSxFQUFFLE1BQU0sQ0FBQyxFQUFFO2dCQUNiLElBQUksRUFBRSxNQUFNLENBQUMsSUFBSTtnQkFDakIsY0FBYyxFQUFFLE1BQU0sQ0FBQyxjQUFjO2dCQUNyQyxZQUFZLEVBQUUsTUFBTSxDQUFDLFlBQVk7YUFDakMsQ0FBQztZQUNGLE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLE1BQU0sQ0FBQyxFQUFFLHNCQUFzQixVQUFVLDJEQUEyRCxJQUFJLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsQ0FBQztTQUMzSztJQUNGLENBQUMsQ0FBQyxDQUFDO0lBRUgsTUFBTSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxVQUFrQixFQUFFLEVBQUU7UUFDdkQsTUFBTSxNQUFNLEdBQUcsV0FBVyxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ3ZDLElBQUksT0FBTyxNQUFNLENBQUMsU0FBUyxLQUFLLFVBQVUsRUFBRTtZQUMzQyxNQUFNLEdBQUcsR0FBOEIsQ0FBQyxHQUFHLEVBQUU7Z0JBQzVDLE1BQU0sSUFBSSxLQUFLLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDM0IsQ0FBQyxDQUFDLENBQUM7WUFDSCxHQUFHLENBQUMsS0FBSyxHQUFHLFNBQVMsQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDO1lBRW5DLE1BQU0sS0FBSyxHQUFHLENBQUMsUUFBZ0IsRUFBRSxRQUFnQixFQUFFLEVBQUU7Z0JBQ3BELE9BQU8sQ0FBQyxJQUFJLENBQUM7b0JBQ1osSUFBSSxFQUFFLFFBQVE7b0JBQ2QsT0FBTyxFQUFFLENBQUM7NEJBQ1QsSUFBSSxFQUFFLElBQUk7NEJBQ1YsUUFBUSxFQUFFLFFBQVE7eUJBQ2xCLENBQUM7aUJBQ0YsQ0FBQyxDQUFDO1lBQ0osQ0FBQyxDQUFDO1lBQ0YsTUFBTSxDQUFDLFNBQVMsQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7U0FDekQ7SUFDRixDQUFDLENBQUMsQ0FBQztJQUVILE1BQU0sT0FBTyxHQUFHLENBQUMsS0FBaUIsRUFBUyxFQUFFO1FBQzVDLElBQUksUUFBUSxHQUFHLG9CQUFvQixDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNoRCxJQUFJLEtBQUssQ0FBQyxXQUFXLEVBQUU7WUFDdEIsUUFBUSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMsWUFBWSxFQUFFLFdBQVcsS0FBSyxDQUFDLFdBQVcsSUFBSSxDQUFDLENBQUM7U0FDNUU7UUFDRCxPQUFPO1lBQ04sSUFBSSxFQUFFLEtBQUssQ0FBQyxJQUFJO1lBQ2hCLFFBQVEsRUFBRSxRQUFRO1NBQ2xCLENBQUM7SUFDSCxDQUFDLENBQUM7SUFFRixNQUFNLFNBQVMsR0FBRyxDQUFDLE9BQU8sSUFBSSxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDL0MsTUFBTSxRQUFRLEdBQUcsQ0FBQyxNQUFNLElBQUksRUFBRSxDQUFDLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBRTdDLFVBQVUsQ0FBQyxPQUFPLEdBQUcsU0FBUyxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBRTNFLE9BQU87UUFDTixLQUFLLEVBQUUsT0FBTztRQUNkLFdBQVcsRUFBRSxXQUFXO0tBQ3hCLENBQUM7QUFDSCxDQUFDO0FBRUQsU0FBUyxvQkFBb0IsQ0FBQyxJQUFZO0lBQ3pDLE1BQU0sYUFBYSxHQUFHLEtBQUssQ0FBQztJQUM1QixJQUFJLFFBQVEsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztJQUM3QyxhQUFhO0lBQ2IsSUFBSSxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxLQUFLLGFBQWEsRUFBRTtRQUM3QyxRQUFRLEdBQUcsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztLQUNqQztJQUNELE9BQU8sUUFBUSxDQUFDO0FBQ2pCLENBQUM7QUFFRCxTQUFTLFVBQVUsQ0FBQyxVQUFrQixFQUFFLE1BQXFCLEVBQUUsVUFBa0IsRUFBRSxVQUFrQjtJQUNwRyxJQUFJLE1BQU0sR0FBRyxFQUFFLENBQUM7SUFDaEIsSUFBSSxPQUFPLE1BQU0sQ0FBQyxLQUFLLEtBQUssVUFBVSxFQUFFO1FBQ3ZDLE1BQU0sS0FBSyxHQUFnQyxDQUFDLENBQUMsSUFBWSxFQUFFLEVBQUU7WUFDNUQsTUFBTSxJQUFJLElBQUksQ0FBQztRQUNoQixDQUFDLENBQUMsQ0FBQztRQUNILEtBQUssQ0FBQyxhQUFhLEdBQUcsR0FBRyxFQUFFO1lBQzFCLE9BQU8sVUFBVSxDQUFDO1FBQ25CLENBQUMsQ0FBQztRQUNGLEtBQUssQ0FBQyxRQUFRLEdBQUcsQ0FBQyxRQUFnQixFQUFFLElBQVksRUFBRSxFQUFFO1lBQ25ELElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFdBQVcsRUFBRSxVQUFVLEdBQUcsUUFBUSxHQUFHLElBQUksQ0FBQyxDQUFDO1lBQy9ELE1BQU0sSUFBSSxJQUFJLENBQUM7UUFDaEIsQ0FBQyxDQUFDO1FBQ0YsTUFBTSxDQUFDLEtBQUssQ0FBQyxVQUFVLEVBQUUsVUFBVSxFQUFFLEtBQUssQ0FBQyxDQUFDO0tBQzVDO0lBQ0QsT0FBTztRQUNOLElBQUksRUFBRSxJQUFJO1FBQ1YsUUFBUSxFQUFFLE1BQU07S0FDaEIsQ0FBQztBQUNILENBQUM7QUFFRCxTQUFTLGVBQWUsQ0FBQyxRQUFnQixFQUFFLGtCQUE2QixFQUFFLElBQVksRUFBRSxRQUFnQjtJQUV2RywwREFBMEQ7SUFDMUQsTUFBTSxnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsa0JBQWtCLENBQUMsSUFBSSxFQUFFLGtCQUFrQixDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBRXJHLG9EQUFvRDtJQUNwRCxNQUFNLFlBQVksR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLEdBQUcsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO0lBRTdELE1BQU0sU0FBUyxHQUFHLEdBQUcsR0FBRyxRQUFRLEdBQUcsS0FBSyxDQUFDO0lBRXpDLE9BQU87UUFDTixJQUFJLEVBQUUsSUFBSTtRQUNWLFFBQVEsRUFBRSxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxZQUFZLEdBQUcsQ0FBQyxDQUFDLEdBQUcsU0FBUyxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsWUFBWSxHQUFHLENBQUMsQ0FBQztLQUM5RixDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsaUJBQWlCLENBQUMsUUFBZ0IsRUFBRSxNQUFnQixFQUFFLE9BQWUsRUFBRSxJQUFZLEVBQUUsUUFBZ0I7SUFDN0csTUFBTSxPQUFPLEdBQUcsQ0FBQyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUMzRSxNQUFNLFNBQVMsR0FBRyxVQUFVLEdBQUcsUUFBUSxHQUFHLE1BQU0sR0FBRyxPQUFPLEdBQUcsS0FBSyxHQUFHLE9BQU8sR0FBRyxJQUFJLENBQUM7SUFDcEYsT0FBTztRQUNOLElBQUksRUFBRSxJQUFJO1FBQ1YsUUFBUSxFQUFFLFFBQVEsR0FBRyxPQUFPLEdBQUcsU0FBUztLQUN4QyxDQUFDO0FBQ0gsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFXLEVBQUUsV0FBbUIsRUFBRSxVQUFrQjtJQUM3RSxJQUFJLFdBQVcsS0FBSyxDQUFDLEVBQUU7UUFDdEIsT0FBTyxVQUFVLEdBQUcsQ0FBQyxDQUFDO0tBQ3RCO0lBRUQsSUFBSSxJQUFJLEdBQUcsQ0FBQyxDQUFDO0lBQ2IsSUFBSSxpQkFBaUIsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUUzQixHQUFHO1FBQ0YsSUFBSSxXQUFXLEtBQUssSUFBSSxFQUFFO1lBQ3pCLE9BQU8saUJBQWlCLEdBQUcsQ0FBQyxHQUFHLFVBQVUsR0FBRyxDQUFDLENBQUM7U0FDOUM7UUFDRCxpQkFBaUIsR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxpQkFBaUIsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUM3RCxJQUFJLEVBQUUsQ0FBQztLQUNQLFFBQVEsaUJBQWlCLElBQUksQ0FBQyxFQUFFO0lBRWpDLE9BQU8sQ0FBQyxDQUFDLENBQUM7QUFDWCxDQUFDO0FBR0Q7O0dBRUc7QUFDSCxTQUFTLEtBQUssQ0FBQyxTQUFtQixFQUFFLEtBQWE7SUFDaEQsTUFBTSxNQUFNLEdBQWEsRUFBRSxDQUFDO0lBQzVCLE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBQztJQUV4QixTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUU7UUFDMUIsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQztJQUNyQixDQUFDLENBQUMsQ0FBQztJQUVILE9BQU8sS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUU7UUFDeEIsTUFBTSxFQUFFLEdBQUcsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3pCLE1BQU0sT0FBTyxHQUFHLEtBQUssQ0FBQyxFQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDakMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFO1lBQzFCLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUU7Z0JBQ3BCLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxJQUFJLENBQUM7Z0JBQ3RCLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7YUFDbkI7UUFDRixDQUFDLENBQUMsQ0FBQztLQUNIO0lBRUQsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDO0FBRUQ7O0dBRUc7QUFDSCxTQUFTLGVBQWUsQ0FBQyxLQUFhO0lBRXJDLE1BQU0sUUFBUSxHQUFhLEVBQUUsRUFDNUIsaUJBQWlCLEdBQStCLEVBQUUsRUFDbEQsWUFBWSxHQUFXLEVBQUUsQ0FBQztJQUUzQixNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLFFBQWdCLEVBQUUsRUFBRTtRQUMvQyxRQUFRLENBQUMsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDO1FBQzFCLGlCQUFpQixDQUFDLFFBQVEsQ0FBQyxHQUFHLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxNQUFNLENBQUM7UUFFckQsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLE1BQU0sRUFBRSxFQUFFO1lBQ2xDLFFBQVEsQ0FBQyxNQUFNLENBQUMsR0FBRyxJQUFJLENBQUM7WUFDeEIsaUJBQWlCLENBQUMsTUFBTSxDQUFDLEdBQUcsaUJBQWlCLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBRTNELFlBQVksQ0FBQyxNQUFNLENBQUMsR0FBRyxZQUFZLENBQUMsTUFBTSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ2xELFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDckMsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDLENBQUMsQ0FBQztJQUVILG9EQUFvRDtJQUNwRCxNQUFNLENBQUMsR0FBYSxFQUFFLEVBQ3JCLENBQUMsR0FBYSxFQUFFLENBQUM7SUFFbEIsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFZLEVBQUUsRUFBRTtRQUM5QyxJQUFJLGlCQUFpQixDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsRUFBRTtZQUNsQyxPQUFPLGlCQUFpQixDQUFDLElBQUksQ0FBQyxDQUFDO1lBQy9CLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDYjtJQUNGLENBQUMsQ0FBQyxDQUFDO0lBRUgsT0FBTyxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtRQUNwQixnRUFBZ0U7UUFDaEUsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1FBRVQsTUFBTSxDQUFDLEdBQVcsQ0FBQyxDQUFDLEtBQUssRUFBRyxDQUFDO1FBQzdCLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFVixNQUFNLGNBQWMsR0FBRyxZQUFZLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1FBQzdDLGNBQWMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFTLEVBQUUsRUFBRTtZQUNwQyxpQkFBaUIsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO1lBQ3ZCLElBQUksaUJBQWlCLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxFQUFFO2dCQUMvQixPQUFPLGlCQUFpQixDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUM1QixDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO2FBQ1Y7UUFDRixDQUFDLENBQUMsQ0FBQztLQUNIO0lBRUQsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRTtRQUM5QyxNQUFNLElBQUksS0FBSyxDQUFDLCtEQUErRCxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDO0tBQ2xIO0lBRUQsT0FBTyxDQUFDLENBQUM7QUFDVixDQUFDIn0= \ No newline at end of file diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 02d21549e8..c5332546c9 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -18,7 +18,10 @@ const ansiColors = require("ansi-colors"); const os = require("os"); const File = require("vinyl"); const task = require("./task"); +// import { Mangler } from './mangleTypeScript'; +// import { RawSourceMap } from 'source-map'; const watch = require('./watch'); +// --- gulp-tsb: compile and transpile -------------------------------- const reporter = (0, reporter_1.createReporter)(); function getTypeScriptCompilerOptions(src) { const rootDir = path.join(__dirname, `../../${src}`); @@ -52,28 +55,32 @@ function createCompile(src, build, emitError, transpileOnly) { console.warn('* and re-run the build/watch task *'); console.warn('********************************************************************************************'); } - const compilation = tsb.create(projectPath, overrideOptions, { verbose: false, transpileOnly }, err => reporter(err)); + const compilation = tsb.create(projectPath, overrideOptions, { + verbose: false, + transpileOnly: Boolean(transpileOnly), + transpileWithSwc: typeof transpileOnly !== 'boolean' && transpileOnly.swc + }, err => reporter(err)); function pipeline(token) { const bom = require('gulp-bom'); - const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path)); const tsFilter = util.filter(data => /\.ts$/.test(data.path)); + const isUtf8Test = (f) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); + const isRuntimeJs = (f) => f.path.endsWith('.js') && !f.path.includes('fixtures'); const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); const input = es.through(); const output = input - .pipe(utf8Filter) - .pipe(bom()) // this is required to preserve BOM in test files that loose it otherwise - .pipe(utf8Filter.restore) + .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise + .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) .pipe(tsFilter) .pipe(util.loadSourcemaps()) .pipe(compilation(token)) .pipe(noDeclarationsFilter) - .pipe(build ? nls.nls() : es.through()) + .pipe(util.$if(build, nls.nls())) .pipe(noDeclarationsFilter.restore) - .pipe(transpileOnly ? es.through() : sourcemaps.write('.', { + .pipe(util.$if(!transpileOnly, sourcemaps.write('.', { addComment: false, includeContent: !!build, sourceRoot: overrideOptions.sourceRoot - })) + }))) .pipe(tsFilter.restore) .pipe(reporter.end(!!emitError)); return es.duplex(input, output); @@ -81,11 +88,12 @@ function createCompile(src, build, emitError, transpileOnly) { pipeline.tsProjectSrc = () => { return compilation.src({ base: src }); }; + pipeline.projectPath = projectPath; return pipeline; } -function transpileTask(src, out) { +function transpileTask(src, out, swc) { return function () { - const transpile = createCompile(src, false, true, true); + const transpile = createCompile(src, false, true, { swc }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); return srcPipe .pipe(transpile()) @@ -93,8 +101,9 @@ function transpileTask(src, out) { }; } exports.transpileTask = transpileTask; -function compileTask(src, out, build) { +function compileTask(src, out, build, options = {}) { return function () { + options.disableMangle = true; // {{SQL CARBON EDIT}} - disable mangling if (os.totalmem() < 4000000000) { throw new Error('compilation requires 4GB of RAM'); } @@ -104,7 +113,29 @@ function compileTask(src, out, build) { if (src === 'src') { generator.execute(); } + // mangle: TypeScript to TypeScript + // let mangleStream = es.through(); + // if (build && !options.disableMangle) { + // let ts2tsMangler = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data)); + // const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState'])); + // mangleStream = es.through(function write(data: File & { sourceMap?: RawSourceMap }) { + // type TypeScriptExt = typeof ts & { normalizePath(path: string): string }; + // const tsNormalPath = (ts).normalizePath(data.path); + // const newContents = newContentsByFileName.get(tsNormalPath); + // if (newContents !== undefined) { + // data.contents = Buffer.from(newContents.out); + // data.sourceMap = newContents.sourceMap && JSON.parse(newContents.sourceMap); + // } + // this.push(data); + // }, function end() { + // this.push(null); + // // free resources + // newContentsByFileName.clear(); + // (ts2tsMangler) = undefined; + // }); + // } return srcPipe + //.pipe(mangleStream) .pipe(generator.stream) .pipe(compile()) .pipe(gulp.dest(out)); @@ -127,8 +158,12 @@ function watchTask(out, build) { exports.watchTask = watchTask; const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); class MonacoGenerator { + _isWatch; + stream; + _watchedFiles; + _fsProvider; + _declarationResolver; constructor(isWatch) { - this._executeSoonTimer = null; this._isWatch = isWatch; this.stream = es.through(); this._watchedFiles = {}; @@ -158,6 +193,7 @@ class MonacoGenerator { }); } } + _executeSoonTimer = null; _executeSoon() { if (this._executeSoonTimer !== null) { clearTimeout(this._executeSoonTimer); @@ -229,7 +265,7 @@ function generateApiProposalNames() { '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', '', 'export const allApiProposals = Object.freeze({', - `${names.map(name => `\t${name}: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${name}.d.ts'`).join(`,${os.EOL}`)}`, + `${names.map(name => `\t${name}: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${name}.d.ts'`).join(`,${eol}`)}`, '});', 'export type ApiProposalName = keyof typeof allApiProposals;', '', @@ -257,3 +293,4 @@ exports.watchApiProposalNamesTask = task.define('watch-api-proposal-names', () = .pipe(util.debounce(task)) .pipe(gulp.dest('src')); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tcGlsYXRpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjb21waWxhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O2dHQUdnRztBQUVoRyxZQUFZLENBQUM7OztBQUViLG1DQUFtQztBQUNuQyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLDZCQUE2QjtBQUM3QiwwQ0FBMEM7QUFDMUMsNkJBQTZCO0FBQzdCLHlDQUE0QztBQUM1QywrQkFBK0I7QUFDL0Isc0NBQXNDO0FBQ3RDLDBDQUEwQztBQUMxQyx5QkFBeUI7QUFFekIsOEJBQThCO0FBQzlCLCtCQUErQjtBQUMvQixnREFBZ0Q7QUFDaEQsNkNBQTZDO0FBQzdDLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUdqQyx1RUFBdUU7QUFFdkUsTUFBTSxRQUFRLEdBQUcsSUFBQSx5QkFBYyxHQUFFLENBQUM7QUFFbEMsU0FBUyw0QkFBNEIsQ0FBQyxHQUFXO0lBQ2hELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLFNBQVMsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUNyRCxNQUFNLE9BQU8sR0FBdUIsRUFBRSxDQUFDO0lBQ3ZDLE9BQU8sQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO0lBQ3hCLE9BQU8sQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDO0lBQ3pCLElBQUksT0FBTyxDQUFDLEdBQUcsQ0FBQyxxQkFBcUIsQ0FBQyxFQUFFLEVBQUUsc0NBQXNDO1FBQy9FLE9BQU8sQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO0tBQzFCO0lBQ0QsT0FBTyxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDMUIsT0FBTyxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7SUFDMUIsT0FBTyxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQzdDLE9BQU8sQ0FBQyxPQUFPLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLFVBQVUsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUMzRSxPQUFPLE9BQU8sQ0FBQztBQUNoQixDQUFDO0FBRUQsU0FBUyxhQUFhLENBQUMsR0FBVyxFQUFFLEtBQWMsRUFBRSxTQUFrQixFQUFFLGFBQXlDO0lBQ2hILE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQTJCLENBQUM7SUFDdkQsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLGlCQUFpQixDQUFxQyxDQUFDO0lBR2xGLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLFFBQVEsRUFBRSxHQUFHLEVBQUUsZUFBZSxDQUFDLENBQUM7SUFDekUsTUFBTSxlQUFlLEdBQUcsRUFBRSxHQUFHLDRCQUE0QixDQUFDLEdBQUcsQ0FBQyxFQUFFLGFBQWEsRUFBRSxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztJQUNoRyxnSEFBZ0g7SUFDaEgsK0ZBQStGO0lBQy9GLElBQUksQ0FBQyxLQUFLLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLHlCQUF5QixDQUFDLEVBQUU7UUFDdEQsZUFBZSxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUM7S0FDdkM7U0FBTSxJQUFJLENBQUMsS0FBSyxFQUFFO1FBQ2xCLE9BQU8sQ0FBQyxJQUFJLENBQUMsOEZBQThGLENBQUMsQ0FBQztRQUM3RyxPQUFPLENBQUMsSUFBSSxDQUFDLDhGQUE4RixDQUFDLENBQUM7UUFDN0csT0FBTyxDQUFDLElBQUksQ0FBQyw4RkFBOEYsQ0FBQyxDQUFDO1FBQzdHLE9BQU8sQ0FBQyxJQUFJLENBQUMsOEZBQThGLENBQUMsQ0FBQztRQUM3RyxPQUFPLENBQUMsSUFBSSxDQUFDLDhGQUE4RixDQUFDLENBQUM7UUFDN0csT0FBTyxDQUFDLElBQUksQ0FBQyw4RkFBOEYsQ0FBQyxDQUFDO0tBQzdHO0lBR0QsTUFBTSxXQUFXLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxXQUFXLEVBQUUsZUFBZSxFQUFFO1FBQzVELE9BQU8sRUFBRSxLQUFLO1FBQ2QsYUFBYSxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUM7UUFDckMsZ0JBQWdCLEVBQUUsT0FBTyxhQUFhLEtBQUssU0FBUyxJQUFJLGFBQWEsQ0FBQyxHQUFHO0tBQ3pFLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUV6QixTQUFTLFFBQVEsQ0FBQyxLQUErQjtRQUNoRCxNQUFNLEdBQUcsR0FBRyxPQUFPLENBQUMsVUFBVSxDQUE4QixDQUFDO1FBRTdELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQzlELE1BQU0sVUFBVSxHQUFHLENBQUMsQ0FBTyxFQUFFLEVBQUUsQ0FBQywwQkFBMEIsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3hFLE1BQU0sV0FBVyxHQUFHLENBQUMsQ0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQ3hGLE1BQU0sb0JBQW9CLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFFaEYsTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQzNCLE1BQU0sTUFBTSxHQUFHLEtBQUs7YUFDbEIsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyx5RUFBeUU7YUFDM0csSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLElBQUksV0FBVyxFQUFFLElBQUksQ0FBQyxzQkFBc0IsRUFBRSxDQUFDLENBQUM7YUFDcEUsSUFBSSxDQUFDLFFBQVEsQ0FBQzthQUNkLElBQUksQ0FBQyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7YUFDM0IsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQzthQUN4QixJQUFJLENBQUMsb0JBQW9CLENBQUM7YUFDMUIsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO2FBQ2hDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxPQUFPLENBQUM7YUFDbEMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxhQUFhLEVBQUUsVUFBVSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUU7WUFDcEQsVUFBVSxFQUFFLEtBQUs7WUFDakIsY0FBYyxFQUFFLENBQUMsQ0FBQyxLQUFLO1lBQ3ZCLFVBQVUsRUFBRSxlQUFlLENBQUMsVUFBVTtTQUN0QyxDQUFDLENBQUMsQ0FBQzthQUNILElBQUksQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDO2FBQ3RCLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO1FBRWxDLE9BQU8sRUFBRSxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDakMsQ0FBQztJQUNELFFBQVEsQ0FBQyxZQUFZLEdBQUcsR0FBRyxFQUFFO1FBQzVCLE9BQU8sV0FBVyxDQUFDLEdBQUcsQ0FBQyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO0lBQ3ZDLENBQUMsQ0FBQztJQUNGLFFBQVEsQ0FBQyxXQUFXLEdBQUcsV0FBVyxDQUFDO0lBQ25DLE9BQU8sUUFBUSxDQUFDO0FBQ2pCLENBQUM7QUFFRCxTQUFnQixhQUFhLENBQUMsR0FBVyxFQUFFLEdBQVcsRUFBRSxHQUFZO0lBRW5FLE9BQU87UUFFTixNQUFNLFNBQVMsR0FBRyxhQUFhLENBQUMsR0FBRyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsRUFBRSxHQUFHLEVBQUUsQ0FBQyxDQUFDO1FBQzNELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxHQUFHLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxHQUFHLEdBQUcsRUFBRSxFQUFFLENBQUMsQ0FBQztRQUUxRCxPQUFPLE9BQU87YUFDWixJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7YUFDakIsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUN4QixDQUFDLENBQUM7QUFDSCxDQUFDO0FBWEQsc0NBV0M7QUFFRCxTQUFnQixXQUFXLENBQUMsR0FBVyxFQUFFLEdBQVcsRUFBRSxLQUFjLEVBQUUsVUFBdUMsRUFBRTtJQUU5RyxPQUFPO1FBRU4sT0FBTyxDQUFDLGFBQWEsR0FBRyxJQUFJLENBQUMsQ0FBQyx5Q0FBeUM7UUFFdkUsSUFBSSxFQUFFLENBQUMsUUFBUSxFQUFFLEdBQUcsVUFBYSxFQUFFO1lBQ2xDLE1BQU0sSUFBSSxLQUFLLENBQUMsaUNBQWlDLENBQUMsQ0FBQztTQUNuRDtRQUVELE1BQU0sT0FBTyxHQUFHLGFBQWEsQ0FBQyxHQUFHLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztRQUN2RCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsR0FBRyxLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsR0FBRyxHQUFHLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFDMUQsTUFBTSxTQUFTLEdBQUcsSUFBSSxlQUFlLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDN0MsSUFBSSxHQUFHLEtBQUssS0FBSyxFQUFFO1lBQ2xCLFNBQVMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztTQUNwQjtRQUVELG1DQUFtQztRQUNuQyxtQ0FBbUM7UUFDbkMseUNBQXlDO1FBQ3pDLHNIQUFzSDtRQUN0SCw4RkFBOEY7UUFDOUYseUZBQXlGO1FBQ3pGLDhFQUE4RTtRQUM5RSx1RUFBdUU7UUFDdkUsaUVBQWlFO1FBQ2pFLHFDQUFxQztRQUNyQyxtREFBbUQ7UUFDbkQsa0ZBQWtGO1FBQ2xGLE1BQU07UUFDTixxQkFBcUI7UUFDckIsdUJBQXVCO1FBQ3ZCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIsbUNBQW1DO1FBQ25DLHFDQUFxQztRQUNyQyxPQUFPO1FBQ1AsSUFBSTtRQUVKLE9BQU8sT0FBTztZQUNiLHFCQUFxQjthQUNwQixJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQzthQUN0QixJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7YUFDZixJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3hCLENBQUMsQ0FBQztBQUNILENBQUM7QUE3Q0Qsa0NBNkNDO0FBRUQsU0FBZ0IsU0FBUyxDQUFDLEdBQVcsRUFBRSxLQUFjO0lBRXBELE9BQU87UUFDTixNQUFNLE9BQU8sR0FBRyxhQUFhLENBQUMsS0FBSyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFDLENBQUM7UUFFMUQsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxRQUFRLEVBQUUsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztRQUNoRCxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsUUFBUSxFQUFFLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztRQUVsRSxNQUFNLFNBQVMsR0FBRyxJQUFJLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1QyxTQUFTLENBQUMsT0FBTyxFQUFFLENBQUM7UUFFcEIsT0FBTyxRQUFRO2FBQ2IsSUFBSSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUM7YUFDdEIsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxJQUFJLENBQUMsQ0FBQzthQUMxQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ3hCLENBQUMsQ0FBQztBQUNILENBQUM7QUFoQkQsOEJBZ0JDO0FBRUQsTUFBTSxlQUFlLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsV0FBVyxDQUFDLENBQUM7QUFFMUQsTUFBTSxlQUFlO0lBQ0gsUUFBUSxDQUFVO0lBQ25CLE1BQU0sQ0FBeUI7SUFFOUIsYUFBYSxDQUFrQztJQUMvQyxXQUFXLENBQXVCO0lBQ2xDLG9CQUFvQixDQUFnQztJQUVyRSxZQUFZLE9BQWdCO1FBQzNCLElBQUksQ0FBQyxRQUFRLEdBQUcsT0FBTyxDQUFDO1FBQ3hCLElBQUksQ0FBQyxNQUFNLEdBQUcsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQzNCLElBQUksQ0FBQyxhQUFhLEdBQUcsRUFBRSxDQUFDO1FBQ3hCLE1BQU0sY0FBYyxHQUFHLENBQUMsUUFBZ0IsRUFBRSxRQUFnQixFQUFFLEVBQUU7WUFDN0QsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUU7Z0JBQ25CLE9BQU87YUFDUDtZQUNELElBQUksSUFBSSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsRUFBRTtnQkFDakMsT0FBTzthQUNQO1lBQ0QsSUFBSSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsR0FBRyxJQUFJLENBQUM7WUFFcEMsRUFBRSxDQUFDLFNBQVMsQ0FBQyxRQUFRLEVBQUUsR0FBRyxFQUFFO2dCQUMzQixJQUFJLENBQUMsb0JBQW9CLENBQUMsZUFBZSxDQUFDLFFBQVEsQ0FBQyxDQUFDO2dCQUNwRCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDckIsQ0FBQyxDQUFDLENBQUM7UUFDSixDQUFDLENBQUM7UUFDRixJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksS0FBTSxTQUFRLFNBQVMsQ0FBQyxVQUFVO1lBQ2pELFlBQVksQ0FBQyxRQUFnQixFQUFFLFFBQWdCO2dCQUNyRCxjQUFjLENBQUMsUUFBUSxFQUFFLFFBQVEsQ0FBQyxDQUFDO2dCQUNuQyxPQUFPLEtBQUssQ0FBQyxZQUFZLENBQUMsUUFBUSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQy9DLENBQUM7U0FDRCxDQUFDO1FBQ0YsSUFBSSxDQUFDLG9CQUFvQixHQUFHLElBQUksU0FBUyxDQUFDLG1CQUFtQixDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUVoRixJQUFJLElBQUksQ0FBQyxRQUFRLEVBQUU7WUFDbEIsRUFBRSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsV0FBVyxFQUFFLEdBQUcsRUFBRTtnQkFDeEMsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3JCLENBQUMsQ0FBQyxDQUFDO1NBQ0g7SUFDRixDQUFDO0lBRU8saUJBQWlCLEdBQXdCLElBQUksQ0FBQztJQUM5QyxZQUFZO1FBQ25CLElBQUksSUFBSSxDQUFDLGlCQUFpQixLQUFLLElBQUksRUFBRTtZQUNwQyxZQUFZLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQUM7WUFDckMsSUFBSSxDQUFDLGlCQUFpQixHQUFHLElBQUksQ0FBQztTQUM5QjtRQUNELElBQUksQ0FBQyxpQkFBaUIsR0FBRyxVQUFVLENBQUMsR0FBRyxFQUFFO1lBQ3hDLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7WUFDOUIsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ2hCLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUNSLENBQUM7SUFFTyxJQUFJO1FBQ1gsTUFBTSxDQUFDLEdBQUcsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsb0JBQW9CLENBQUMsQ0FBQztRQUNwRCxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRTtZQUN6Qiw0REFBNEQ7WUFDNUQsTUFBTSxJQUFJLEtBQUssQ0FBQyxnREFBZ0QsQ0FBQyxDQUFDO1NBQ2xFO1FBQ0QsT0FBTyxDQUFDLENBQUM7SUFDVixDQUFDO0lBRU8sSUFBSSxDQUFDLE9BQVksRUFBRSxHQUFHLElBQVc7UUFDeEMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsZUFBZSxDQUFDLEVBQUUsT0FBTyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7SUFDOUQsQ0FBQztJQUVNLE9BQU87UUFDYixNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDN0IsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQzNCLElBQUksQ0FBQyxNQUFNLEVBQUU7WUFDWix5QkFBeUI7WUFDekIsT0FBTztTQUNQO1FBQ0QsSUFBSSxNQUFNLENBQUMsU0FBUyxFQUFFO1lBQ3JCLE9BQU87U0FDUDtRQUVELEVBQUUsQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7UUFDbEQsRUFBRSxDQUFDLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLGVBQWUsRUFBRSxnREFBZ0QsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUM3RyxJQUFJLENBQUMsSUFBSSxDQUFDLDRDQUE0QyxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsU0FBUyxLQUFLLENBQUMsQ0FBQztRQUNuRixJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRTtZQUNuQixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUscUZBQXFGLENBQUMsQ0FBQztTQUNqSDtJQUNGLENBQUM7Q0FDRDtBQUVELFNBQVMsd0JBQXdCO0lBQ2hDLElBQUksR0FBVyxDQUFDO0lBRWhCLElBQUk7UUFDSCxNQUFNLEdBQUcsR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLHVFQUF1RSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQzlHLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDakMsR0FBRyxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsR0FBRyxDQUFDO0tBQ2hDO0lBQUMsTUFBTTtRQUNQLEdBQUcsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDO0tBQ2I7SUFFRCxNQUFNLE9BQU8sR0FBRyx1Q0FBdUMsQ0FBQztJQUN4RCxNQUFNLGFBQWEsR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFDO0lBRXhDLE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUMzQixNQUFNLE1BQU0sR0FBRyxLQUFLO1NBQ2xCLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBTyxFQUFFLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1NBQ3BELElBQUksQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBTyxFQUFFLEVBQUU7UUFDNUIsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDbkMsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVqQyxJQUFJLEtBQUssRUFBRTtZQUNWLGFBQWEsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7U0FDNUI7SUFDRixDQUFDLEVBQUU7UUFDRixNQUFNLEtBQUssR0FBRyxDQUFDLEdBQUcsYUFBYSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDakQsTUFBTSxRQUFRLEdBQUc7WUFDaEIsaUdBQWlHO1lBQ2pHLCtEQUErRDtZQUMvRCxrR0FBa0c7WUFDbEcsa0dBQWtHO1lBQ2xHLEVBQUU7WUFDRixvREFBb0Q7WUFDcEQsRUFBRTtZQUNGLGdEQUFnRDtZQUNoRCxHQUFHLEtBQUssQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxLQUFLLElBQUksNkZBQTZGLElBQUksUUFBUSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxFQUFFLENBQUMsRUFBRTtZQUMxSixLQUFLO1lBQ0wsNkRBQTZEO1lBQzdELEVBQUU7U0FDRixDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUVaLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksSUFBSSxDQUFDO1lBQzFCLElBQUksRUFBRSxtRUFBbUU7WUFDekUsUUFBUSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDO1NBQy9CLENBQUMsQ0FBQyxDQUFDO1FBQ0osSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNsQixDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRUwsT0FBTyxFQUFFLENBQUMsTUFBTSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsQ0FBQztBQUNqQyxDQUFDO0FBRUQsTUFBTSx3QkFBd0IsR0FBRyxJQUFBLHlCQUFjLEVBQUMsb0JBQW9CLENBQUMsQ0FBQztBQUV6RCxRQUFBLDJCQUEyQixHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsNEJBQTRCLEVBQUUsR0FBRyxFQUFFO0lBQ3pGLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsQ0FBQztTQUNsQyxJQUFJLENBQUMsd0JBQXdCLEVBQUUsQ0FBQztTQUNoQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztTQUN0QixJQUFJLENBQUMsd0JBQXdCLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7QUFDNUMsQ0FBQyxDQUFDLENBQUM7QUFFVSxRQUFBLHlCQUF5QixHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsMEJBQTBCLEVBQUUsR0FBRyxFQUFFO0lBQ3JGLE1BQU0sSUFBSSxHQUFHLEdBQUcsRUFBRSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUM7U0FDOUMsSUFBSSxDQUFDLHdCQUF3QixFQUFFLENBQUM7U0FDaEMsSUFBSSxDQUFDLHdCQUF3QixDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBRTNDLE9BQU8sS0FBSyxDQUFDLG1CQUFtQixFQUFFLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxDQUFDO1NBQ25ELElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1NBQ3pCLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7QUFDMUIsQ0FBQyxDQUFDLENBQUMifQ== \ No newline at end of file diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 91b7e9c3b5..26b0f7bc22 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -19,9 +19,13 @@ import * as os from 'os'; import ts = require('typescript'); import * as File from 'vinyl'; import * as task from './task'; - +// import { Mangler } from './mangleTypeScript'; +// import { RawSourceMap } from 'source-map'; const watch = require('./watch'); + +// --- gulp-tsb: compile and transpile -------------------------------- + const reporter = createReporter(); function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { @@ -39,7 +43,7 @@ function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { return options; } -function createCompile(src: string, build: boolean, emitError: boolean, transpileOnly: boolean) { +function createCompile(src: string, build: boolean, emitError: boolean, transpileOnly: boolean | { swc: boolean }) { const tsb = require('./tsb') as typeof import('./tsb'); const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); @@ -60,31 +64,35 @@ function createCompile(src: string, build: boolean, emitError: boolean, transpil } - const compilation = tsb.create(projectPath, overrideOptions, { verbose: false, transpileOnly }, err => reporter(err)); + const compilation = tsb.create(projectPath, overrideOptions, { + verbose: false, + transpileOnly: Boolean(transpileOnly), + transpileWithSwc: typeof transpileOnly !== 'boolean' && transpileOnly.swc + }, err => reporter(err)); function pipeline(token?: util.ICancellationToken) { const bom = require('gulp-bom') as typeof import('gulp-bom'); - const utf8Filter = util.filter(data => /(\/|\\)test(\/|\\).*utf8/.test(data.path)); const tsFilter = util.filter(data => /\.ts$/.test(data.path)); + const isUtf8Test = (f: File) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); + const isRuntimeJs = (f: File) => f.path.endsWith('.js') && !f.path.includes('fixtures'); const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); const input = es.through(); const output = input - .pipe(utf8Filter) - .pipe(bom()) // this is required to preserve BOM in test files that loose it otherwise - .pipe(utf8Filter.restore) + .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise + .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) .pipe(tsFilter) .pipe(util.loadSourcemaps()) .pipe(compilation(token)) .pipe(noDeclarationsFilter) - .pipe(build ? nls.nls() : es.through()) + .pipe(util.$if(build, nls.nls())) .pipe(noDeclarationsFilter.restore) - .pipe(transpileOnly ? es.through() : sourcemaps.write('.', { + .pipe(util.$if(!transpileOnly, sourcemaps.write('.', { addComment: false, includeContent: !!build, sourceRoot: overrideOptions.sourceRoot - })) + }))) .pipe(tsFilter.restore) .pipe(reporter.end(!!emitError)); @@ -93,14 +101,15 @@ function createCompile(src: string, build: boolean, emitError: boolean, transpil pipeline.tsProjectSrc = () => { return compilation.src({ base: src }); }; + pipeline.projectPath = projectPath; return pipeline; } -export function transpileTask(src: string, out: string): () => NodeJS.ReadWriteStream { +export function transpileTask(src: string, out: string, swc: boolean): () => NodeJS.ReadWriteStream { return function () { - const transpile = createCompile(src, false, true, true); + const transpile = createCompile(src, false, true, { swc }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); return srcPipe @@ -109,10 +118,12 @@ export function transpileTask(src: string, out: string): () => NodeJS.ReadWriteS }; } -export function compileTask(src: string, out: string, build: boolean): () => NodeJS.ReadWriteStream { +export function compileTask(src: string, out: string, build: boolean, options: { disableMangle?: boolean } = {}): () => NodeJS.ReadWriteStream { return function () { + options.disableMangle = true; // {{SQL CARBON EDIT}} - disable mangling + if (os.totalmem() < 4_000_000_000) { throw new Error('compilation requires 4GB of RAM'); } @@ -124,7 +135,30 @@ export function compileTask(src: string, out: string, build: boolean): () => Nod generator.execute(); } + // mangle: TypeScript to TypeScript + // let mangleStream = es.through(); + // if (build && !options.disableMangle) { + // let ts2tsMangler = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data)); + // const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState'])); + // mangleStream = es.through(function write(data: File & { sourceMap?: RawSourceMap }) { + // type TypeScriptExt = typeof ts & { normalizePath(path: string): string }; + // const tsNormalPath = (ts).normalizePath(data.path); + // const newContents = newContentsByFileName.get(tsNormalPath); + // if (newContents !== undefined) { + // data.contents = Buffer.from(newContents.out); + // data.sourceMap = newContents.sourceMap && JSON.parse(newContents.sourceMap); + // } + // this.push(data); + // }, function end() { + // this.push(null); + // // free resources + // newContentsByFileName.clear(); + // (ts2tsMangler) = undefined; + // }); + // } + return srcPipe + //.pipe(mangleStream) .pipe(generator.stream) .pipe(compile()) .pipe(gulp.dest(out)); @@ -272,7 +306,7 @@ function generateApiProposalNames() { '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', '', 'export const allApiProposals = Object.freeze({', - `${names.map(name => `\t${name}: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${name}.d.ts'`).join(`,${os.EOL}`)}`, + `${names.map(name => `\t${name}: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${name}.d.ts'`).join(`,${eol}`)}`, '});', 'export type ApiProposalName = keyof typeof allApiProposals;', '', diff --git a/build/lib/dependencies.js b/build/lib/dependencies.js index 9571ee8666..2241e449d9 100644 --- a/build/lib/dependencies.js +++ b/build/lib/dependencies.js @@ -5,10 +5,11 @@ *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.getProductionDependencies = void 0; +const fs = require("fs"); const path = require("path"); const cp = require("child_process"); -const _ = require("underscore"); const parseSemver = require('parse-semver'); +const root = fs.realpathSync(path.dirname(path.dirname(__dirname))); function asYarnDependency(prefix, tree) { let parseResult; try { @@ -35,26 +36,41 @@ function asYarnDependency(prefix, tree) { } return { name, version, path: dependencyPath, children }; } -function getYarnProductionDependencies(cwd) { - const raw = cp.execSync('yarn list --json', { cwd, encoding: 'utf8', env: { ...process.env, NODE_ENV: 'production' }, stdio: [null, null, 'inherit'] }); +function getYarnProductionDependencies(folderPath) { + const raw = cp.execSync('yarn list --json', { cwd: folderPath, encoding: 'utf8', env: { ...process.env, NODE_ENV: 'production' }, stdio: [null, null, 'inherit'] }); const match = /^{"type":"tree".*$/m.exec(raw); if (!match || match.length !== 1) { throw new Error('Could not parse result of `yarn list --json`'); } const trees = JSON.parse(match[0]).data.trees; return trees - .map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree)) + .map(tree => asYarnDependency(path.join(folderPath, 'node_modules'), tree)) .filter((dep) => !!dep); } -function getProductionDependencies(cwd) { +function getProductionDependencies(folderPath) { const result = []; - const deps = getYarnProductionDependencies(cwd); + const deps = getYarnProductionDependencies(folderPath); const flatten = (dep) => { result.push({ name: dep.name, version: dep.version, path: dep.path }); dep.children.forEach(flatten); }; deps.forEach(flatten); - return _.uniq(result); + // Account for distro npm dependencies + const realFolderPath = fs.realpathSync(folderPath); + const relativeFolderPath = path.relative(root, realFolderPath); + const distroPackageJsonPath = `${root}/.build/distro/npm/${relativeFolderPath}/package.json`; + if (fs.existsSync(distroPackageJsonPath)) { + const distroPackageJson = JSON.parse(fs.readFileSync(distroPackageJsonPath, 'utf8')); + const distroDependencyNames = Object.keys(distroPackageJson.dependencies ?? {}); + for (const name of distroDependencyNames) { + result.push({ + name, + version: distroPackageJson.dependencies[name], + path: path.join(realFolderPath, 'node_modules', name) + }); + } + } + return [...new Set(result)]; } exports.getProductionDependencies = getProductionDependencies; if (require.main === module) { - const root = path.dirname(path.dirname(__dirname)); console.log(JSON.stringify(getProductionDependencies(root), null, ' ')); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwZW5kZW5jaWVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwZW5kZW5jaWVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0Isb0NBQW9DO0FBQ3BDLE1BQU0sV0FBVyxHQUFHLE9BQU8sQ0FBQyxjQUFjLENBQUMsQ0FBQztBQUM1QyxNQUFNLElBQUksR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFpQnBFLFNBQVMsZ0JBQWdCLENBQUMsTUFBYyxFQUFFLElBQVU7SUFDbkQsSUFBSSxXQUFXLENBQUM7SUFFaEIsSUFBSTtRQUNILFdBQVcsR0FBRyxXQUFXLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0tBQ3JDO0lBQUMsT0FBTyxHQUFHLEVBQUU7UUFDYixHQUFHLENBQUMsT0FBTyxJQUFJLEtBQUssSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ2hDLE9BQU8sQ0FBQyxJQUFJLENBQUMsMkJBQTJCLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ3JELE9BQU8sSUFBSSxDQUFDO0tBQ1o7SUFFRCxtQ0FBbUM7SUFDbkMsSUFBSSxXQUFXLENBQUMsT0FBTyxLQUFLLFdBQVcsQ0FBQyxLQUFLLEVBQUU7UUFDOUMsT0FBTyxJQUFJLENBQUM7S0FDWjtJQUVELE1BQU0sSUFBSSxHQUFHLFdBQVcsQ0FBQyxJQUFJLENBQUM7SUFDOUIsTUFBTSxPQUFPLEdBQUcsV0FBVyxDQUFDLE9BQU8sQ0FBQztJQUNwQyxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUMvQyxNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUM7SUFFcEIsS0FBSyxNQUFNLEtBQUssSUFBSSxD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 \ No newline at end of file diff --git a/build/lib/dependencies.ts b/build/lib/dependencies.ts index 4ef161bc7c..681bb86959 100644 --- a/build/lib/dependencies.ts +++ b/build/lib/dependencies.ts @@ -3,10 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as fs from 'fs'; import * as path from 'path'; import * as cp from 'child_process'; -import * as _ from 'underscore'; const parseSemver = require('parse-semver'); +const root = fs.realpathSync(path.dirname(path.dirname(__dirname))); interface Tree { readonly name: string; @@ -55,8 +56,8 @@ function asYarnDependency(prefix: string, tree: Tree): Dependency | null { return { name, version, path: dependencyPath, children }; } -function getYarnProductionDependencies(cwd: string): Dependency[] { - const raw = cp.execSync('yarn list --json', { cwd, encoding: 'utf8', env: { ...process.env, NODE_ENV: 'production' }, stdio: [null, null, 'inherit'] }); +function getYarnProductionDependencies(folderPath: string): Dependency[] { + const raw = cp.execSync('yarn list --json', { cwd: folderPath, encoding: 'utf8', env: { ...process.env, NODE_ENV: 'production' }, stdio: [null, null, 'inherit'] }); const match = /^{"type":"tree".*$/m.exec(raw); if (!match || match.length !== 1) { @@ -66,19 +67,37 @@ function getYarnProductionDependencies(cwd: string): Dependency[] { const trees = JSON.parse(match[0]).data.trees as Tree[]; return trees - .map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree)) + .map(tree => asYarnDependency(path.join(folderPath, 'node_modules'), tree)) .filter((dep): dep is Dependency => !!dep); } -export function getProductionDependencies(cwd: string): FlatDependency[] { +export function getProductionDependencies(folderPath: string): FlatDependency[] { const result: FlatDependency[] = []; - const deps = getYarnProductionDependencies(cwd); + const deps = getYarnProductionDependencies(folderPath); const flatten = (dep: Dependency) => { result.push({ name: dep.name, version: dep.version, path: dep.path }); dep.children.forEach(flatten); }; deps.forEach(flatten); - return _.uniq(result); + + // Account for distro npm dependencies + const realFolderPath = fs.realpathSync(folderPath); + const relativeFolderPath = path.relative(root, realFolderPath); + const distroPackageJsonPath = `${root}/.build/distro/npm/${relativeFolderPath}/package.json`; + + if (fs.existsSync(distroPackageJsonPath)) { + const distroPackageJson = JSON.parse(fs.readFileSync(distroPackageJsonPath, 'utf8')); + const distroDependencyNames = Object.keys(distroPackageJson.dependencies ?? {}); + + for (const name of distroDependencyNames) { + result.push({ + name, + version: distroPackageJson.dependencies[name], + path: path.join(realFolderPath, 'node_modules', name) + }); + } + } + + return [...new Set(result)]; } if (require.main === module) { - const root = path.dirname(path.dirname(__dirname)); console.log(JSON.stringify(getProductionDependencies(root), null, ' ')); } diff --git a/build/lib/electron.js b/build/lib/electron.js index 9557f5cc74..06b4bbae47 100644 --- a/build/lib/electron.js +++ b/build/lib/electron.js @@ -11,12 +11,13 @@ const vfs = require("vinyl-fs"); const filter = require("gulp-filter"); const _ = require("underscore"); const util = require("./util"); +const getVersion_1 = require("./getVersion"); function isDocumentSuffix(str) { return str === 'document' || str === 'script' || str === 'file' || str === 'source code'; } const root = path.dirname(path.dirname(__dirname)); const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); -const commit = util.getVersion(root); +const commit = (0, getVersion_1.getVersion)(root); const darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); /** * Generate a `DarwinDocumentType` given a list of file extensions, an icon name, and an optional suffix or file type name. @@ -76,10 +77,10 @@ function darwinBundleDocumentType(extensions, icon, nameOrSuffix, utis) { // }); // } exports.config = { - version: util.getElectronVersion(), + version: product.electronRepository ? '22.5.7' : util.getElectronVersion(), productAppName: product.nameLong, companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2022 Microsoft. All rights reserved', + copyright: 'Copyright (C) 2023 Microsoft. All rights reserved', darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', @@ -97,17 +98,17 @@ exports.config = { darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, linuxExecutableName: product.applicationName, winIcon: 'resources/win32/code.ico', - token: process.env['VSCODE_MIXIN_PASSWORD'] || process.env['GITHUB_TOKEN'] || undefined, + token: process.env['GITHUB_TOKEN'], repo: product.electronRepository || undefined }; function getElectron(arch) { return () => { - const electron = require('gulp-atom-electron'); + const electron = require('@vscode/gulp-electron'); const json = require('gulp-json-editor'); const electronOpts = _.extend({}, exports.config, { platform: process.platform, arch: arch === 'armhf' ? 'arm' : arch, - ffmpegChromium: true, + ffmpegChromium: false, keepDefaultApp: true }); return vfs.src('package.json') @@ -118,7 +119,7 @@ function getElectron(arch) { }; } async function main(arch = process.arch) { - const version = util.getElectronVersion(); + const version = product.electronRepository ? '22.5.7' : util.getElectronVersion(); const electronPath = path.join(root, '.build', 'electron'); const versionFile = path.join(electronPath, 'version'); const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; @@ -133,3 +134,4 @@ if (require.main === module) { process.exit(1); }); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWxlY3Ryb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJlbGVjdHJvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLGdDQUFnQztBQUNoQyxzQ0FBc0M7QUFDdEMsZ0NBQWdDO0FBQ2hDLCtCQUErQjtBQUMvQiw2Q0FBMEM7QUFZMUMsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFZO0lBQ3JDLE9BQU8sR0FBRyxLQUFLLFVBQVUsSUFBSSxHQUFHLEtBQUssUUFBUSxJQUFJLEdBQUcsS0FBSyxNQUFNLElBQUksR0FBRyxLQUFLLGFBQWEsQ0FBQztBQUMxRixDQUFDO0FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFDbkQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLGNBQWMsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFDckYsTUFBTSxNQUFNLEdBQUcsSUFBQSx1QkFBVSxFQUFDLElBQUksQ0FBQyxDQUFDO0FBRWhDLE1BQU0scUJBQXFCLEdBQUcsT0FBTyxDQUFDLGFBQWEsSUFBSSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLE1BQU0sQ0FBQyxDQUFDLENBQUM7QUFFbkk7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWtCRztBQUNILFNBQVMsd0JBQXdCLENBQUMsVUFBb0IsRUFBRSxJQUFZLEVBQUUsWUFBNEMsRUFBRSxJQUFlO0lBQ2xJLDJGQUEyRjtJQUMzRixJQUFJLGdCQUFnQixDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsWUFB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 \ No newline at end of file diff --git a/build/lib/electron.ts b/build/lib/electron.ts index 7aedd39710..008c34c08a 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -9,6 +9,7 @@ import * as vfs from 'vinyl-fs'; import * as filter from 'gulp-filter'; import * as _ from 'underscore'; import * as util from './util'; +import { getVersion } from './getVersion'; type DarwinDocumentSuffix = 'document' | 'script' | 'file' | 'source code'; type DarwinDocumentType = { @@ -26,7 +27,7 @@ function isDocumentSuffix(str?: string): str is DarwinDocumentSuffix { const root = path.dirname(path.dirname(__dirname)); const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); -const commit = util.getVersion(root); +const commit = getVersion(root); const darwinCreditsTemplate = product.darwinCredits && _.template(fs.readFileSync(path.join(root, product.darwinCredits), 'utf8')); @@ -91,10 +92,10 @@ function darwinBundleDocumentType(extensions: string[], icon: string, nameOrSuff // } export const config = { - version: util.getElectronVersion(), + version: product.electronRepository ? '22.5.7' : util.getElectronVersion(), productAppName: product.nameLong, companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2022 Microsoft. All rights reserved', + copyright: 'Copyright (C) 2023 Microsoft. All rights reserved', darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', @@ -112,19 +113,19 @@ export const config = { darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, linuxExecutableName: product.applicationName, winIcon: 'resources/win32/code.ico', - token: process.env['VSCODE_MIXIN_PASSWORD'] || process.env['GITHUB_TOKEN'] || undefined, + token: process.env['GITHUB_TOKEN'], repo: product.electronRepository || undefined }; function getElectron(arch: string): () => NodeJS.ReadWriteStream { return () => { - const electron = require('gulp-atom-electron'); + const electron = require('@vscode/gulp-electron'); const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); const electronOpts = _.extend({}, config, { platform: process.platform, arch: arch === 'armhf' ? 'arm' : arch, - ffmpegChromium: true, + ffmpegChromium: false, keepDefaultApp: true }); @@ -137,7 +138,7 @@ function getElectron(arch: string): () => NodeJS.ReadWriteStream { } async function main(arch = process.arch): Promise { - const version = util.getElectronVersion(); + const version = product.electronRepository ? '22.5.7' : util.getElectronVersion(); const electronPath = path.join(root, '.build', 'electron'); const versionFile = path.join(electronPath, 'version'); const isUpToDate = fs.existsSync(versionFile) && fs.readFileSync(versionFile, 'utf8') === `${version}`; diff --git a/build/lib/eslint/code-import-patterns.js b/build/lib/eslint/code-import-patterns.js deleted file mode 100644 index e8cd13bbb7..0000000000 --- a/build/lib/eslint/code-import-patterns.js +++ /dev/null @@ -1,199 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const path = require("path"); -const minimatch = require("minimatch"); -const utils_1 = require("./utils"); -const REPO_ROOT = path.normalize(path.join(__dirname, '../../../')); -function isLayerAllowRule(option) { - return !!(option.when && option.allow); -} -/** - * Returns the filename relative to the project root and using `/` as separators - */ -function getRelativeFilename(context) { - const filename = path.normalize(context.getFilename()); - return filename.substring(REPO_ROOT.length).replace(/\\/g, '/'); -} -module.exports = new class { - constructor() { - this.meta = { - messages: { - badImport: 'Imports violates \'{{restrictions}}\' restrictions. See https://github.com/microsoft/vscode/wiki/Source-Code-Organization', - badFilename: 'Missing definition in `code-import-patterns` for this file. Define rules at https://github.com/microsoft/vscode/blob/main/.eslintrc.json' - }, - docs: { - url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization' - } - }; - this._optionsCache = new WeakMap(); - } - create(context) { - const options = context.options; - const configs = this._processOptions(options); - const relativeFilename = getRelativeFilename(context); - for (const config of configs) { - if (minimatch(relativeFilename, config.target)) { - return (0, utils_1.createImportRuleListener)((node, value) => this._checkImport(context, config, node, value)); - } - } - context.report({ - loc: { line: 1, column: 0 }, - messageId: 'badFilename' - }); - return {}; - } - _processOptions(options) { - if (this._optionsCache.has(options)) { - return this._optionsCache.get(options); - } - function orSegment(variants) { - return (variants.length === 1 ? variants[0] : `{${variants.join(',')}}`); - } - const layerRules = [ - { layer: 'common', deps: orSegment(['common']) }, - { layer: 'worker', deps: orSegment(['common', 'worker']) }, - { layer: 'browser', deps: orSegment(['common', 'browser']), isBrowser: true }, - { layer: 'electron-sandbox', deps: orSegment(['common', 'browser', 'electron-sandbox']), isBrowser: true }, - { layer: 'node', deps: orSegment(['common', 'node']), isNode: true }, - { layer: 'electron-browser', deps: orSegment(['common', 'browser', 'node', 'electron-sandbox', 'electron-browser']), isBrowser: true, isNode: true }, - { layer: 'electron-main', deps: orSegment(['common', 'node', 'electron-main']), isNode: true }, - ]; - let browserAllow = []; - let nodeAllow = []; - let testAllow = []; - for (const option of options) { - if (isLayerAllowRule(option)) { - if (option.when === 'hasBrowser') { - browserAllow = option.allow.slice(0); - } - else if (option.when === 'hasNode') { - nodeAllow = option.allow.slice(0); - } - else if (option.when === 'test') { - testAllow = option.allow.slice(0); - } - } - } - function findLayer(layer) { - for (const layerRule of layerRules) { - if (layerRule.layer === layer) { - return layerRule; - } - } - return null; - } - function generateConfig(layerRule, target, rawRestrictions) { - const restrictions = []; - const testRestrictions = [...testAllow]; - if (layerRule.isBrowser) { - restrictions.push(...browserAllow); - } - if (layerRule.isNode) { - restrictions.push(...nodeAllow); - } - for (const rawRestriction of rawRestrictions) { - let importPattern; - let when = undefined; - if (typeof rawRestriction === 'string') { - importPattern = rawRestriction; - } - else { - importPattern = rawRestriction.pattern; - when = rawRestriction.when; - } - if (typeof when === 'undefined' - || (when === 'hasBrowser' && layerRule.isBrowser) - || (when === 'hasNode' && layerRule.isNode)) { - restrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`)); - testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`)); - } - else if (when === 'test') { - testRestrictions.push(importPattern.replace(/\/\~$/, `/${layerRule.deps}/**`)); - testRestrictions.push(importPattern.replace(/\/\~$/, `/test/${layerRule.deps}/**`)); - } - } - testRestrictions.push(...restrictions); - return [ - { - target: target.replace(/\/\~$/, `/${layerRule.layer}/**`), - restrictions: restrictions - }, - { - target: target.replace(/\/\~$/, `/test/${layerRule.layer}/**`), - restrictions: testRestrictions - } - ]; - } - const configs = []; - for (const option of options) { - if (isLayerAllowRule(option)) { - continue; - } - const target = option.target; - const targetIsVS = /^src\/{vs,sql}\//.test(target); - const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions).slice(0); - if (targetIsVS) { - // Always add "vs/nls" - restrictions.push('vs/nls'); - } - if (targetIsVS && option.layer) { - // single layer => simple substitution for /~ - const layerRule = findLayer(option.layer); - if (layerRule) { - const [config, testConfig] = generateConfig(layerRule, target, restrictions); - if (option.test) { - configs.push(testConfig); - } - else { - configs.push(config); - } - } - } - else if (targetIsVS && /\/\~$/.test(target)) { - // generate all layers - for (const layerRule of layerRules) { - const [config, testConfig] = generateConfig(layerRule, target, restrictions); - configs.push(config); - configs.push(testConfig); - } - } - else { - configs.push({ target, restrictions: restrictions.filter(r => typeof r === 'string') }); - } - } - this._optionsCache.set(options, configs); - return configs; - } - _checkImport(context, config, node, importPath) { - // resolve relative paths - if (importPath[0] === '.') { - const relativeFilename = getRelativeFilename(context); - importPath = path.posix.join(path.posix.dirname(relativeFilename), importPath); - if (/^src\/{vs,sql}\//.test(importPath)) { - // resolve using AMD base url - importPath = importPath.substring('src/'.length); - } - } - const restrictions = config.restrictions; - let matched = false; - for (const pattern of restrictions) { - if (minimatch(importPath, pattern)) { - matched = true; - break; - } - } - if (!matched) { - // None of the restrictions matched - context.report({ - loc: node.loc, - messageId: 'badImport', - data: { - restrictions: restrictions.join(' or ') - } - }); - } - } -}; diff --git a/build/lib/eslint/code-layering.js b/build/lib/eslint/code-layering.js deleted file mode 100644 index d8b70f5ac2..0000000000 --- a/build/lib/eslint/code-layering.js +++ /dev/null @@ -1,68 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const path_1 = require("path"); -const utils_1 = require("./utils"); -module.exports = new class { - constructor() { - this.meta = { - messages: { - layerbreaker: 'Bad layering. You are not allowed to access {{from}} from here, allowed layers are: [{{allowed}}]' - }, - docs: { - url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization' - } - }; - } - create(context) { - const fileDirname = (0, path_1.dirname)(context.getFilename()); - const parts = fileDirname.split(/\\|\//); - const ruleArgs = context.options[0]; - let config; - for (let i = parts.length - 1; i >= 0; i--) { - if (ruleArgs[parts[i]]) { - config = { - allowed: new Set(ruleArgs[parts[i]]).add(parts[i]), - disallowed: new Set() - }; - Object.keys(ruleArgs).forEach(key => { - if (!config.allowed.has(key)) { - config.disallowed.add(key); - } - }); - break; - } - } - if (!config) { - // nothing - return {}; - } - return (0, utils_1.createImportRuleListener)((node, path) => { - if (path[0] === '.') { - path = (0, path_1.join)((0, path_1.dirname)(context.getFilename()), path); - } - const parts = (0, path_1.dirname)(path).split(/\\|\//); - for (let i = parts.length - 1; i >= 0; i--) { - const part = parts[i]; - if (config.allowed.has(part)) { - // GOOD - same layer - break; - } - if (config.disallowed.has(part)) { - // BAD - wrong layer - context.report({ - loc: node.loc, - messageId: 'layerbreaker', - data: { - from: part, - allowed: [...config.allowed.keys()].join(', ') - } - }); - break; - } - } - }); - } -}; diff --git a/build/lib/eslint/code-no-look-behind-regex.js b/build/lib/eslint/code-no-look-behind-regex.js deleted file mode 100644 index babb681fbe..0000000000 --- a/build/lib/eslint/code-no-look-behind-regex.js +++ /dev/null @@ -1,42 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ -const _positiveLookBehind = /\(\?<=.+/; -const _negativeLookBehind = /\(\? { - const pattern = node.regex?.pattern; - if (_containsLookBehind(pattern)) { - context.report({ - node, - message: 'Look behind assertions are not yet supported in all browsers' - }); - } - }, - // new Regex("...") - ['NewExpression[callee.name="RegExp"] Literal']: (node) => { - if (_containsLookBehind(node.value)) { - context.report({ - node, - message: 'Look behind assertions are not yet supported in all browsers' - }); - } - } - }; - } -}; diff --git a/build/lib/eslint/code-no-nls-in-standalone-editor.js b/build/lib/eslint/code-no-nls-in-standalone-editor.js deleted file mode 100644 index 5d508810d1..0000000000 --- a/build/lib/eslint/code-no-nls-in-standalone-editor.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const path_1 = require("path"); -const utils_1 = require("./utils"); -module.exports = new class NoNlsInStandaloneEditorRule { - constructor() { - this.meta = { - messages: { - noNls: 'Not allowed to import vs/nls in standalone editor modules. Use standaloneStrings.ts' - } - }; - } - create(context) { - const fileName = context.getFilename(); - if (/vs(\/|\\)editor(\/|\\)standalone(\/|\\)/.test(fileName) - || /vs(\/|\\)editor(\/|\\)common(\/|\\)standalone(\/|\\)/.test(fileName) - || /vs(\/|\\)editor(\/|\\)editor.api/.test(fileName) - || /vs(\/|\\)editor(\/|\\)editor.main/.test(fileName) - || /vs(\/|\\)editor(\/|\\)editor.worker/.test(fileName)) { - return (0, utils_1.createImportRuleListener)((node, path) => { - // resolve relative paths - if (path[0] === '.') { - path = (0, path_1.join)(context.getFilename(), path); - } - if (/vs(\/|\\)nls/.test(path)) { - context.report({ - loc: node.loc, - messageId: 'noNls' - }); - } - }); - } - return {}; - } -}; diff --git a/build/lib/eslint/code-no-standalone-editor.js b/build/lib/eslint/code-no-standalone-editor.js deleted file mode 100644 index 5812f1a1cc..0000000000 --- a/build/lib/eslint/code-no-standalone-editor.js +++ /dev/null @@ -1,41 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const path_1 = require("path"); -const utils_1 = require("./utils"); -module.exports = new class NoNlsInStandaloneEditorRule { - constructor() { - this.meta = { - messages: { - badImport: 'Not allowed to import standalone editor modules.' - }, - docs: { - url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization' - } - }; - } - create(context) { - if (/vs(\/|\\)editor/.test(context.getFilename())) { - // the vs/editor folder is allowed to use the standalone editor - return {}; - } - return (0, utils_1.createImportRuleListener)((node, path) => { - // resolve relative paths - if (path[0] === '.') { - path = (0, path_1.join)(context.getFilename(), path); - } - if (/vs(\/|\\)editor(\/|\\)standalone(\/|\\)/.test(path) - || /vs(\/|\\)editor(\/|\\)common(\/|\\)standalone(\/|\\)/.test(path) - || /vs(\/|\\)editor(\/|\\)editor.api/.test(path) - || /vs(\/|\\)editor(\/|\\)editor.main/.test(path) - || /vs(\/|\\)editor(\/|\\)editor.worker/.test(path)) { - context.report({ - loc: node.loc, - messageId: 'badImport' - }); - } - }); - } -}; diff --git a/build/lib/eslint/code-no-test-only.js b/build/lib/eslint/code-no-test-only.js deleted file mode 100644 index 747e9c58cd..0000000000 --- a/build/lib/eslint/code-no-test-only.js +++ /dev/null @@ -1,17 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -module.exports = new class NoTestOnly { - create(context) { - return { - ['MemberExpression[object.name="test"][property.name="only"]']: (node) => { - return context.report({ - node, - message: 'test.only is a dev-time tool and CANNOT be pushed' - }); - } - }; - } -}; diff --git a/build/lib/eslint/code-no-unexternalized-strings.js b/build/lib/eslint/code-no-unexternalized-strings.js deleted file mode 100644 index e72ea0974b..0000000000 --- a/build/lib/eslint/code-no-unexternalized-strings.js +++ /dev/null @@ -1,111 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var _a; -const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); -function isStringLiteral(node) { - return !!node && node.type === experimental_utils_1.AST_NODE_TYPES.Literal && typeof node.value === 'string'; -} -function isDoubleQuoted(node) { - return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; -} -module.exports = new (_a = class NoUnexternalizedStrings { - constructor() { - this.meta = { - messages: { - doubleQuoted: 'Only use double-quoted strings for externalized strings.', - badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.', - duplicateKey: 'Duplicate key \'{{key}}\' with different message value.', - badMessage: 'Message argument to \'{{message}}\' must be a string literal.' - } - }; - } - create(context) { - const externalizedStringLiterals = new Map(); - const doubleQuotedStringLiterals = new Set(); - function collectDoubleQuotedStrings(node) { - if (isStringLiteral(node) && isDoubleQuoted(node)) { - doubleQuotedStringLiterals.add(node); - } - } - function visitLocalizeCall(node) { - // localize(key, message) - const [keyNode, messageNode] = node.arguments; - // (1) - // extract key so that it can be checked later - let key; - if (isStringLiteral(keyNode)) { - doubleQuotedStringLiterals.delete(keyNode); - key = keyNode.value; - } - else if (keyNode.type === experimental_utils_1.AST_NODE_TYPES.ObjectExpression) { - for (const property of keyNode.properties) { - if (property.type === experimental_utils_1.AST_NODE_TYPES.Property && !property.computed) { - if (property.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier && property.key.name === 'key') { - if (isStringLiteral(property.value)) { - doubleQuotedStringLiterals.delete(property.value); - key = property.value.value; - break; - } - } - } - } - } - if (typeof key === 'string') { - let array = externalizedStringLiterals.get(key); - if (!array) { - array = []; - externalizedStringLiterals.set(key, array); - } - array.push({ call: node, message: messageNode }); - } - // (2) - // remove message-argument from doubleQuoted list and make - // sure it is a string-literal - doubleQuotedStringLiterals.delete(messageNode); - if (!isStringLiteral(messageNode)) { - context.report({ - loc: messageNode.loc, - messageId: 'badMessage', - data: { message: context.getSourceCode().getText(node) } - }); - } - } - function reportBadStringsAndBadKeys() { - // (1) - // report all strings that are in double quotes - for (const node of doubleQuotedStringLiterals) { - context.report({ loc: node.loc, messageId: 'doubleQuoted' }); - } - for (const [key, values] of externalizedStringLiterals) { - // (2) - // report all invalid NLS keys - if (!key.match(NoUnexternalizedStrings._rNlsKeys)) { - for (const value of values) { - context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } }); - } - } - // (2) - // report all invalid duplicates (same key, different message) - if (values.length > 1) { - for (let i = 1; i < values.length; i++) { - if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { - context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); - } - } - } - } - } - return { - ['Literal']: (node) => collectDoubleQuotedStrings(node), - ['ExpressionStatement[directive] Literal:exit']: (node) => doubleQuotedStringLiterals.delete(node), - ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node) => visitLocalizeCall(node), - ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node) => visitLocalizeCall(node), - ['Program:exit']: reportBadStringsAndBadKeys, - }; - } - }, - _a._rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/, - _a); diff --git a/build/lib/eslint/code-no-unused-expressions.js b/build/lib/eslint/code-no-unused-expressions.js deleted file mode 100644 index 19c6e2b183..0000000000 --- a/build/lib/eslint/code-no-unused-expressions.js +++ /dev/null @@ -1,119 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ -module.exports = { - meta: { - type: 'suggestion', - docs: { - description: 'disallow unused expressions', - category: 'Best Practices', - recommended: false, - url: 'https://eslint.org/docs/rules/no-unused-expressions' - }, - schema: [ - { - type: 'object', - properties: { - allowShortCircuit: { - type: 'boolean', - default: false - }, - allowTernary: { - type: 'boolean', - default: false - }, - allowTaggedTemplates: { - type: 'boolean', - default: false - } - }, - additionalProperties: false - } - ] - }, - create(context) { - const config = context.options[0] || {}, allowShortCircuit = config.allowShortCircuit || false, allowTernary = config.allowTernary || false, allowTaggedTemplates = config.allowTaggedTemplates || false; - // eslint-disable-next-line jsdoc/require-description - /** - * @param node any node - * @returns whether the given node structurally represents a directive - */ - function looksLikeDirective(node) { - return node.type === 'ExpressionStatement' && - node.expression.type === 'Literal' && typeof node.expression.value === 'string'; - } - // eslint-disable-next-line jsdoc/require-description - /** - * @param predicate ([a] -> Boolean) the function used to make the determination - * @param list the input list - * @returns the leading sequence of members in the given list that pass the given predicate - */ - function takeWhile(predicate, list) { - for (let i = 0; i < list.length; ++i) { - if (!predicate(list[i])) { - return list.slice(0, i); - } - } - return list.slice(); - } - // eslint-disable-next-line jsdoc/require-description - /** - * @param node a Program or BlockStatement node - * @returns the leading sequence of directive nodes in the given node's body - */ - function directives(node) { - return takeWhile(looksLikeDirective, node.body); - } - // eslint-disable-next-line jsdoc/require-description - /** - * @param node any node - * @param ancestors the given node's ancestors - * @returns whether the given node is considered a directive in its current position - */ - function isDirective(node, ancestors) { - const parent = ancestors[ancestors.length - 1], grandparent = ancestors[ancestors.length - 2]; - return (parent.type === 'Program' || parent.type === 'BlockStatement' && - (/Function/u.test(grandparent.type))) && - directives(parent).indexOf(node) >= 0; - } - /** - * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags. - * @param node any node - * @returns whether the given node is a valid expression - */ - function isValidExpression(node) { - if (allowTernary) { - // Recursive check for ternary and logical expressions - if (node.type === 'ConditionalExpression') { - return isValidExpression(node.consequent) && isValidExpression(node.alternate); - } - } - if (allowShortCircuit) { - if (node.type === 'LogicalExpression') { - return isValidExpression(node.right); - } - } - if (allowTaggedTemplates && node.type === 'TaggedTemplateExpression') { - return true; - } - if (node.type === 'ExpressionStatement') { - return isValidExpression(node.expression); - } - return /^(?:Assignment|OptionalCall|Call|New|Update|Yield|Await|Chain)Expression$/u.test(node.type) || - (node.type === 'UnaryExpression' && ['delete', 'void'].indexOf(node.operator) >= 0); - } - return { - ExpressionStatement(node) { - if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) { - context.report({ node: node, message: `Expected an assignment or function call and instead saw an expression. ${node.expression}` }); - } - } - }; - } -}; diff --git a/build/lib/eslint/code-translation-remind.js b/build/lib/eslint/code-translation-remind.js deleted file mode 100644 index 4107285d76..0000000000 --- a/build/lib/eslint/code-translation-remind.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var _a; -const fs_1 = require("fs"); -const utils_1 = require("./utils"); -module.exports = new (_a = class TranslationRemind { - constructor() { - this.meta = { - messages: { - missing: 'Please add \'{{resource}}\' to ./build/lib/i18n.resources.json file to use translations here.' - } - }; - } - create(context) { - return (0, utils_1.createImportRuleListener)((node, path) => this._checkImport(context, node, path)); - } - _checkImport(context, node, path) { - if (path !== TranslationRemind.NLS_MODULE) { - return; - } - const currentFile = context.getFilename(); - const matchService = currentFile.match(/vs\/workbench\/services\/\w+/); - const matchPart = currentFile.match(/vs\/workbench\/contrib\/\w+/); - if (!matchService && !matchPart) { - return; - } - const resource = matchService ? matchService[0] : matchPart[0]; - let resourceDefined = false; - let json; - try { - json = (0, fs_1.readFileSync)('./build/lib/i18n.resources.json', 'utf8'); - } - catch (e) { - console.error('[translation-remind rule]: File with resources to pull from Transifex was not found. Aborting translation resource check for newly defined workbench part/service.'); - return; - } - const workbenchResources = JSON.parse(json).workbench; - workbenchResources.forEach((existingResource) => { - if (existingResource.name === resource) { - resourceDefined = true; - return; - } - }); - if (!resourceDefined) { - context.report({ - loc: node.loc, - messageId: 'missing', - data: { resource } - }); - } - } - }, - _a.NLS_MODULE = 'vs/nls', - _a); diff --git a/build/lib/eslint/utils.js b/build/lib/eslint/utils.js deleted file mode 100644 index c9af82acc4..0000000000 --- a/build/lib/eslint/utils.js +++ /dev/null @@ -1,37 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createImportRuleListener = void 0; -function createImportRuleListener(validateImport) { - function _checkImport(node) { - if (node && node.type === 'Literal' && typeof node.value === 'string') { - validateImport(node, node.value); - } - } - return { - // import ??? from 'module' - ImportDeclaration: (node) => { - _checkImport(node.source); - }, - // import('module').then(...) OR await import('module') - ['CallExpression[callee.type="Import"][arguments.length=1] > Literal']: (node) => { - _checkImport(node); - }, - // import foo = ... - ['TSImportEqualsDeclaration > TSExternalModuleReference > Literal']: (node) => { - _checkImport(node); - }, - // export ?? from 'module' - ExportAllDeclaration: (node) => { - _checkImport(node.source); - }, - // export {foo} from 'module' - ExportNamedDeclaration: (node) => { - _checkImport(node.source); - }, - }; -} -exports.createImportRuleListener = createImportRuleListener; diff --git a/build/lib/eslint/vscode-dts-cancellation.js b/build/lib/eslint/vscode-dts-cancellation.js deleted file mode 100644 index 10a4576d34..0000000000 --- a/build/lib/eslint/vscode-dts-cancellation.js +++ /dev/null @@ -1,33 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); -module.exports = new class ApiProviderNaming { - constructor() { - this.meta = { - messages: { - noToken: 'Function lacks a cancellation token, preferable as last argument', - } - }; - } - create(context) { - return { - ['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature[key.name=/^(provide|resolve).+/]']: (node) => { - let found = false; - for (const param of node.params) { - if (param.type === experimental_utils_1.AST_NODE_TYPES.Identifier) { - found = found || param.name === 'token'; - } - } - if (!found) { - context.report({ - node, - messageId: 'noToken' - }); - } - } - }; - } -}; diff --git a/build/lib/eslint/vscode-dts-create-func.js b/build/lib/eslint/vscode-dts-create-func.js deleted file mode 100644 index 108023b2bd..0000000000 --- a/build/lib/eslint/vscode-dts-create-func.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); -module.exports = new class ApiLiteralOrTypes { - constructor() { - this.meta = { - docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#creating-objects' }, - messages: { sync: '`createXYZ`-functions are constructor-replacements and therefore must return sync', } - }; - } - create(context) { - return { - ['TSDeclareFunction Identifier[name=/create.*/]']: (node) => { - const decl = node.parent; - if (decl.returnType?.typeAnnotation.type !== experimental_utils_1.AST_NODE_TYPES.TSTypeReference) { - return; - } - if (decl.returnType.typeAnnotation.typeName.type !== experimental_utils_1.AST_NODE_TYPES.Identifier) { - return; - } - const ident = decl.returnType.typeAnnotation.typeName.name; - if (ident === 'Promise' || ident === 'Thenable') { - context.report({ - node, - messageId: 'sync' - }); - } - } - }; - } -}; diff --git a/build/lib/eslint/vscode-dts-event-naming.js b/build/lib/eslint/vscode-dts-event-naming.js deleted file mode 100644 index 2565e6e03f..0000000000 --- a/build/lib/eslint/vscode-dts-event-naming.js +++ /dev/null @@ -1,86 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var _a; -const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); -module.exports = new (_a = class ApiEventNaming { - constructor() { - this.meta = { - docs: { - url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#event-naming' - }, - messages: { - naming: 'Event names must follow this patten: `on[Did|Will]`', - verb: 'Unknown verb \'{{verb}}\' - is this really a verb? Iff so, then add this verb to the configuration', - subject: 'Unknown subject \'{{subject}}\' - This subject has not been used before but it should refer to something in the API', - unknown: 'UNKNOWN event declaration, lint-rule needs tweaking' - } - }; - } - create(context) { - const config = context.options[0]; - const allowed = new Set(config.allowed); - const verbs = new Set(config.verbs); - return { - ['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node) => { - const def = node.parent?.parent?.parent; - const ident = this.getIdent(def); - if (!ident) { - // event on unknown structure... - return context.report({ - node, - message: 'unknown' - }); - } - if (allowed.has(ident.name)) { - // configured exception - return; - } - const match = ApiEventNaming._nameRegExp.exec(ident.name); - if (!match) { - context.report({ - node: ident, - messageId: 'naming' - }); - return; - } - // check that is spelled out (configured) as verb - if (!verbs.has(match[2].toLowerCase())) { - context.report({ - node: ident, - messageId: 'verb', - data: { verb: match[2] } - }); - } - // check that a subject (if present) has occurred - if (match[3]) { - const regex = new RegExp(match[3], 'ig'); - const parts = context.getSourceCode().getText().split(regex); - if (parts.length < 3) { - context.report({ - node: ident, - messageId: 'subject', - data: { subject: match[3] } - }); - } - } - } - }; - } - getIdent(def) { - if (!def) { - return; - } - if (def.type === experimental_utils_1.AST_NODE_TYPES.Identifier) { - return def; - } - else if ((def.type === experimental_utils_1.AST_NODE_TYPES.TSPropertySignature || def.type === experimental_utils_1.AST_NODE_TYPES.PropertyDefinition) && def.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier) { - return def.key; - } - return this.getIdent(def.parent); - } - }, - _a._nameRegExp = /on(Did|Will)([A-Z][a-z]+)([A-Z][a-z]+)?/, - _a); diff --git a/build/lib/eslint/vscode-dts-interface-naming.js b/build/lib/eslint/vscode-dts-interface-naming.js deleted file mode 100644 index 4b63aa203c..0000000000 --- a/build/lib/eslint/vscode-dts-interface-naming.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var _a; -module.exports = new (_a = class ApiInterfaceNaming { - constructor() { - this.meta = { - messages: { - naming: 'Interfaces must not be prefixed with uppercase `I`', - } - }; - } - create(context) { - return { - ['TSInterfaceDeclaration Identifier']: (node) => { - const name = node.name; - if (ApiInterfaceNaming._nameRegExp.test(name)) { - context.report({ - node, - messageId: 'naming' - }); - } - } - }; - } - }, - _a._nameRegExp = /I[A-Z]/, - _a); diff --git a/build/lib/eslint/vscode-dts-literal-or-types.js b/build/lib/eslint/vscode-dts-literal-or-types.js deleted file mode 100644 index 2717cfabe2..0000000000 --- a/build/lib/eslint/vscode-dts-literal-or-types.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -module.exports = new class ApiLiteralOrTypes { - constructor() { - this.meta = { - docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#enums' }, - messages: { useEnum: 'Use enums, not literal-or-types', } - }; - } - create(context) { - return { - ['TSTypeAnnotation TSUnionType']: (node) => { - if (node.types.every(value => value.type === 'TSLiteralType')) { - context.report({ - node: node, - messageId: 'useEnum' - }); - } - } - }; - } -}; diff --git a/build/lib/eslint/vscode-dts-provider-naming.js b/build/lib/eslint/vscode-dts-provider-naming.js deleted file mode 100644 index 72bda9cfab..0000000000 --- a/build/lib/eslint/vscode-dts-provider-naming.js +++ /dev/null @@ -1,37 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var _a; -module.exports = new (_a = class ApiProviderNaming { - constructor() { - this.meta = { - messages: { - naming: 'A provider should only have functions like provideXYZ or resolveXYZ', - } - }; - } - create(context) { - const config = context.options[0]; - const allowed = new Set(config.allowed); - return { - ['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature']: (node) => { - const interfaceName = (node.parent?.parent).id.name; - if (allowed.has(interfaceName)) { - // allowed - return; - } - const methodName = node.key.name; - if (!ApiProviderNaming._providerFunctionNames.test(methodName)) { - context.report({ - node, - messageId: 'naming' - }); - } - } - }; - } - }, - _a._providerFunctionNames = /^(provide|resolve|prepare).+/, - _a); diff --git a/build/lib/eslint/vscode-dts-region-comments.js b/build/lib/eslint/vscode-dts-region-comments.js deleted file mode 100644 index 5edde9d93e..0000000000 --- a/build/lib/eslint/vscode-dts-region-comments.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -module.exports = new class ApiEventNaming { - constructor() { - this.meta = { - messages: { - comment: 'region comments should start with a camel case identifier, `:`, then either a GH issue link or owner, e.g #region myProposalName: https://github.com/microsoft/vscode/issues/', - } - }; - } - create(context) { - const sourceCode = context.getSourceCode(); - return { - ['Program']: (_node) => { - for (const comment of sourceCode.getAllComments()) { - if (comment.type !== 'Line') { - continue; - } - if (!/^\s*#region /.test(comment.value)) { - continue; - } - if (!/^\s*#region ([a-z]+): (@[a-z]+|https:\/\/github.com\/microsoft\/vscode\/issues\/\d+)/i.test(comment.value)) { - context.report({ - node: comment, - messageId: 'comment', - }); - } - } - } - }; - } -}; diff --git a/build/lib/eslint/vscode-dts-use-thenable.js b/build/lib/eslint/vscode-dts-use-thenable.js deleted file mode 100644 index 4111e50ff8..0000000000 --- a/build/lib/eslint/vscode-dts-use-thenable.js +++ /dev/null @@ -1,24 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -module.exports = new class ApiEventNaming { - constructor() { - this.meta = { - messages: { - usage: 'Use the Thenable-type instead of the Promise type', - } - }; - } - create(context) { - return { - ['TSTypeAnnotation TSTypeReference Identifier[name="Promise"]']: (node) => { - context.report({ - node, - messageId: 'usage', - }); - } - }; - } -}; diff --git a/build/lib/eslint/vscode-dts-vscode-in-comments.js b/build/lib/eslint/vscode-dts-vscode-in-comments.js deleted file mode 100644 index 221004d0d6..0000000000 --- a/build/lib/eslint/vscode-dts-vscode-in-comments.js +++ /dev/null @@ -1,45 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -module.exports = new class ApiVsCodeInComments { - constructor() { - this.meta = { - messages: { - comment: `Don't use the term 'vs code' in comments` - } - }; - } - create(context) { - const sourceCode = context.getSourceCode(); - return { - ['Program']: (_node) => { - for (const comment of sourceCode.getAllComments()) { - if (comment.type !== 'Block') { - continue; - } - if (!comment.range) { - continue; - } - const startIndex = comment.range[0] + '/*'.length; - const re = /vs code/ig; - let match; - while ((match = re.exec(comment.value))) { - // Allow using 'VS Code' in quotes - if (comment.value[match.index - 1] === `'` && comment.value[match.index + match[0].length] === `'`) { - continue; - } - // Types for eslint seem incorrect - const start = sourceCode.getLocFromIndex(startIndex + match.index); - const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length); - context.report({ - messageId: 'comment', - loc: { start, end } - }); - } - } - } - }; - } -}; diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 825d90722b..1861b5c606 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -11,8 +11,6 @@ const cp = require("child_process"); const glob = require("glob"); const gulp = require("gulp"); const path = require("path"); -const through2 = require("through2"); -const got_1 = require("got"); const File = require("vinyl"); const stats_1 = require("./stats"); const util2 = require("./util"); @@ -24,9 +22,12 @@ const ansiColors = require("ansi-colors"); const buffer = require('gulp-buffer'); const jsoncParser = require("jsonc-parser"); const dependencies_1 = require("./dependencies"); -const util = require('./util'); +const builtInExtensions_1 = require("./builtInExtensions"); +const getVersion_1 = require("./getVersion"); +const gulpRemoteSource_1 = require("./gulpRemoteSource"); +const github_1 = require("./github"); const root = path.dirname(path.dirname(__dirname)); -const commit = util.getVersion(root); +const commit = (0, getVersion_1.getVersion)(root); const sourceMappingURLBase = `https://sqlopsbuilds.blob.core.windows.net/sourcemaps/${commit}`; // {{SQL CARBON EDIT}} function minifyExtensionResources(input) { const jsonFilter = filter(['**/*.json', '**/*.code-snippets'], { restore: true }); @@ -35,7 +36,7 @@ function minifyExtensionResources(input) { .pipe(buffer()) .pipe(es.mapSync((f) => { const errors = []; - const value = jsoncParser.parse(f.contents.toString('utf8'), errors); + const value = jsoncParser.parse(f.contents.toString('utf8'), errors, { allowTrailingComma: true }); if (errors.length === 0) { // file parsed OK => just stringify to drop whitespace and comments f.contents = Buffer.from(JSON.stringify(value)); @@ -182,20 +183,18 @@ const baseHeaders = { 'X-Market-User-Id': '291C1CD0-051A-4123-9B4B-30D60EF52EE2', }; function fromMarketplace(_serviceUrl, { name: extensionName, version, metadata }) { - const remote = require('gulp-remote-retry-src'); const json = require('gulp-json-editor'); const [_publisher, name] = extensionName.split('.'); // {{SQL CARBON EDIT}} We don't have the publisher in our path const url = `https://sqlopsextensions.blob.core.windows.net/extensions/${name}/${name}-${version}.vsix`; // {{SQL CARBON EDIT}} Use our own download URL fancyLog('Downloading extension:', ansiColors.yellow(`${extensionName}@${version}`), '...'); const options = { base: url, - requestOptions: { - gzip: true, + fetchOptions: { headers: baseHeaders } }; const packageJsonFilter = filter('package.json', { restore: true }); - return remote('', options) + return (0, gulpRemoteSource_1.remote)('', options) .pipe(vzip.src()) .pipe(filter('extension/**')) .pipe(rename(p => p.dirname = p.dirname.replace(/^extension\/?/, ''))) @@ -205,34 +204,11 @@ function fromMarketplace(_serviceUrl, { name: extensionName, version, metadata } .pipe(packageJsonFilter.restore); } exports.fromMarketplace = fromMarketplace; -const ghApiHeaders = { - Accept: 'application/vnd.github.v3+json', - 'User-Agent': userAgent, -}; -if (process.env.GITHUB_TOKEN) { - ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); -} -const ghDownloadHeaders = { - ...ghApiHeaders, - Accept: 'application/octet-stream', -}; function fromGithub({ name, version, repo, metadata }) { - const remote = require('gulp-remote-retry-src'); const json = require('gulp-json-editor'); fancyLog('Downloading extension from GH:', ansiColors.yellow(`${name}@${version}`), '...'); const packageJsonFilter = filter('package.json', { restore: true }); - return remote([`/repos${new URL(repo).pathname}/releases/tags/v${version}`], { - base: 'https://api.github.com', - requestOptions: { headers: ghApiHeaders } - }).pipe(through2.obj(function (file, _enc, callback) { - const asset = JSON.parse(file.contents.toString()).assets.find((a) => a.name.endsWith('.vsix')); - if (!asset) { - return callback(new Error(`Could not find vsix in release of ${repo} @ ${version}`)); - } - const res = got_1.default.stream(asset.url, { headers: ghDownloadHeaders, followRedirect: true }); - file.contents = res.pipe(through2()); - callback(null, file); - })) + return (0, github_1.assetFromGithub)(new URL(repo).pathname, version, name => name.endsWith('.vsix')) .pipe(buffer()) .pipe(vzip.src()) .pipe(filter('extension/**')) @@ -250,7 +226,6 @@ const excludedExtensions = [ 'ms-vscode.node-debug', 'ms-vscode.node-debug2', 'vscode-custom-editor-tests', - 'vscode-notebook-tests', 'integration-tests', // {{SQL CARBON EDIT}} ]; // {{SQL CARBON EDIT}} @@ -359,16 +334,15 @@ function packageLocalExtensionsStream(forWeb) { .pipe(util2.setExecutableBit(['**/*.sh']))); } exports.packageLocalExtensionsStream = packageLocalExtensionsStream; -function packageMarketplaceExtensionsStream(forWeb, galleryServiceUrl) { +function packageMarketplaceExtensionsStream(forWeb) { const marketplaceExtensionsDescriptions = [ ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), ...(forWeb ? webBuiltInExtensions : []) ]; const marketplaceExtensionsStream = minifyExtensionResources(es.merge(...marketplaceExtensionsDescriptions .map(extension => { - const input = (galleryServiceUrl ? fromMarketplace(galleryServiceUrl, extension) : fromGithub(extension)) - .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); - return updateExtensionPackageJSON(input, (data) => { + const src = (0, builtInExtensions_1.getExtensionStream)(extension).pipe(rename(p => p.dirname = `extensions/${p.dirname}`)); + return updateExtensionPackageJSON(src, (data) => { delete data.scripts; delete data.dependencies; delete data.devDependencies; @@ -492,19 +466,14 @@ async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { const webpackConfigs = []; for (const { configPath, outputRoot } of webpackConfigLocations) { const configOrFnOrArray = require(configPath); - function addConfig(configOrFn) { - let config; - if (typeof configOrFn === 'function') { - config = configOrFn({}, {}); + function addConfig(configOrFnOrArray) { + for (const configOrFn of Array.isArray(configOrFnOrArray) ? configOrFnOrArray : [configOrFnOrArray]) { + const config = typeof configOrFn === 'function' ? configOrFn({}, {}) : configOrFn; + if (outputRoot) { + config.output.path = path.join(outputRoot, path.relative(path.dirname(configPath), config.output.path)); + } webpackConfigs.push(config); } - else { - config = configOrFn; - } - if (outputRoot) { - config.output.path = path.join(outputRoot, path.relative(path.dirname(configPath), config.output.path)); - } - webpackConfigs.push(configOrFn); } addConfig(configOrFnOrArray); } @@ -597,3 +566,4 @@ async function buildExtensionMedia(isWatch, outputRoot) { }))); } exports.buildExtensionMedia = buildExtensionMedia; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXh0ZW5zaW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImV4dGVuc2lvbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsbUNBQW1DO0FBQ25DLHlCQUF5QjtBQUN6QixvQ0FBb0M7QUFDcEMsNkJBQTZCO0FBQzdCLDZCQUE2QjtBQUM3Qiw2QkFBNkI7QUFFN0IsOEJBQThCO0FBQzlCLG1DQUE0QztBQUM1QyxnQ0FBZ0M7QUFDaEMsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLGdCQUFnQixDQUFDLENBQUM7QUFDdkMsc0NBQXVDO0FBQ3ZDLHNDQUF1QztBQUN2QyxzQ0FBc0M7QUFDdEMsMENBQTBDO0FBQzFDLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQztBQUN0Qyw0Q0FBNEM7QUFFNUMsaURBQTJEO0FBQzNELDJEQUF5RDtBQUN6RCw2Q0FBMEM7QUFDMUMseURBQTJFO0FBQzNFLHFDQUEyQztBQUUzQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUNuRCxNQUFNLE1BQU0sR0FBRyxJQUFBLHVCQUFVLEVBQUMsSUFBSSxDQUFDLENBQUM7QUFDaEMsTUFBTSxvQkFBb0IsR0FBRyx5REFBeUQsTUFBTSxFQUFFLENBQUMsQ0FBQyxzQkFBc0I7QUFFdEgsU0FBUyx3QkFBd0IsQ0FBQyxLQUFhO0lBQzlDLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxDQUFDLFdBQVcsRUFBRSxvQkFBb0IsQ0FBQyxFQUFFLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFDbEYsT0FBTyxLQUFLO1NBQ1YsSUFBSSxDQUFDLFVBQVUsQ0FBQztTQUNoQixJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7U0FDZCxJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQU8sRUFBRSxF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 \ No newline at end of file diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index 73b394f347..9b8f31eede 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -9,8 +9,6 @@ import * as cp from 'child_process'; import * as glob from 'glob'; import * as gulp from 'gulp'; import * as path from 'path'; -import * as through2 from 'through2' -import got from 'got'; import { Stream } from 'stream'; import * as File from 'vinyl'; import { createStatsStream } from './stats'; @@ -24,9 +22,13 @@ const buffer = require('gulp-buffer'); import * as jsoncParser from 'jsonc-parser'; import webpack = require('webpack'); import { getProductionDependencies } from './dependencies'; -const util = require('./util'); +import { getExtensionStream } from './builtInExtensions'; +import { getVersion } from './getVersion'; +import { remote, IOptions as IRemoteSrcOptions } from './gulpRemoteSource'; +import { assetFromGithub } from './github'; + const root = path.dirname(path.dirname(__dirname)); -const commit = util.getVersion(root); +const commit = getVersion(root); const sourceMappingURLBase = `https://sqlopsbuilds.blob.core.windows.net/sourcemaps/${commit}`; // {{SQL CARBON EDIT}} function minifyExtensionResources(input: Stream): Stream { @@ -36,7 +38,7 @@ function minifyExtensionResources(input: Stream): Stream { .pipe(buffer()) .pipe(es.mapSync((f: File) => { const errors: jsoncParser.ParseError[] = []; - const value = jsoncParser.parse(f.contents.toString('utf8'), errors); + const value = jsoncParser.parse(f.contents.toString('utf8'), errors, { allowTrailingComma: true }); if (errors.length === 0) { // file parsed OK => just stringify to drop whitespace and comments f.contents = Buffer.from(JSON.stringify(value)); @@ -207,7 +209,6 @@ const baseHeaders = { }; export function fromMarketplace(_serviceUrl: string, { name: extensionName, version, metadata }: IBuiltInExtension): Stream { // {{SQL CARBON EDIT}} We don't use the passed in service URL - const remote = require('gulp-remote-retry-src'); const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); const [_publisher, name] = extensionName.split('.'); // {{SQL CARBON EDIT}} We don't have the publisher in our path @@ -215,10 +216,9 @@ export function fromMarketplace(_serviceUrl: string, { name: extensionName, vers fancyLog('Downloading extension:', ansiColors.yellow(`${extensionName}@${version}`), '...'); - const options = { + const options: IRemoteSrcOptions = { base: url, - requestOptions: { - gzip: true, + fetchOptions: { headers: baseHeaders } }; @@ -235,39 +235,15 @@ export function fromMarketplace(_serviceUrl: string, { name: extensionName, vers .pipe(packageJsonFilter.restore); } -const ghApiHeaders: Record = { - Accept: 'application/vnd.github.v3+json', - 'User-Agent': userAgent, -}; -if (process.env.GITHUB_TOKEN) { - ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); -} -const ghDownloadHeaders = { - ...ghApiHeaders, - Accept: 'application/octet-stream', -}; export function fromGithub({ name, version, repo, metadata }: IBuiltInExtension): Stream { - const remote = require('gulp-remote-retry-src'); const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); fancyLog('Downloading extension from GH:', ansiColors.yellow(`${name}@${version}`), '...'); const packageJsonFilter = filter('package.json', { restore: true }); - return remote([`/repos${new URL(repo).pathname}/releases/tags/v${version}`], { - base: 'https://api.github.com', - requestOptions: { headers: ghApiHeaders } - }).pipe(through2.obj(function (file, _enc, callback) { - const asset = JSON.parse(file.contents.toString()).assets.find((a: any) => a.name.endsWith('.vsix')); - if (!asset) { - return callback(new Error(`Could not find vsix in release of ${repo} @ ${version}`)); - } - - const res = got.stream(asset.url, { headers: ghDownloadHeaders, followRedirect: true }); - file.contents = res.pipe(through2()); - callback(null, file); - })) + return assetFromGithub(new URL(repo).pathname, version, name => name.endsWith('.vsix')) .pipe(buffer()) .pipe(vzip.src()) .pipe(filter('extension/**')) @@ -285,7 +261,6 @@ const excludedExtensions = [ 'ms-vscode.node-debug', 'ms-vscode.node-debug2', 'vscode-custom-editor-tests', - 'vscode-notebook-tests', 'integration-tests', // {{SQL CARBON EDIT}} ]; @@ -429,7 +404,7 @@ export function packageLocalExtensionsStream(forWeb: boolean): Stream { ); } -export function packageMarketplaceExtensionsStream(forWeb: boolean, galleryServiceUrl?: string): Stream { +export function packageMarketplaceExtensionsStream(forWeb: boolean): Stream { const marketplaceExtensionsDescriptions = [ ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), ...(forWeb ? webBuiltInExtensions : []) @@ -438,9 +413,8 @@ export function packageMarketplaceExtensionsStream(forWeb: boolean, galleryServi es.merge( ...marketplaceExtensionsDescriptions .map(extension => { - const input = (galleryServiceUrl ? fromMarketplace(galleryServiceUrl, extension) : fromGithub(extension)) - .pipe(rename(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); - return updateExtensionPackageJSON(input, (data: any) => { + const src = getExtensionStream(extension).pipe(rename(p => p.dirname = `extensions/${p.dirname}`)); + return updateExtensionPackageJSON(src, (data: any) => { delete data.scripts; delete data.dependencies; delete data.devDependencies; @@ -587,20 +561,14 @@ export async function webpackExtensions(taskName: string, isWatch: boolean, webp for (const { configPath, outputRoot } of webpackConfigLocations) { const configOrFnOrArray = require(configPath); - function addConfig(configOrFn: webpack.Configuration | Function) { - let config; - if (typeof configOrFn === 'function') { - config = (configOrFn as Function)({}, {}); + function addConfig(configOrFnOrArray: webpack.Configuration | ((env: unknown, args: unknown) => webpack.Configuration) | webpack.Configuration[]) { + for (const configOrFn of Array.isArray(configOrFnOrArray) ? configOrFnOrArray : [configOrFnOrArray]) { + const config = typeof configOrFn === 'function' ? configOrFn({}, {}) : configOrFn; + if (outputRoot) { + config.output!.path = path.join(outputRoot, path.relative(path.dirname(configPath), config.output!.path!)); + } webpackConfigs.push(config); - } else { - config = configOrFn; } - - if (outputRoot) { - config.output.path = path.join(outputRoot, path.relative(path.dirname(configPath), config.output.path)); - } - - webpackConfigs.push(configOrFn); } addConfig(configOrFnOrArray); } diff --git a/build/lib/getVersion.js b/build/lib/getVersion.js new file mode 100644 index 0000000000..ee8beb8a46 --- /dev/null +++ b/build/lib/getVersion.js @@ -0,0 +1,17 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getVersion = void 0; +const git = require("./git"); +function getVersion(root) { + let version = process.env['BUILD_SOURCEVERSION']; + if (!version || !/^[0-9a-f]{40}$/i.test(version.trim())) { + version = git.getVersion(root); + } + return version; +} +exports.getVersion = getVersion; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2V0VmVyc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImdldFZlcnNpb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsNkJBQTZCO0FBRTdCLFNBQWdCLFVBQVUsQ0FBQyxJQUFZO0lBQ3RDLElBQUksT0FBTyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMscUJBQXFCLENBQUMsQ0FBQztJQUVqRCxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxFQUFFO1FBQ3hELE9BQU8sR0FBRyxHQUFHLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDO0tBQy9CO0lBRUQsT0FBTyxPQUFPLENBQUM7QUFDaEIsQ0FBQztBQVJELGdDQVFDIn0= \ No newline at end of file diff --git a/build/lib/getVersion.ts b/build/lib/getVersion.ts new file mode 100644 index 0000000000..f2a7a8fd2b --- /dev/null +++ b/build/lib/getVersion.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as git from './git'; + +export function getVersion(root: string): string | undefined { + let version = process.env['BUILD_SOURCEVERSION']; + + if (!version || !/^[0-9a-f]{40}$/i.test(version.trim())) { + version = git.getVersion(root); + } + + return version; +} diff --git a/build/lib/git.js b/build/lib/git.js index 0bdd2f247e..ca5522c8f2 100644 --- a/build/lib/git.js +++ b/build/lib/git.js @@ -52,3 +52,4 @@ function getVersion(repo) { return refs[ref]; } exports.getVersion = getVersion; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2l0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZ2l0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBOzs7Z0dBR2dHO0FBQ2hHLDZCQUE2QjtBQUM3Qix5QkFBeUI7QUFFekI7O0dBRUc7QUFDSCxTQUFnQixVQUFVLENBQUMsSUFBWTtJQUN0QyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNwQyxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxNQUFNLENBQUMsQ0FBQztJQUN4QyxJQUFJLElBQVksQ0FBQztJQUVqQixJQUFJO1FBQ0gsSUFBSSxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsUUFBUSxFQUFFLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO0tBQ2hEO0lBQUMsT0FBTyxDQUFDLEVBQUU7UUFDWCxPQUFPLFNBQVMsQ0FBQztLQUNqQjtJQUVELElBQUksaUJBQWlCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFO1FBQ2pDLE9BQU8sSUFBSSxDQUFDO0tBQ1o7SUFFRCxNQUFNLFFBQVEsR0FBRyxhQUFhLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRTFDLElBQUksQ0FBQyxRQUFRLEVBQUU7UUFDZCxPQUFPLFNBQVMsQ0FBQztLQUNqQjtJQUVELE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN4QixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUVwQyxJQUFJO1FBQ0gsT0FBTyxFQUFFLENBQUMsWUFBWSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztLQUMvQztJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1gsT0FBTztLQUNQO0lBRUQsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsYUFBYSxDQUFDLENBQUM7SUFDckQsSUFBSSxPQUFlLENBQUM7SUFFcEIsSUFBSTtRQUNILE9BQU8sR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLGNBQWMsRUFBRSxNQUFNLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztLQUN6RDtJQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQ1gsT0FBTyxTQUFTLENBQUM7S0FDakI7SUFFRCxNQUFNLFNBQVMsR0FBRywyQkFBMkIsQ0FBQztJQUM5QyxJQUFJLFNBQWlDLENBQUM7SUFDdEMsTUFBTSxJQUFJLEdBQThCLEVBQUUsQ0FBQztJQUUzQyxPQUFPLFNBQVMsR0FBRyxTQUFTLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQzNDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7S0FDbEM7SUFFRCxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztBQUNsQixDQUFDO0FBaERELGdDQWdEQyJ9 \ No newline at end of file diff --git a/build/lib/github.js b/build/lib/github.js new file mode 100644 index 0000000000..01b2cd2e3d --- /dev/null +++ b/build/lib/github.js @@ -0,0 +1,48 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.assetFromGithub = void 0; +const node_fetch_1 = require("node-fetch"); +const gulpRemoteSource_1 = require("./gulpRemoteSource"); +const through2 = require("through2"); +const ghApiHeaders = { + Accept: 'application/vnd.github.v3+json', + 'User-Agent': 'VSCode Build', +}; +if (process.env.GITHUB_TOKEN) { + ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); +} +const ghDownloadHeaders = { + ...ghApiHeaders, + Accept: 'application/octet-stream', +}; +/** + * @param repo for example `Microsoft/vscode` + * @param version for example `16.17.1` - must be a valid releases tag + * @param assetName for example (name) => name === `win-x64-node.exe` - must be an asset that exists + * @returns a stream with the asset as file + */ +function assetFromGithub(repo, version, assetFilter) { + return (0, gulpRemoteSource_1.remote)(`/repos/${repo.replace(/^\/|\/$/g, '')}/releases/tags/v${version}`, { + base: 'https://api.github.com', + fetchOptions: { headers: ghApiHeaders } + }).pipe(through2.obj(async function (file, _enc, callback) { + const asset = JSON.parse(file.contents.toString()).assets.find((a) => assetFilter(a.name)); + if (!asset) { + return callback(new Error(`Could not find asset in release of ${repo} @ ${version}`)); + } + const response = await (0, node_fetch_1.default)(asset.url, { headers: ghDownloadHeaders }); + if (response.ok) { + file.contents = response.body.pipe(through2()); + callback(null, file); + } + else { + return callback(new Error(`Request ${response.url} failed with status code: ${response.status}`)); + } + })); +} +exports.assetFromGithub = assetFromGithub; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2l0aHViLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZ2l0aHViLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBR2hHLDJDQUErQjtBQUMvQix5REFBNEM7QUFDNUMscUNBQXFDO0FBRXJDLE1BQU0sWUFBWSxHQUEyQjtJQUM1QyxNQUFNLEVBQUUsZ0NBQWdDO0lBQ3hDLFlBQVksRUFBRSxjQUFjO0NBQzVCLENBQUM7QUFDRixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxFQUFFO0lBQzdCLFlBQVksQ0FBQyxhQUFhLEdBQUcsUUFBUSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7Q0FDakc7QUFDRCxNQUFNLGlCQUFpQixHQUFHO0lBQ3pCLEdBQUcsWUFBWTtJQUNmLE1BQU0sRUFBRSwwQkFBMEI7Q0FDbEMsQ0FBQztBQUVGOzs7OztHQUtHO0FBQ0gsU0FBZ0IsZUFBZSxDQUFDLElBQVksRUFBRSxPQUFlLEVBQUUsV0FBc0M7SUFDcEcsT0FBTyxJQUFBLHlCQUFNLEVBQUMsVUFBVSxJQUFJLENBQUMsT0FBTyxDQUFDLFVBQVUsRUFBRSxFQUFFLENBQUMsbUJBQW1CLE9BQU8sRUFBRSxFQUFFO1FBQ2pGLElBQUksRUFBRSx3QkFBd0I7UUFDOUIsWUFBWSxFQUFFLEVBQUUsT0FBTyxFQUFFLFlBQVksRUFBRTtLQUN2QyxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsS0FBSyxXQUFXLElBQUksRUFBRSxJQUFJLEVBQUUsUUFBUTtRQUN4RCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBbUIsRUFBRSxFQUFFLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQzdHLElBQUksQ0FBQyxLQUFLLEVBQUU7WUFDWCxPQUFPLFFBQVEsQ0FBQyxJQUFJLEtBQUssQ0FBQyxzQ0FBc0MsSUFBSSxNQUFNLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztTQUN0RjtRQUNELE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBQSxvQkFBSyxFQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsRUFBRSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsQ0FBQyxDQUFDO1FBQ3hFLElBQUksUUFBUSxDQUFDLEVBQUUsRUFBRTtZQUNoQixJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7WUFDL0MsUUFBUSxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztTQUNyQjthQUFNO1lBQ04sT0FBTyxRQUFRLENBQUMsSUFBSSxLQUFLLENBQUMsV0FBVyxRQUFRLENBQUMsR0FBRyw2QkFBNkIsUUFBUSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQztTQUNsRztJQUVGLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDO0FBbEJELDBDQWtCQyJ9 \ No newline at end of file diff --git a/build/lib/github.ts b/build/lib/github.ts new file mode 100644 index 0000000000..d000de8aa9 --- /dev/null +++ b/build/lib/github.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Stream } from 'stream'; +import fetch from 'node-fetch'; +import { remote } from './gulpRemoteSource'; +import * as through2 from 'through2'; + +const ghApiHeaders: Record = { + Accept: 'application/vnd.github.v3+json', + 'User-Agent': 'VSCode Build', +}; +if (process.env.GITHUB_TOKEN) { + ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); +} +const ghDownloadHeaders = { + ...ghApiHeaders, + Accept: 'application/octet-stream', +}; + +/** + * @param repo for example `Microsoft/vscode` + * @param version for example `16.17.1` - must be a valid releases tag + * @param assetName for example (name) => name === `win-x64-node.exe` - must be an asset that exists + * @returns a stream with the asset as file + */ +export function assetFromGithub(repo: string, version: string, assetFilter: (name: string) => boolean): Stream { + return remote(`/repos/${repo.replace(/^\/|\/$/g, '')}/releases/tags/v${version}`, { + base: 'https://api.github.com', + fetchOptions: { headers: ghApiHeaders } + }).pipe(through2.obj(async function (file, _enc, callback) { + const asset = JSON.parse(file.contents.toString()).assets.find((a: { name: string }) => assetFilter(a.name)); + if (!asset) { + return callback(new Error(`Could not find asset in release of ${repo} @ ${version}`)); + } + const response = await fetch(asset.url, { headers: ghDownloadHeaders }); + if (response.ok) { + file.contents = response.body.pipe(through2()); + callback(null, file); + } else { + return callback(new Error(`Request ${response.url} failed with status code: ${response.status}`)); + } + + })); +} diff --git a/build/lib/gulpRemoteSource.js b/build/lib/gulpRemoteSource.js new file mode 100644 index 0000000000..18b30d7de0 --- /dev/null +++ b/build/lib/gulpRemoteSource.js @@ -0,0 +1,71 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.remote = void 0; +const es = require("event-stream"); +const node_fetch_1 = require("node-fetch"); +const VinylFile = require("vinyl"); +const through2 = require("through2"); +const log = require("fancy-log"); +const ansiColors = require("ansi-colors"); +function remote(urls, options) { + if (options === undefined) { + options = {}; + } + if (typeof options.base !== 'string' && options.base !== null) { + options.base = '/'; + } + if (typeof options.buffer !== 'boolean') { + options.buffer = true; + } + if (!Array.isArray(urls)) { + urls = [urls]; + } + return es.readArray(urls).pipe(es.map((data, cb) => { + const url = [options.base, data].join(''); + fetchWithRetry(url, options).then(file => { + cb(undefined, file); + }, error => { + cb(error); + }); + })); +} +exports.remote = remote; +async function fetchWithRetry(url, options, retries = 3, retryDelay = 250) { + try { + let startTime = 0; + if (options.verbose) { + log(`Start fetching ${ansiColors.magenta(url)}${retries !== 3 ? `(${3 - retries} retry}` : ''}`); + startTime = new Date().getTime(); + } + const response = await (0, node_fetch_1.default)(url, options.fetchOptions); + if (options.verbose) { + log(`Fetch completed: Status ${response.status}. Took ${ansiColors.magenta(`${new Date().getTime() - startTime} ms`)}`); + } + if (response.ok && (response.status >= 200 && response.status < 300)) { + // request must be piped out once created, or we'll get this error: "You cannot pipe after data has been emitted from the response." + const contents = options.buffer ? await response.buffer() : response.body.pipe(through2()); + return new VinylFile({ + cwd: '/', + base: options.base, + path: url, + contents + }); + } + throw new Error(`Request ${ansiColors.magenta(url)} failed with status code: ${response.status}`); + } + catch (e) { + if (retries > 0) { + await new Promise(c => setTimeout(c, retryDelay)); + return fetchWithRetry(url, options, retries - 1, retryDelay * 3); + } + if (options.verbose) { + log(`Fetching ${ansiColors.cyan(url)} failed: ${e}`); + } + throw e; + } +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ3VscFJlbW90ZVNvdXJjZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImd1bHBSZW1vdGVTb3VyY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsbUNBQW1DO0FBQ25DLDJDQUFnRDtBQUNoRCxtQ0FBbUM7QUFDbkMscUNBQXFDO0FBQ3JDLGlDQUFpQztBQUNqQywwQ0FBMEM7QUFTMUMsU0FBZ0IsTUFBTSxDQUFDLElBQXVCLEVBQUUsT0FBaUI7SUFDaEUsSUFBSSxPQUFPLEtBQUssU0FBUyxFQUFFO1FBQzFCLE9BQU8sR0FBRyxFQUFFLENBQUM7S0FDYjtJQUVELElBQUksT0FBTyxPQUFPLENBQUMsSUFBSSxLQUFLLFFBQVEsSUFBSSxPQUFPLENBQUMsSUFBSSxLQUFLLElBQUksRUFBRTtRQUM5RCxPQUFPLENBQUMsSUFBSSxHQUFHLEdBQUcsQ0FBQztLQUNuQjtJQUVELElBQUksT0FBTyxPQUFPLENBQUMsTUFBTSxLQUFLLFNBQVMsRUFBRTtRQUN4QyxPQUFPLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQztLQUN0QjtJQUVELElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFO1FBQ3pCLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO0tBQ2Q7SUFFRCxPQUFPLEVBQUUsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxHQUFHLENBQTJCLENBQUMsSUFBWSxFQUFFLEVBQUUsRUFBRSxFQUFFO1FBQ3BGLE1BQU0sR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDMUMsY0FBYyxDQUFDLEdBQUcsRUFBRSxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFDeEMsRUFBRSxDQUFDLFNBQVMsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNyQixDQUFDLEVBQUUsS0FBSyxDQUFDLEVBQUU7WUFDVixFQUFFLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDWCxDQUFDLENBQUMsQ0FBQztJQUNKLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDO0FBekJELHdCQXlCQztBQUVELEtBQUssVUFBVSxjQUFjLENBQUMsR0FBVyxFQUFFLE9BQWlCLEVBQUUsT0FBTyxHQUFHLENBQUMsRUFBRSxVQUFVLEdBQUcsR0FBRztJQUMxRixJQUFJO1FBQ0gsSUFBSSxTQUFTLEdBQUcsQ0FBQyxDQUFDO1FBQ2xCLElBQUksT0FBTyxDQUFDLE9BQU8sRUFBRTtZQUNwQixHQUFHLENBQUMsa0JBQWtCLFVBQVUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLEdBQUcsT0FBTyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsT0FBTyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDakcsU0FBUyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUM7U0FDakM7UUFDRCxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUEsb0JBQUssRUFBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ3hELElBQUksT0FBTyxDQUFDLE9BQU8sRUFBRTtZQUNwQixHQUFHLENBQUMsMkJBQTJCLFFBQVEsQ0FBQyxNQUFNLFVBQVUsVUFBVSxDQUFDLE9BQU8sQ0FBQyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUMsT0FBTyxFQUFFLEdBQUcsU0FBUyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7U0FDeEg7UUFDRCxJQUFJLFFBQVEsQ0FBQyxFQUFFLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxJQUFJLEdBQUcsSUFBSSxRQUFRLENBQUMsTUFBTSxHQUFHLEdBQUcsQ0FBQyxFQUFFO1lBQ3JFLG9JQUFvSTtZQUNwSSxNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxNQUFNLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztZQUMzRixPQUFPLElBQUksU0FBUyxDQUFDO2dCQUNwQixHQUFHLEVBQUUsR0FBRztnQkFDUixJQUFJLEVBQUUsT0FBTyxDQUFDLElBQUk7Z0JBQ2xCLElBQUksRUFBRSxHQUFHO2dCQUNULFFBQVE7YUFDUixDQUFDLENBQUM7U0FDSDtRQUNELE1BQU0sSUFBSSxLQUFLLENBQUMsV0FBVyxVQUFVLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyw2QkFBNkIsUUFBUSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7S0FDbEc7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNYLElBQUksT0FBTyxHQUFHLENBQUMsRUFBRTtZQUNoQixNQUFNLElBQUksT0FBTyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDO1lBQ2xELE9BQU8sY0FBYyxDQUFDLEdBQUcsRUFBRSxPQUFPLEVBQUUsT0FBTyxHQUFHLENBQUMsRUFBRSxVQUFVLEdBQUcsQ0FBQyxDQUFDLENBQUM7U0FDakU7UUFDRCxJQUFJLE9BQU8sQ0FBQyxPQUFPLEVBQUU7WUFDcEIsR0FBRyxDQUFDLFlBQVksVUFBVSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1NBQ3JEO1FBQ0QsTUFBTSxDQUFDLENBQUM7S0FDUjtBQUNGLENBQUMifQ== \ No newline at end of file diff --git a/build/lib/gulpRemoteSource.ts b/build/lib/gulpRemoteSource.ts new file mode 100644 index 0000000000..04e1108a4c --- /dev/null +++ b/build/lib/gulpRemoteSource.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as es from 'event-stream'; +import fetch, { RequestInit } from 'node-fetch'; +import * as VinylFile from 'vinyl'; +import * as through2 from 'through2'; +import * as log from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; + +export interface IOptions { + base?: string; + buffer?: boolean; + fetchOptions?: RequestInit; + verbose?: boolean; +} + +export function remote(urls: string[] | string, options: IOptions): es.ThroughStream { + if (options === undefined) { + options = {}; + } + + if (typeof options.base !== 'string' && options.base !== null) { + options.base = '/'; + } + + if (typeof options.buffer !== 'boolean') { + options.buffer = true; + } + + if (!Array.isArray(urls)) { + urls = [urls]; + } + + return es.readArray(urls).pipe(es.map((data: string, cb) => { + const url = [options.base, data].join(''); + fetchWithRetry(url, options).then(file => { + cb(undefined, file); + }, error => { + cb(error); + }); + })); +} + +async function fetchWithRetry(url: string, options: IOptions, retries = 3, retryDelay = 250): Promise { + try { + let startTime = 0; + if (options.verbose) { + log(`Start fetching ${ansiColors.magenta(url)}${retries !== 3 ? `(${3 - retries} retry}` : ''}`); + startTime = new Date().getTime(); + } + const response = await fetch(url, options.fetchOptions); + if (options.verbose) { + log(`Fetch completed: Status ${response.status}. Took ${ansiColors.magenta(`${new Date().getTime() - startTime} ms`)}`); + } + if (response.ok && (response.status >= 200 && response.status < 300)) { + // request must be piped out once created, or we'll get this error: "You cannot pipe after data has been emitted from the response." + const contents = options.buffer ? await response.buffer() : response.body.pipe(through2()); + return new VinylFile({ + cwd: '/', + base: options.base, + path: url, + contents + }); + } + throw new Error(`Request ${ansiColors.magenta(url)} failed with status code: ${response.status}`); + } catch (e) { + if (retries > 0) { + await new Promise(c => setTimeout(c, retryDelay)); + return fetchWithRetry(url, options, retries - 1, retryDelay * 3); + } + if (options.verbose) { + log(`Fetching ${ansiColors.cyan(url)} failed: ${e}`); + } + throw e; + } +} + + + + diff --git a/build/lib/i18n.js b/build/lib/i18n.js index 3f0ec18b0a..38bd7b1793 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -4,19 +4,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.prepareIslFiles = exports.prepareI18nPackFiles = exports.i18nPackVersion = exports.createI18nFile = exports.prepareI18nFiles = exports.pullSetupXlfFiles = exports.findObsoleteResources = exports.pushXlfFiles = exports.createXlfFilesForIsl = exports.createXlfFilesForExtensions = exports.createXlfFilesForCoreBundle = exports.getResource = exports.processNlsFiles = exports.Limiter = exports.XLF = exports.Line = exports.externalExtensionsWithTranslations = exports.extraLanguages = exports.defaultLanguages = void 0; +exports.prepareIslFiles = exports.prepareI18nPackFiles = exports.i18nPackVersion = exports.createI18nFile = exports.createXlfFilesForIsl = exports.createXlfFilesForExtensions = exports.EXTERNAL_EXTENSIONS = exports.createXlfFilesForCoreBundle = exports.getResource = exports.processNlsFiles = exports.XLF = exports.Line = exports.extraLanguages = exports.defaultLanguages = void 0; const path = require("path"); const fs = require("fs"); const event_stream_1 = require("event-stream"); +const jsonMerge = require("gulp-merge-json"); const File = require("vinyl"); const Is = require("is"); const xml2js = require("xml2js"); -const https = require("https"); const gulp = require("gulp"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); const iconv = require("@vscode/iconv-lite-umd"); -const NUMBER_OF_CONCURRENT_DOWNLOADS = 4; +const l10n_dev_1 = require("@vscode/l10n-dev"); function log(message, ...rest) { fancyLog(ansiColors.green('[i18n]'), message, ...rest); } @@ -37,12 +37,6 @@ exports.extraLanguages = [ { id: 'hu', folderName: 'hun' }, { id: 'tr', folderName: 'trk' } ]; -// non built-in extensions also that are transifex and need to be part of the language packs -exports.externalExtensionsWithTranslations = { - 'vscode-chrome-debug': 'msjsdiag.debugger-for-chrome', - 'vscode-node-debug': 'ms-vscode.node-debug', - 'vscode-node-debug2': 'ms-vscode.node-debug2' -}; var LocalizeInfo; (function (LocalizeInfo) { function is(value) { @@ -63,22 +57,9 @@ var BundledFormat; } BundledFormat.is = is; })(BundledFormat || (BundledFormat = {})); -var PackageJsonFormat; -(function (PackageJsonFormat) { - function is(value) { - if (Is.undef(value) || !Is.object(value)) { - return false; - } - return Object.keys(value).every(key => { - const element = value[key]; - return Is.string(element) || (Is.object(element) && Is.defined(element.message) && Is.defined(element.comment)); - }); - } - PackageJsonFormat.is = is; -})(PackageJsonFormat || (PackageJsonFormat = {})); class Line { + buffer = []; constructor(indent = 0) { - this.buffer = []; if (indent > 0) { this.buffer.push(new Array(indent + 1).join(' ')); } @@ -93,6 +74,7 @@ class Line { } exports.Line = Line; class TextModel { + _lines; constructor(contents) { this._lines = contents.split(/\r\n|\r|\n/); } @@ -101,6 +83,10 @@ class TextModel { } } class XLF { + project; + buffer; + files; + numberOfMessages; constructor(project) { this.project = project; this.buffer = []; @@ -182,107 +168,127 @@ class XLF { line.append(content); this.buffer.push(line.toString()); } + static parsePseudo = function (xlfString) { + return new Promise((resolve) => { + const parser = new xml2js.Parser(); + const files = []; + parser.parseString(xlfString, function (_err, result) { + const fileNodes = result['xliff']['file']; + fileNodes.forEach(file => { + const originalFilePath = file.$.original; + const messages = {}; + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit) => { + const key = unit.$.id; + const val = pseudify(unit.source[0]['_'].toString()); + if (key && val) { + messages[key] = decodeEntities(val); + } + }); + files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' }); + } + }); + resolve(files); + }); + }); + }; + static org_parse = function (xlfString) { + return new Promise((resolve, reject) => { + const parser = new xml2js.Parser(); + const files = []; + parser.parseString(xlfString, function (err, result) { + if (err) { + reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); + } + const fileNodes = result['xliff']['file']; + if (!fileNodes) { + reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); + } + fileNodes.forEach((file) => { + const originalFilePath = file.$.original; + if (!originalFilePath) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); + } + const language = file.$['target-language']; + if (!language) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); + } + const messages = {}; + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit) => { + const key = unit.$.id; + if (!unit.target) { + return; // No translation available + } + let val = unit.target[0]; + if (typeof val !== 'string') { + // We allow empty source values so support them for translations as well. + val = val._ ? val._ : ''; + } + if (!key) { + reject(new Error(`XLF parsing error: trans-unit ${JSON.stringify(unit, undefined, 0)} defined in file ${originalFilePath} is missing the ID attribute.`)); + return; + } + messages[key] = decodeEntities(val); + }); + files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() }); + } + }); + resolve(files); + }); + }); + }; + static parse = function (xlfString) { + return new Promise((resolve, reject) => { + const parser = new xml2js.Parser(); + const files = []; + parser.parseString(xlfString, function (err, result) { + if (err) { + reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); + } + const fileNodes = result['xliff']['file']; + if (!fileNodes) { + reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); + } + fileNodes.forEach((file) => { + const name = file.$.original; + if (!name) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); + } + const language = file.$['target-language']; + if (!language) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); + } + const messages = {}; + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit) => { + const key = unit.$.id; + if (!unit.target) { + return; // No translation available + } + let val = unit.target[0]; + if (typeof val !== 'string') { + // We allow empty source values so support them for translations as well. + val = val._ ? val._ : ''; + } + if (!key) { + reject(new Error(`XLF parsing error: trans-unit ${JSON.stringify(unit, undefined, 0)} defined in file ${name} is missing the ID attribute.`)); + return; + } + messages[key] = decodeEntities(val); + }); + files.push({ messages, name, language: language.toLowerCase() }); + } + }); + resolve(files); + }); + }); + }; } exports.XLF = XLF; -XLF.parsePseudo = function (xlfString) { - return new Promise((resolve) => { - const parser = new xml2js.Parser(); - const files = []; - parser.parseString(xlfString, function (_err, result) { - const fileNodes = result['xliff']['file']; - fileNodes.forEach(file => { - const originalFilePath = file.$.original; - const messages = {}; - const transUnits = file.body[0]['trans-unit']; - if (transUnits) { - transUnits.forEach((unit) => { - const key = unit.$.id; - const val = pseudify(unit.source[0]['_'].toString()); - if (key && val) { - messages[key] = decodeEntities(val); - } - }); - files.push({ messages: messages, originalFilePath: originalFilePath, language: 'ps' }); - } - }); - resolve(files); - }); - }); -}; -XLF.parse = function (xlfString) { - return new Promise((resolve, reject) => { - const parser = new xml2js.Parser(); - const files = []; - parser.parseString(xlfString, function (err, result) { - if (err) { - reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); - } - const fileNodes = result['xliff']['file']; - if (!fileNodes) { - reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); - } - fileNodes.forEach((file) => { - const originalFilePath = file.$.original; - if (!originalFilePath) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); - } - const language = file.$['target-language']; - if (!language) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); - } - const messages = {}; - const transUnits = file.body[0]['trans-unit']; - if (transUnits) { - transUnits.forEach((unit) => { - const key = unit.$.id; - if (!unit.target) { - return; // No translation available - } - let val = unit.target[0]; - if (typeof val !== 'string') { - // We allow empty source values so support them for translations as well. - val = val._ ? val._ : ''; - } - if (!key) { - reject(new Error(`XLF parsing error: trans-unit ${JSON.stringify(unit, undefined, 0)} defined in file ${originalFilePath} is missing the ID attribute.`)); - return; - } - messages[key] = decodeEntities(val); - }); - files.push({ messages: messages, originalFilePath: originalFilePath, language: language.toLowerCase() }); - } - }); - resolve(files); - }); - }); -}; -class Limiter { - constructor(maxDegreeOfParalellism) { - this.maxDegreeOfParalellism = maxDegreeOfParalellism; - this.outstandingPromises = []; - this.runningPromises = 0; - } - queue(factory) { - return new Promise((c, e) => { - this.outstandingPromises.push({ factory, c, e }); - this.consume(); - }); - } - consume() { - while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { - const iLimitedTask = this.outstandingPromises.shift(); - this.runningPromises++; - const promise = iLimitedTask.factory(); - promise.then(iLimitedTask.c).catch(iLimitedTask.e); - promise.then(() => this.consumed()).catch(() => this.consumed()); - } - } - consumed() { - this.runningPromises--; - this.consume(); - } -} -exports.Limiter = Limiter; function sortLanguages(languages) { return languages.sort((a, b) => { return a.id < b.id ? -1 : (a.id > b.id ? 1 : 0); @@ -491,7 +497,7 @@ function processNlsFiles(opts) { }); } exports.processNlsFiles = processNlsFiles; -const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup'; +const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup', serverProject = 'vscode-server'; // {{SQL CARBON EDIT}} const adsProject = 'ads-core'; function getResource(sourceFile) { @@ -511,6 +517,9 @@ function getResource(sourceFile) { else if (/^vs\/code/.test(sourceFile)) { return { name: 'vs/code', project: workbenchProject }; } + else if (/^vs\/server/.test(sourceFile)) { + return { name: 'vs/server', project: serverProject }; + } else if (/^vs\/workbench\/contrib/.test(sourceFile)) { resource = sourceFile.split('/', 4).join('/'); return { name: resource, project: workbenchProject }; @@ -581,6 +590,70 @@ function createXlfFilesForCoreBundle() { }); } exports.createXlfFilesForCoreBundle = createXlfFilesForCoreBundle; +function createL10nBundleForExtension(extensionFolderName, prefixWithBuildFolder) { + const prefix = prefixWithBuildFolder ? '.build/' : ''; + return gulp + .src([ + // For source code of extensions + `${prefix}extensions/${extensionFolderName}/{src,client,server}/**/*.{ts,tsx}`, + // // For any dependencies pulled in (think vscode-css-languageservice or @vscode/emmet-helper) + `${prefix}extensions/${extensionFolderName}/**/node_modules/{@vscode,vscode-*}/**/*.{js,jsx}`, + // // For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle + `${prefix}extensions/${extensionFolderName}/**/bundle.l10n.json`, + ]) + .pipe((0, event_stream_1.map)(function (data, callback) { + const file = data; + if (!file.isBuffer()) { + // Not a buffer so we drop it + callback(); + return; + } + const extension = path.extname(file.relative); + if (extension !== '.json') { + const contents = file.contents.toString('utf8'); + (0, l10n_dev_1.getL10nJson)([{ contents, extension }]) + .then((json) => { + callback(undefined, new File({ + path: `extensions/${extensionFolderName}/bundle.l10n.json`, + contents: Buffer.from(JSON.stringify(json), 'utf8') + })); + }) + .catch((err) => { + callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); + }); + // signal pause? + return false; + } + // for bundle.l10n.jsons + let bundleJson; + try { + bundleJson = JSON.parse(file.contents.toString('utf8')); + } + catch (err) { + callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); + return; + } + // some validation of the bundle.l10n.json format + for (const key in bundleJson) { + if (typeof bundleJson[key] !== 'string' && + (typeof bundleJson[key].message !== 'string' || !Array.isArray(bundleJson[key].comment))) { + callback(new Error(`Invalid bundle.l10n.json file. The value for key ${key} is not in the expected format.`)); + return; + } + } + callback(undefined, file); + })) + .pipe(jsonMerge({ + fileName: `extensions/${extensionFolderName}/bundle.l10n.json`, + jsonSpace: '', + concatArrays: true + })); +} +exports.EXTERNAL_EXTENSIONS = [ + 'ms-vscode.js-debug', + 'ms-vscode.js-debug-companion', + 'ms-vscode.vscode-js-profile-table', +]; function createXlfFilesForExtensions() { let counter = 0; let folderStreamEnded = false; @@ -591,64 +664,60 @@ function createXlfFilesForExtensions() { if (!stat.isDirectory()) { return; } - const extensionName = path.basename(extensionFolder.path); - if (extensionName === 'node_modules') { + const extensionFolderName = path.basename(extensionFolder.path); + if (extensionFolderName === 'node_modules') { return; } + // Get extension id and use that as the id + const manifest = fs.readFileSync(path.join(extensionFolder.path, 'package.json'), 'utf-8'); + const manifestJson = JSON.parse(manifest); + const extensionId = manifestJson.publisher + '.' + manifestJson.name; counter++; - let _xlf; - function getXlf() { - if (!_xlf) { - _xlf = new XLF(extensionsProject); + let _l10nMap; + function getL10nMap() { + if (!_l10nMap) { + _l10nMap = new Map(); } - return _xlf; + return _l10nMap; } - gulp.src([`.build/extensions/${extensionName}/package.nls.json`, `.build/extensions/${extensionName}/**/nls.metadata.json`], { allowEmpty: true }).pipe((0, event_stream_1.through)(function (file) { + (0, event_stream_1.merge)(gulp.src([`.build/extensions/${extensionFolderName}/package.nls.json`, `.build/extensions/${extensionFolderName}/**/nls.metadata.json`], { allowEmpty: true }), createL10nBundleForExtension(extensionFolderName, exports.EXTERNAL_EXTENSIONS.includes(extensionId))).pipe((0, event_stream_1.through)(function (file) { if (file.isBuffer()) { const buffer = file.contents; const basename = path.basename(file.path); if (basename === 'package.nls.json') { const json = JSON.parse(buffer.toString('utf8')); - const keys = []; - const messages = []; - Object.keys(json).forEach((key) => { - const value = json[key]; - if (Is.string(value)) { - keys.push(key); - messages.push(value); - } - else if (value) { - keys.push({ - key, - comment: value.comment - }); - messages.push(value.message); - } - else { - keys.push(key); - messages.push(`Unknown message for key: ${key}`); - } - }); - getXlf().addFile(`extensions/${extensionName}/package`, keys, messages); + getL10nMap().set(`extensions/${extensionId}/package`, json); } else if (basename === 'nls.metadata.json') { const json = JSON.parse(buffer.toString('utf8')); - const relPath = path.relative(`.build/extensions/${extensionName}`, path.dirname(file.path)); + const relPath = path.relative(`.build/extensions/${extensionFolderName}`, path.dirname(file.path)); for (const file in json) { const fileContent = json[file]; - getXlf().addFile(`extensions/${extensionName}/${relPath}/${file}`, fileContent.keys, fileContent.messages); + const info = Object.create(null); + for (let i = 0; i < fileContent.messages.length; i++) { + const message = fileContent.messages[i]; + const { key, comment } = LocalizeInfo.is(fileContent.keys[i]) + ? fileContent.keys[i] + : { key: fileContent.keys[i], comment: undefined }; + info[key] = comment ? { message, comment } : message; + } + getL10nMap().set(`extensions/${extensionId}/${relPath}/${file}`, info); } } + else if (basename === 'bundle.l10n.json') { + const json = JSON.parse(buffer.toString('utf8')); + getL10nMap().set(`extensions/${extensionId}/bundle`, json); + } else { this.emit('error', new Error(`${file.path} is not a valid extension nls file`)); return; } } }, function () { - if (_xlf) { + if (_l10nMap?.size > 0) { const xlfFile = new File({ - path: path.join(extensionsProject, extensionName + '.xlf'), - contents: Buffer.from(_xlf.toString(), 'utf8') + path: path.join(extensionsProject, extensionId + '.xlf'), + contents: Buffer.from((0, l10n_dev_1.getL10nXlf)(_l10nMap), 'utf8') }); folderStream.queue(xlfFile); } @@ -719,300 +788,8 @@ function createXlfFilesForIsl() { }); } exports.createXlfFilesForIsl = createXlfFilesForIsl; -function pushXlfFiles(apiHostname, username, password) { - const tryGetPromises = []; - const updateCreatePromises = []; - return (0, event_stream_1.through)(function (file) { - const project = path.dirname(file.relative); - const fileName = path.basename(file.path); - const slug = fileName.substr(0, fileName.length - '.xlf'.length); - const credentials = `${username}:${password}`; - // Check if resource already exists, if not, then create it. - let promise = tryGetResource(project, slug, apiHostname, credentials); - tryGetPromises.push(promise); - promise.then(exists => { - if (exists) { - promise = updateResource(project, slug, file, apiHostname, credentials); - } - else { - promise = createResource(project, slug, file, apiHostname, credentials); - } - updateCreatePromises.push(promise); - }); - }, function () { - // End the pipe only after all the communication with Transifex API happened - Promise.all(tryGetPromises).then(() => { - Promise.all(updateCreatePromises).then(() => { - this.queue(null); - }).catch((reason) => { throw new Error(reason); }); - }).catch((reason) => { throw new Error(reason); }); - }); -} -exports.pushXlfFiles = pushXlfFiles; -function getAllResources(project, apiHostname, username, password) { - return new Promise((resolve, reject) => { - const credentials = `${username}:${password}`; - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resources`, - auth: credentials, - method: 'GET' - }; - const request = https.request(options, (res) => { - const buffer = []; - res.on('data', (chunk) => buffer.push(chunk)); - res.on('end', () => { - if (res.statusCode === 200) { - const json = JSON.parse(Buffer.concat(buffer).toString()); - if (Array.isArray(json)) { - resolve(json.map(o => o.slug)); - return; - } - reject(`Unexpected data format. Response code: ${res.statusCode}.`); - } - else { - reject(`No resources in ${project} returned no data. Response code: ${res.statusCode}.`); - } - }); - }); - request.on('error', (err) => { - reject(`Failed to query resources in ${project} with the following error: ${err}. ${options.path}`); - }); - request.end(); - }); -} -function findObsoleteResources(apiHostname, username, password) { - const resourcesByProject = Object.create(null); - resourcesByProject[extensionsProject] = [].concat(exports.externalExtensionsWithTranslations); // clone - return (0, event_stream_1.through)(function (file) { - const project = path.dirname(file.relative); - const fileName = path.basename(file.path); - const slug = fileName.substr(0, fileName.length - '.xlf'.length); - let slugs = resourcesByProject[project]; - if (!slugs) { - resourcesByProject[project] = slugs = []; - } - slugs.push(slug); - this.push(file); - }, function () { - const json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8')); - const i18Resources = [...json.editor, ...json.workbench].map((r) => r.project + '/' + r.name.replace(/\//g, '_')); - const extractedResources = []; - for (const project of [workbenchProject, editorProject]) { - for (const resource of resourcesByProject[project]) { - if (resource !== 'setup_messages') { - extractedResources.push(project + '/' + resource); - } - } - } - if (i18Resources.length !== extractedResources.length) { - console.log(`[i18n] Obsolete resources in file 'build/lib/i18n.resources.json': JSON.stringify(${i18Resources.filter(p => extractedResources.indexOf(p) === -1)})`); - console.log(`[i18n] Missing resources in file 'build/lib/i18n.resources.json': JSON.stringify(${extractedResources.filter(p => i18Resources.indexOf(p) === -1)})`); - } - const promises = []; - for (const project in resourcesByProject) { - promises.push(getAllResources(project, apiHostname, username, password).then(resources => { - const expectedResources = resourcesByProject[project]; - const unusedResources = resources.filter(resource => resource && expectedResources.indexOf(resource) === -1); - if (unusedResources.length) { - console.log(`[transifex] Obsolete resources in project '${project}': ${unusedResources.join(', ')}`); - } - })); - } - return Promise.all(promises).then(_ => { - this.push(null); - }).catch((reason) => { throw new Error(reason); }); - }); -} -exports.findObsoleteResources = findObsoleteResources; -function tryGetResource(project, slug, apiHostname, credentials) { - return new Promise((resolve, reject) => { - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resource/${slug}/?details`, - auth: credentials, - method: 'GET' - }; - const request = https.request(options, (response) => { - if (response.statusCode === 404) { - resolve(false); - } - else if (response.statusCode === 200) { - resolve(true); - } - else { - reject(`Failed to query resource ${project}/${slug}. Response: ${response.statusCode} ${response.statusMessage}`); - } - }); - request.on('error', (err) => { - reject(`Failed to get ${project}/${slug} on Transifex: ${err}`); - }); - request.end(); - }); -} -function createResource(project, slug, xlfFile, apiHostname, credentials) { - return new Promise((_resolve, reject) => { - const data = JSON.stringify({ - 'content': xlfFile.contents.toString(), - 'name': slug, - 'slug': slug, - 'i18n_type': 'XLIFF' - }); - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resources`, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data) - }, - auth: credentials, - method: 'POST' - }; - const request = https.request(options, (res) => { - if (res.statusCode === 201) { - log(`Resource ${project}/${slug} successfully created on Transifex.`); - } - else { - reject(`Something went wrong in the request creating ${slug} in ${project}. ${res.statusCode}`); - } - }); - request.on('error', (err) => { - reject(`Failed to create ${project}/${slug} on Transifex: ${err}`); - }); - request.write(data); - request.end(); - }); -} -/** - * The following link provides information about how Transifex handles updates of a resource file: - * https://dev.befoolish.co/tx-docs/public/projects/updating-content#what-happens-when-you-update-files - */ -function updateResource(project, slug, xlfFile, apiHostname, credentials) { - return new Promise((resolve, reject) => { - const data = JSON.stringify({ content: xlfFile.contents.toString() }); - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resource/${slug}/content`, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data) - }, - auth: credentials, - method: 'PUT' - }; - const request = https.request(options, (res) => { - if (res.statusCode === 200) { - res.setEncoding('utf8'); - let responseBuffer = ''; - res.on('data', function (chunk) { - responseBuffer += chunk; - }); - res.on('end', () => { - const response = JSON.parse(responseBuffer); - log(`Resource ${project}/${slug} successfully updated on Transifex. Strings added: ${response.strings_added}, updated: ${response.strings_added}, deleted: ${response.strings_added}`); - resolve(); - }); - } - else { - reject(`Something went wrong in the request updating ${slug} in ${project}. ${res.statusCode}`); - } - }); - request.on('error', (err) => { - reject(`Failed to update ${project}/${slug} on Transifex: ${err}`); - }); - request.write(data); - request.end(); - }); -} -function pullSetupXlfFiles(apiHostname, username, password, language, includeDefault) { - const setupResources = [{ name: 'setup_messages', project: workbenchProject }]; - if (includeDefault) { - setupResources.push({ name: 'setup_default', project: setupProject }); - } - return pullXlfFiles(apiHostname, username, password, language, setupResources); -} -exports.pullSetupXlfFiles = pullSetupXlfFiles; -function pullXlfFiles(apiHostname, username, password, language, resources) { - const credentials = `${username}:${password}`; - const expectedTranslationsCount = resources.length; - let translationsRetrieved = 0, called = false; - return (0, event_stream_1.readable)(function (_count, callback) { - // Mark end of stream when all resources were retrieved - if (translationsRetrieved === expectedTranslationsCount) { - return this.emit('end'); - } - if (!called) { - called = true; - const stream = this; - resources.map(function (resource) { - retrieveResource(language, resource, apiHostname, credentials).then((file) => { - if (file) { - stream.emit('data', file); - } - translationsRetrieved++; - }).catch(error => { throw new Error(error); }); - }); - } - callback(); - }); -} -const limiter = new Limiter(NUMBER_OF_CONCURRENT_DOWNLOADS); -function retrieveResource(language, resource, apiHostname, credentials) { - return limiter.queue(() => new Promise((resolve, reject) => { - const slug = resource.name.replace(/\//g, '_'); - const project = resource.project; - const transifexLanguageId = language.id === 'ps' ? 'en' : language.translationId || language.id; - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resource/${slug}/translation/${transifexLanguageId}?file&mode=onlyreviewed`, - auth: credentials, - port: 443, - method: 'GET' - }; - console.log('[transifex] Fetching ' + options.path); - const request = https.request(options, (res) => { - const xlfBuffer = []; - res.on('data', (chunk) => xlfBuffer.push(chunk)); - res.on('end', () => { - if (res.statusCode === 200) { - resolve(new File({ contents: Buffer.concat(xlfBuffer), path: `${project}/${slug}.xlf` })); - } - else if (res.statusCode === 404) { - console.log(`[transifex] ${slug} in ${project} returned no data.`); - resolve(null); - } - else { - reject(`${slug} in ${project} returned no data. Response code: ${res.statusCode}.`); - } - }); - }); - request.on('error', (err) => { - reject(`Failed to query resource ${slug} with the following error: ${err}. ${options.path}`); - }); - request.end(); - })); -} -function prepareI18nFiles() { - const parsePromises = []; - return (0, event_stream_1.through)(function (xlf) { - const stream = this; - const parsePromise = XLF.parse(xlf.contents.toString()); - parsePromises.push(parsePromise); - parsePromise.then(resolvedFiles => { - resolvedFiles.forEach(file => { - const translatedFile = createI18nFile(file.originalFilePath, file.messages); - stream.queue(translatedFile); - }); - }); - }, function () { - Promise.all(parsePromises) - .then(() => { this.queue(null); }) - .catch(reason => { throw new Error(reason); }); - }); -} -exports.prepareI18nFiles = prepareI18nFiles; -function createI18nFile(originalFilePath, messages) { - let result = Object.create(null); +function createI18nFile(name, messages) { + const result = Object.create(null); result[''] = [ '--------------------------------------------------------------------------------------------', 'Copyright (c) Microsoft Corporation. All rights reserved.', @@ -1028,44 +805,52 @@ function createI18nFile(originalFilePath, messages) { content = content.replace(/\n/g, '\r\n'); } return new File({ - path: path.join(originalFilePath + '.i18n.json'), + path: path.join(name + '.i18n.json'), contents: Buffer.from(content, 'utf8') }); } exports.createI18nFile = createI18nFile; exports.i18nPackVersion = '1.0.0'; // {{SQL CARBON EDIT}} Needed in locfunc. -function prepareI18nPackFiles(externalExtensions, resultingTranslationPaths, pseudo = false) { +function getRecordFromL10nJsonFormat(l10nJsonFormat) { + const record = {}; + for (const key of Object.keys(l10nJsonFormat).sort()) { + const value = l10nJsonFormat[key]; + record[key] = typeof value === 'string' ? value : value.message; + } + return record; +} +function prepareI18nPackFiles(resultingTranslationPaths) { const parsePromises = []; const mainPack = { version: exports.i18nPackVersion, contents: {} }; const extensionsPacks = {}; const errors = []; return (0, event_stream_1.through)(function (xlf) { - const project = path.basename(path.dirname(path.dirname(xlf.relative))); - const resource = path.basename(xlf.relative, '.xlf'); + let project = path.basename(path.dirname(path.dirname(xlf.relative))); + // strip `-new` since vscode-extensions-loc uses the `-new` suffix to indicate that it's from the new loc pipeline + const resource = path.basename(path.basename(xlf.relative, '.xlf'), '-new'); + if (exports.EXTERNAL_EXTENSIONS.find(e => e === resource)) { + project = extensionsProject; + } const contents = xlf.contents.toString(); log(`Found ${project}: ${resource}`); - const parsePromise = pseudo ? XLF.parsePseudo(contents) : XLF.parse(contents); + const parsePromise = (0, l10n_dev_1.getL10nFilesFromXlf)(contents); parsePromises.push(parsePromise); parsePromise.then(resolvedFiles => { resolvedFiles.forEach(file => { - const path = file.originalFilePath; + const path = file.name; const firstSlash = path.indexOf('/'); if (project === extensionsProject) { + // resource will be the extension id let extPack = extensionsPacks[resource]; if (!extPack) { extPack = extensionsPacks[resource] = { version: exports.i18nPackVersion, contents: {} }; } - const externalId = externalExtensions[resource]; - if (!externalId) { // internal extension: remove 'extensions/extensionId/' segnent - const secondSlash = path.indexOf('/', firstSlash + 1); - extPack.contents[path.substr(secondSlash + 1)] = file.messages; - } - else { - extPack.contents[path] = file.messages; - } + // remove 'extensions/extensionId/' segment + const secondSlash = path.indexOf('/', firstSlash + 1); + extPack.contents[path.substring(secondSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); } else { - mainPack.contents[path.substr(firstSlash + 1)] = file.messages; + mainPack.contents[path.substring(firstSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); } }); }).catch(reason => { @@ -1080,16 +865,10 @@ function prepareI18nPackFiles(externalExtensions, resultingTranslationPaths, pse const translatedMainFile = createI18nFile('./main', mainPack); resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' }); this.queue(translatedMainFile); - for (const extension in extensionsPacks) { - const translatedExtFile = createI18nFile(`extensions/${extension}`, extensionsPacks[extension]); + for (const extensionId in extensionsPacks) { + const translatedExtFile = createI18nFile(`extensions/${extensionId}`, extensionsPacks[extensionId]); this.queue(translatedExtFile); - const externalExtensionId = externalExtensions[extension]; - if (externalExtensionId) { - resultingTranslationPaths.push({ id: externalExtensionId, resourceName: `extensions/${extension}.i18n.json` }); - } - else { - resultingTranslationPaths.push({ id: `vscode.${extension}`, resourceName: `extensions/${extension}.i18n.json` }); - } + resultingTranslationPaths.push({ id: extensionId, resourceName: `extensions/${extensionId}.i18n.json` }); } this.queue(null); }) @@ -1107,7 +886,7 @@ function prepareIslFiles(language, innoSetupConfig) { parsePromises.push(parsePromise); parsePromise.then(resolvedFiles => { resolvedFiles.forEach(file => { - const translatedFile = createIslFile(file.originalFilePath, file.messages, language, innoSetupConfig); + const translatedFile = createIslFile(file.name, file.messages, language, innoSetupConfig); stream.queue(translatedFile); }); }).catch(reason => { @@ -1122,14 +901,14 @@ function prepareIslFiles(language, innoSetupConfig) { }); } exports.prepareIslFiles = prepareIslFiles; -function createIslFile(originalFilePath, messages, language, innoSetup) { +function createIslFile(name, messages, language, innoSetup) { const content = []; let originalContent; - if (path.basename(originalFilePath) === 'Default') { - originalContent = new TextModel(fs.readFileSync(originalFilePath + '.isl', 'utf8')); + if (path.basename(name) === 'Default') { + originalContent = new TextModel(fs.readFileSync(name + '.isl', 'utf8')); } else { - originalContent = new TextModel(fs.readFileSync(originalFilePath + '.en.isl', 'utf8')); + originalContent = new TextModel(fs.readFileSync(name + '.en.isl', 'utf8')); } originalContent.lines.forEach(line => { if (line.length > 0) { @@ -1151,7 +930,7 @@ function createIslFile(originalFilePath, messages, language, innoSetup) { } } }); - const basename = path.basename(originalFilePath); + const basename = path.basename(name); const filePath = `${basename}.${language.id}.isl`; const encoded = iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage); return new File({ @@ -1185,3 +964,4 @@ function decodeEntities(value) { function pseudify(message) { return '\uFF3B' + message.replace(/[aouei]/g, '$&$&') + '\uFF3D'; } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaTE4bi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImkxOG4udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsNkJBQTZCO0FBQzdCLHlCQUF5QjtBQUV6QiwrQ0FBa0U7QUFDbEUsNkNBQTZDO0FBQzdDLDhCQUE4QjtBQUM5Qix5QkFBeUI7QUFDekIsaUNBQWlDO0FBQ2pDLDZCQUE2QjtBQUM3QixzQ0FBc0M7QUFDdEMsMENBQTBDO0FBQzFDLGdEQUFnRDtBQUNoRCwrQ0FBaUg7QUFFakgsU0FBUyxHQUFHLENBQUMsT0FBWSxFQUFFLEdBQUcsSUFBVztJQUN4QyxRQUFRLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsRUFBRSxPQUFPLEVBQUUsR0FBRyxJQUFJLENBQUMsQ0FBQztBQUN4RCxDQUFDO0FBWVksUUFBQSxnQkFBZ0IsR0FBZTtJQUMzQyxFQUFFLEVBQUUsRUFBRSxPQUFPLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRSxhQUFhLEVBQUUsU0FBUyxFQUFFO0lBQzVELEVBQUUsRUFBRSxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLGFBQWEsRUFBRSxTQUFTLEVBQUU7SUFDNUQsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxLQUFLLEVBQUU7SUFDL0IsRUFBRSxFQUFFLEVBQUUsSUFBSSxFQUFFLFVB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 \ No newline at end of file diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index dd949e618f..46234248d2 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -54,6 +54,10 @@ "name": "vs/workbench/contrib/codeActions", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/commands", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" @@ -90,6 +94,10 @@ "name": "vs/workbench/contrib/files", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/folding", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/html", "project": "vscode-workbench" @@ -110,6 +118,10 @@ "name": "vs/workbench/contrib/languageStatus", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/limitIndicator", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/keybindings", "project": "vscode-workbench" @@ -146,6 +158,14 @@ "name": "vs/workbench/contrib/notebook", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/interactiveEditor", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/chat", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/quickaccess", "project": "vscode-workbench" @@ -206,6 +226,10 @@ "name": "vs/workbench/contrib/terminal", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/terminalContrib", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/themes", "project": "vscode-workbench" @@ -254,10 +278,6 @@ "name": "vs/workbench/contrib/welcomeGettingStarted", "project": "vscode-workbench" }, - { - "name": "vs/workbench/contrib/welcomeOverlay", - "project": "vscode-workbench" - }, { "name": "vs/workbench/contrib/welcomePage", "project": "vscode-workbench" @@ -270,6 +290,10 @@ "name": "vs/workbench/contrib/welcomeWalkthrough", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/welcomeDialog", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/outline", "project": "vscode-workbench" @@ -303,7 +327,7 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/contrib/offline", + "name": "vs/workbench/contrib/remoteTunnel", "project": "vscode-workbench" }, { @@ -362,6 +386,10 @@ "name": "vs/workbench/services/history", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/hover", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/log", "project": "vscode-workbench" @@ -394,6 +422,10 @@ "name": "vs/workbench/services/search", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/suggest", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/textfile", "project": "vscode-workbench" @@ -477,6 +509,18 @@ { "name": "vs/workbench/services/userDataProfile", "project": "vscode-profiles" + }, + { + "name": "vs/workbench/services/localization", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/share", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/accessibility", + "project": "vscode-workbench" } ] } diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index df146393b9..f124b5e261 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -6,17 +6,16 @@ import * as path from 'path'; import * as fs from 'fs'; -import { through, readable, ThroughStream } from 'event-stream'; +import { map, merge, through, ThroughStream } from 'event-stream'; +import * as jsonMerge from 'gulp-merge-json'; import * as File from 'vinyl'; import * as Is from 'is'; import * as xml2js from 'xml2js'; -import * as https from 'https'; import * as gulp from 'gulp'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; import * as iconv from '@vscode/iconv-lite-umd'; - -const NUMBER_OF_CONCURRENT_DOWNLOADS = 4; +import { l10nJsonFormat, getL10nXlf, l10nJsonDetails, getL10nFilesFromXlf, getL10nJson } from '@vscode/l10n-dev'; function log(message: any, ...rest: any[]): void { fancyLog(ansiColors.green('[i18n]'), message, ...rest); @@ -51,18 +50,15 @@ export const extraLanguages: Language[] = [ { id: 'tr', folderName: 'trk' } ]; -// non built-in extensions also that are transifex and need to be part of the language packs -export const externalExtensionsWithTranslations = { - 'vscode-chrome-debug': 'msjsdiag.debugger-for-chrome', - 'vscode-node-debug': 'ms-vscode.node-debug', - 'vscode-node-debug2': 'ms-vscode.node-debug2' -}; - - -export interface Map { // {{SQL CARBON EDIT}} Needed in locfunc. +export interface StringMap { // {{SQL CARBON EDIT}} Needed in locfunc. [key: string]: V; } +export interface ParsedXLF { // {{SQL CARBON EDIT}} Needed in locfunc. + messages: StringMap; + originalFilePath: string; + language: string; +} interface Item { id: string; message: string; @@ -74,12 +70,6 @@ export interface Resource { project: string; } -export interface ParsedXLF { // {{SQL CARBON EDIT}} Needed in locfunc. - messages: Map; - originalFilePath: string; - language: string; -} - interface LocalizeInfo { key: string; comment: string[]; @@ -93,9 +83,9 @@ module LocalizeInfo { } interface BundledFormat { - keys: Map<(string | LocalizeInfo)[]>; - messages: Map; - bundles: Map; + keys: Record; + messages: Record; + bundles: Record; } module BundledFormat { @@ -111,27 +101,6 @@ module BundledFormat { } } -interface ValueFormat { - message: string; - comment: string[]; -} - -interface PackageJsonFormat { - [key: string]: string | ValueFormat; -} - -module PackageJsonFormat { - export function is(value: any): value is PackageJsonFormat { - if (Is.undef(value) || !Is.object(value)) { - return false; - } - return Object.keys(value).every(key => { - const element = value[key]; - return Is.string(element) || (Is.object(element) && Is.defined(element.message) && Is.defined(element.comment)); - }); - } -} - interface BundledExtensionFormat { [key: string]: { messages: string[]; @@ -181,7 +150,7 @@ class TextModel { export class XLF { private buffer: string[]; - private files: Map; + private files: Record; public numberOfMessages: number; constructor(public project: string) { @@ -277,12 +246,12 @@ export class XLF { static parsePseudo = function (xlfString: string): Promise { return new Promise((resolve) => { const parser = new xml2js.Parser(); - const files: { messages: Map; originalFilePath: string; language: string }[] = []; + const files: { messages: StringMap; originalFilePath: string; language: string }[] = []; parser.parseString(xlfString, function (_err: any, result: any) { const fileNodes: any[] = result['xliff']['file']; fileNodes.forEach(file => { const originalFilePath = file.$.original; - const messages: Map = {}; + const messages: StringMap = {}; const transUnits = file.body[0]['trans-unit']; if (transUnits) { transUnits.forEach((unit: any) => { @@ -300,11 +269,12 @@ export class XLF { }); }; - static parse = function (xlfString: string): Promise { + + static org_parse = function (xlfString: string): Promise { return new Promise((resolve, reject) => { const parser = new xml2js.Parser(); - const files: { messages: Map; originalFilePath: string; language: string }[] = []; + const files: { messages: StringMap; originalFilePath: string; language: string }[] = []; parser.parseString(xlfString, function (err: any, result: any) { if (err) { @@ -325,7 +295,7 @@ export class XLF { if (!language) { reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); } - const messages: Map = {}; + const messages: StringMap = {}; const transUnits = file.body[0]['trans-unit']; if (transUnits) { @@ -354,49 +324,61 @@ export class XLF { }); }); }; -} -export interface ITask { - (): T; -} + static parse = function (xlfString: string): Promise { + return new Promise((resolve, reject) => { + const parser = new xml2js.Parser(); -interface ILimitedTaskFactory { - factory: ITask>; - c: (value?: T | Promise) => void; - e: (error?: any) => void; -} + const files: { messages: Record; name: string; language: string }[] = []; -export class Limiter { - private runningPromises: number; - private outstandingPromises: ILimitedTaskFactory[]; + parser.parseString(xlfString, function (err: any, result: any) { + if (err) { + reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); + } - constructor(private maxDegreeOfParalellism: number) { - this.outstandingPromises = []; - this.runningPromises = 0; - } + const fileNodes: any[] = result['xliff']['file']; + if (!fileNodes) { + reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); + } - queue(factory: ITask>): Promise { - return new Promise((c, e) => { - this.outstandingPromises.push({ factory, c, e }); - this.consume(); + fileNodes.forEach((file) => { + const name = file.$.original; + if (!name) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); + } + const language = file.$['target-language']; + if (!language) { + reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); + } + const messages: Record = {}; + + const transUnits = file.body[0]['trans-unit']; + if (transUnits) { + transUnits.forEach((unit: any) => { + const key = unit.$.id; + if (!unit.target) { + return; // No translation available + } + + let val = unit.target[0]; + if (typeof val !== 'string') { + // We allow empty source values so support them for translations as well. + val = val._ ? val._ : ''; + } + if (!key) { + reject(new Error(`XLF parsing error: trans-unit ${JSON.stringify(unit, undefined, 0)} defined in file ${name} is missing the ID attribute.`)); + return; + } + messages[key] = decodeEntities(val); + }); + files.push({ messages, name, language: language.toLowerCase() }); + } + }); + + resolve(files); + }); }); - } - - private consume(): void { - while (this.outstandingPromises.length && this.runningPromises < this.maxDegreeOfParalellism) { - const iLimitedTask = this.outstandingPromises.shift()!; - this.runningPromises++; - - const promise = iLimitedTask.factory(); - promise.then(iLimitedTask.c).catch(iLimitedTask.e); - promise.then(() => this.consumed()).catch(() => this.consumed()); - } - } - - private consumed(): void { - this.runningPromises--; - this.consume(); - } + }; } function sortLanguages(languages: Language[]): Language[] { @@ -480,9 +462,9 @@ function processCoreBundleFormat(fileHeader: string, languages: Language[], json const messageSection = json.messages; const bundleSection = json.bundles; - const statistics: Map = Object.create(null); + const statistics: Record = Object.create(null); - const defaultMessages: Map> = Object.create(null); + const defaultMessages: Record> = Object.create(null); const modules = Object.keys(keysSection); modules.forEach((module) => { const keys = keysSection[module]; @@ -491,7 +473,7 @@ function processCoreBundleFormat(fileHeader: string, languages: Language[], json emitter.emit('error', `Message for module ${module} corrupted. Mismatch in number of keys and messages.`); return; } - const messageMap: Map = Object.create(null); + const messageMap: Record = Object.create(null); defaultMessages[module] = messageMap; keys.map((key, i) => { if (typeof key === 'string') { @@ -514,7 +496,7 @@ function processCoreBundleFormat(fileHeader: string, languages: Language[], json } statistics[language.id] = 0; - const localizedModules: Map = Object.create(null); + const localizedModules: Record = Object.create(null); const languageFolderName = language.translationId || language.id; const i18nFile = path.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json'); let allMessages: I18nFormat | undefined; @@ -611,7 +593,8 @@ export function processNlsFiles(opts: { fileHeader: string; languages: Language[ const editorProject: string = 'vscode-editor', workbenchProject: string = 'vscode-workbench', extensionsProject: string = 'vscode-extensions', - setupProject: string = 'vscode-setup'; + setupProject: string = 'vscode-setup', + serverProject: string = 'vscode-server'; // {{SQL CARBON EDIT}} const adsProject: string = 'ads-core'; @@ -629,6 +612,8 @@ export function getResource(sourceFile: string): Resource { return { name: 'vs/base', project: editorProject }; } else if (/^vs\/code/.test(sourceFile)) { return { name: 'vs/code', project: workbenchProject }; + } else if (/^vs\/server/.test(sourceFile)) { + return { name: 'vs/server', project: serverProject }; } else if (/^vs\/workbench\/contrib/.test(sourceFile)) { resource = sourceFile.split('/', 4).join('/'); return { name: resource, project: workbenchProject }; @@ -653,7 +638,7 @@ export function createXlfFilesForCoreBundle(): ThroughStream { const basename = path.basename(file.path); if (basename === 'nls.metadata.json') { if (file.isBuffer()) { - const xlfs: Map = Object.create(null); + const xlfs: Record = Object.create(null); const json: BundledFormat = JSON.parse((file.contents as Buffer).toString('utf8')); // {{SQL CARBON EDIT}} - Must sort the keys for easier translation. let sortedKeys = Object.keys(json.keys).sort(); @@ -698,6 +683,76 @@ export function createXlfFilesForCoreBundle(): ThroughStream { }); } +function createL10nBundleForExtension(extensionFolderName: string, prefixWithBuildFolder: boolean): NodeJS.ReadWriteStream { + const prefix = prefixWithBuildFolder ? '.build/' : ''; + return gulp + .src([ + // For source code of extensions + `${prefix}extensions/${extensionFolderName}/{src,client,server}/**/*.{ts,tsx}`, + // // For any dependencies pulled in (think vscode-css-languageservice or @vscode/emmet-helper) + `${prefix}extensions/${extensionFolderName}/**/node_modules/{@vscode,vscode-*}/**/*.{js,jsx}`, + // // For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle + `${prefix}extensions/${extensionFolderName}/**/bundle.l10n.json`, + ]) + .pipe(map(function (data, callback) { + const file = data as File; + if (!file.isBuffer()) { + // Not a buffer so we drop it + callback(); + return; + } + const extension = path.extname(file.relative); + if (extension !== '.json') { + const contents = file.contents.toString('utf8'); + getL10nJson([{ contents, extension }]) + .then((json) => { + callback(undefined, new File({ + path: `extensions/${extensionFolderName}/bundle.l10n.json`, + contents: Buffer.from(JSON.stringify(json), 'utf8') + })); + }) + .catch((err) => { + callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); + }); + // signal pause? + return false; + } + + // for bundle.l10n.jsons + let bundleJson; + try { + bundleJson = JSON.parse(file.contents.toString('utf8')); + } catch (err) { + callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); + return; + } + + // some validation of the bundle.l10n.json format + for (const key in bundleJson) { + if ( + typeof bundleJson[key] !== 'string' && + (typeof bundleJson[key].message !== 'string' || !Array.isArray(bundleJson[key].comment)) + ) { + callback(new Error(`Invalid bundle.l10n.json file. The value for key ${key} is not in the expected format.`)); + return; + } + } + + callback(undefined, file); + })) + .pipe(jsonMerge({ + fileName: `extensions/${extensionFolderName}/bundle.l10n.json`, + jsonSpace: '', + concatArrays: true + })); +} + +export const EXTERNAL_EXTENSIONS = [ + 'ms-vscode.js-debug', + 'ms-vscode.js-debug-companion', + 'ms-vscode.vscode-js-profile-table', +]; + export function createXlfFilesForExtensions(): ThroughStream { let counter: number = 0; let folderStreamEnded: boolean = false; @@ -708,60 +763,62 @@ export function createXlfFilesForExtensions(): ThroughStream { if (!stat.isDirectory()) { return; } - const extensionName = path.basename(extensionFolder.path); - if (extensionName === 'node_modules') { + const extensionFolderName = path.basename(extensionFolder.path); + if (extensionFolderName === 'node_modules') { return; } + // Get extension id and use that as the id + const manifest = fs.readFileSync(path.join(extensionFolder.path, 'package.json'), 'utf-8'); + const manifestJson = JSON.parse(manifest); + const extensionId = manifestJson.publisher + '.' + manifestJson.name; + counter++; - let _xlf: XLF; - function getXlf() { - if (!_xlf) { - _xlf = new XLF(extensionsProject); + let _l10nMap: Map; + function getL10nMap() { + if (!_l10nMap) { + _l10nMap = new Map(); } - return _xlf; + return _l10nMap; } - gulp.src([`.build/extensions/${extensionName}/package.nls.json`, `.build/extensions/${extensionName}/**/nls.metadata.json`], { allowEmpty: true }).pipe(through(function (file: File) { + merge( + gulp.src([`.build/extensions/${extensionFolderName}/package.nls.json`, `.build/extensions/${extensionFolderName}/**/nls.metadata.json`], { allowEmpty: true }), + createL10nBundleForExtension(extensionFolderName, EXTERNAL_EXTENSIONS.includes(extensionId)) + ).pipe(through(function (file: File) { if (file.isBuffer()) { const buffer: Buffer = file.contents as Buffer; const basename = path.basename(file.path); if (basename === 'package.nls.json') { - const json: PackageJsonFormat = JSON.parse(buffer.toString('utf8')); - const keys: Array = []; - const messages: string[] = []; - Object.keys(json).forEach((key) => { - const value = json[key]; - if (Is.string(value)) { - keys.push(key); - messages.push(value); - } else if (value) { - keys.push({ - key, - comment: value.comment - }); - messages.push(value.message); - } else { - keys.push(key); - messages.push(`Unknown message for key: ${key}`); - } - }); - getXlf().addFile(`extensions/${extensionName}/package`, keys, messages); + const json: l10nJsonFormat = JSON.parse(buffer.toString('utf8')); + getL10nMap().set(`extensions/${extensionId}/package`, json); } else if (basename === 'nls.metadata.json') { const json: BundledExtensionFormat = JSON.parse(buffer.toString('utf8')); - const relPath = path.relative(`.build/extensions/${extensionName}`, path.dirname(file.path)); + const relPath = path.relative(`.build/extensions/${extensionFolderName}`, path.dirname(file.path)); for (const file in json) { const fileContent = json[file]; - getXlf().addFile(`extensions/${extensionName}/${relPath}/${file}`, fileContent.keys, fileContent.messages); + const info: l10nJsonFormat = Object.create(null); + for (let i = 0; i < fileContent.messages.length; i++) { + const message = fileContent.messages[i]; + const { key, comment } = LocalizeInfo.is(fileContent.keys[i]) + ? fileContent.keys[i] as LocalizeInfo + : { key: fileContent.keys[i] as string, comment: undefined }; + + info[key] = comment ? { message, comment } : message; + } + getL10nMap().set(`extensions/${extensionId}/${relPath}/${file}`, info); } + } else if (basename === 'bundle.l10n.json') { + const json: l10nJsonFormat = JSON.parse(buffer.toString('utf8')); + getL10nMap().set(`extensions/${extensionId}/bundle`, json); } else { this.emit('error', new Error(`${file.path} is not a valid extension nls file`)); return; } } }, function () { - if (_xlf) { + if (_l10nMap?.size > 0) { const xlfFile = new File({ - path: path.join(extensionsProject, extensionName + '.xlf'), - contents: Buffer.from(_xlf.toString(), 'utf8') + path: path.join(extensionsProject, extensionId + '.xlf'), + contents: Buffer.from(getL10nXlf(_l10nMap), 'utf8') }); folderStream.queue(xlfFile); } @@ -837,322 +894,8 @@ export function createXlfFilesForIsl(): ThroughStream { }); } -export function pushXlfFiles(apiHostname: string, username: string, password: string): ThroughStream { - const tryGetPromises: Array> = []; - const updateCreatePromises: Array> = []; - - return through(function (this: ThroughStream, file: File) { - const project = path.dirname(file.relative); - const fileName = path.basename(file.path); - const slug = fileName.substr(0, fileName.length - '.xlf'.length); - const credentials = `${username}:${password}`; - - // Check if resource already exists, if not, then create it. - let promise = tryGetResource(project, slug, apiHostname, credentials); - tryGetPromises.push(promise); - promise.then(exists => { - if (exists) { - promise = updateResource(project, slug, file, apiHostname, credentials); - } else { - promise = createResource(project, slug, file, apiHostname, credentials); - } - updateCreatePromises.push(promise); - }); - - }, function () { - // End the pipe only after all the communication with Transifex API happened - Promise.all(tryGetPromises).then(() => { - Promise.all(updateCreatePromises).then(() => { - this.queue(null); - }).catch((reason) => { throw new Error(reason); }); - }).catch((reason) => { throw new Error(reason); }); - }); -} - -function getAllResources(project: string, apiHostname: string, username: string, password: string): Promise { - return new Promise((resolve, reject) => { - const credentials = `${username}:${password}`; - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resources`, - auth: credentials, - method: 'GET' - }; - - const request = https.request(options, (res) => { - const buffer: Buffer[] = []; - res.on('data', (chunk: Buffer) => buffer.push(chunk)); - res.on('end', () => { - if (res.statusCode === 200) { - const json = JSON.parse(Buffer.concat(buffer).toString()); - if (Array.isArray(json)) { - resolve(json.map(o => o.slug)); - return; - } - reject(`Unexpected data format. Response code: ${res.statusCode}.`); - } else { - reject(`No resources in ${project} returned no data. Response code: ${res.statusCode}.`); - } - }); - }); - request.on('error', (err) => { - reject(`Failed to query resources in ${project} with the following error: ${err}. ${options.path}`); - }); - request.end(); - }); -} - -export function findObsoleteResources(apiHostname: string, username: string, password: string): ThroughStream { - const resourcesByProject: Map = Object.create(null); - resourcesByProject[extensionsProject] = ([] as any[]).concat(externalExtensionsWithTranslations); // clone - - return through(function (this: ThroughStream, file: File) { - const project = path.dirname(file.relative); - const fileName = path.basename(file.path); - const slug = fileName.substr(0, fileName.length - '.xlf'.length); - - let slugs = resourcesByProject[project]; - if (!slugs) { - resourcesByProject[project] = slugs = []; - } - slugs.push(slug); - this.push(file); - }, function () { - - const json = JSON.parse(fs.readFileSync('./build/lib/i18n.resources.json', 'utf8')); - const i18Resources = [...json.editor, ...json.workbench].map((r: Resource) => r.project + '/' + r.name.replace(/\//g, '_')); - const extractedResources: string[] = []; - for (const project of [workbenchProject, editorProject]) { - for (const resource of resourcesByProject[project]) { - if (resource !== 'setup_messages') { - extractedResources.push(project + '/' + resource); - } - } - } - if (i18Resources.length !== extractedResources.length) { - console.log(`[i18n] Obsolete resources in file 'build/lib/i18n.resources.json': JSON.stringify(${i18Resources.filter(p => extractedResources.indexOf(p) === -1)})`); - console.log(`[i18n] Missing resources in file 'build/lib/i18n.resources.json': JSON.stringify(${extractedResources.filter(p => i18Resources.indexOf(p) === -1)})`); - } - - const promises: Array> = []; - for (const project in resourcesByProject) { - promises.push( - getAllResources(project, apiHostname, username, password).then(resources => { - const expectedResources = resourcesByProject[project]; - const unusedResources = resources.filter(resource => resource && expectedResources.indexOf(resource) === -1); - if (unusedResources.length) { - console.log(`[transifex] Obsolete resources in project '${project}': ${unusedResources.join(', ')}`); - } - }) - ); - } - return Promise.all(promises).then(_ => { - this.push(null); - }).catch((reason) => { throw new Error(reason); }); - }); -} - -function tryGetResource(project: string, slug: string, apiHostname: string, credentials: string): Promise { - return new Promise((resolve, reject) => { - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resource/${slug}/?details`, - auth: credentials, - method: 'GET' - }; - - const request = https.request(options, (response) => { - if (response.statusCode === 404) { - resolve(false); - } else if (response.statusCode === 200) { - resolve(true); - } else { - reject(`Failed to query resource ${project}/${slug}. Response: ${response.statusCode} ${response.statusMessage}`); - } - }); - request.on('error', (err) => { - reject(`Failed to get ${project}/${slug} on Transifex: ${err}`); - }); - - request.end(); - }); -} - -function createResource(project: string, slug: string, xlfFile: File, apiHostname: string, credentials: any): Promise { - return new Promise((_resolve, reject) => { - const data = JSON.stringify({ - 'content': xlfFile.contents.toString(), - 'name': slug, - 'slug': slug, - 'i18n_type': 'XLIFF' - }); - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resources`, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data) - }, - auth: credentials, - method: 'POST' - }; - - const request = https.request(options, (res) => { - if (res.statusCode === 201) { - log(`Resource ${project}/${slug} successfully created on Transifex.`); - } else { - reject(`Something went wrong in the request creating ${slug} in ${project}. ${res.statusCode}`); - } - }); - request.on('error', (err) => { - reject(`Failed to create ${project}/${slug} on Transifex: ${err}`); - }); - - request.write(data); - request.end(); - }); -} - -/** - * The following link provides information about how Transifex handles updates of a resource file: - * https://dev.befoolish.co/tx-docs/public/projects/updating-content#what-happens-when-you-update-files - */ -function updateResource(project: string, slug: string, xlfFile: File, apiHostname: string, credentials: string): Promise { - return new Promise((resolve, reject) => { - const data = JSON.stringify({ content: xlfFile.contents.toString() }); - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resource/${slug}/content`, - headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(data) - }, - auth: credentials, - method: 'PUT' - }; - - const request = https.request(options, (res) => { - if (res.statusCode === 200) { - res.setEncoding('utf8'); - - let responseBuffer: string = ''; - res.on('data', function (chunk) { - responseBuffer += chunk; - }); - res.on('end', () => { - const response = JSON.parse(responseBuffer); - log(`Resource ${project}/${slug} successfully updated on Transifex. Strings added: ${response.strings_added}, updated: ${response.strings_added}, deleted: ${response.strings_added}`); - resolve(); - }); - } else { - reject(`Something went wrong in the request updating ${slug} in ${project}. ${res.statusCode}`); - } - }); - request.on('error', (err) => { - reject(`Failed to update ${project}/${slug} on Transifex: ${err}`); - }); - - request.write(data); - request.end(); - }); -} - -export function pullSetupXlfFiles(apiHostname: string, username: string, password: string, language: Language, includeDefault: boolean): NodeJS.ReadableStream { - const setupResources = [{ name: 'setup_messages', project: workbenchProject }]; - if (includeDefault) { - setupResources.push({ name: 'setup_default', project: setupProject }); - } - return pullXlfFiles(apiHostname, username, password, language, setupResources); -} - -function pullXlfFiles(apiHostname: string, username: string, password: string, language: Language, resources: Resource[]): NodeJS.ReadableStream { - const credentials = `${username}:${password}`; - const expectedTranslationsCount = resources.length; - let translationsRetrieved = 0, called = false; - - return readable(function (_count: any, callback: any) { - // Mark end of stream when all resources were retrieved - if (translationsRetrieved === expectedTranslationsCount) { - return this.emit('end'); - } - - if (!called) { - called = true; - const stream = this; - resources.map(function (resource) { - retrieveResource(language, resource, apiHostname, credentials).then((file: File | null) => { - if (file) { - stream.emit('data', file); - } - translationsRetrieved++; - }).catch(error => { throw new Error(error); }); - }); - } - - callback(); - }); -} -const limiter = new Limiter(NUMBER_OF_CONCURRENT_DOWNLOADS); - -function retrieveResource(language: Language, resource: Resource, apiHostname: string, credentials: string): Promise { - return limiter.queue(() => new Promise((resolve, reject) => { - const slug = resource.name.replace(/\//g, '_'); - const project = resource.project; - const transifexLanguageId = language.id === 'ps' ? 'en' : language.translationId || language.id; - const options = { - hostname: apiHostname, - path: `/api/2/project/${project}/resource/${slug}/translation/${transifexLanguageId}?file&mode=onlyreviewed`, - auth: credentials, - port: 443, - method: 'GET' - }; - console.log('[transifex] Fetching ' + options.path); - - const request = https.request(options, (res) => { - const xlfBuffer: Buffer[] = []; - res.on('data', (chunk: Buffer) => xlfBuffer.push(chunk)); - res.on('end', () => { - if (res.statusCode === 200) { - resolve(new File({ contents: Buffer.concat(xlfBuffer), path: `${project}/${slug}.xlf` })); - } else if (res.statusCode === 404) { - console.log(`[transifex] ${slug} in ${project} returned no data.`); - resolve(null); - } else { - reject(`${slug} in ${project} returned no data. Response code: ${res.statusCode}.`); - } - }); - }); - request.on('error', (err) => { - reject(`Failed to query resource ${slug} with the following error: ${err}. ${options.path}`); - }); - request.end(); - })); -} - -export function prepareI18nFiles(): ThroughStream { - const parsePromises: Promise[] = []; - - return through(function (this: ThroughStream, xlf: File) { - const stream = this; - const parsePromise = XLF.parse(xlf.contents.toString()); - parsePromises.push(parsePromise); - parsePromise.then( - resolvedFiles => { - resolvedFiles.forEach(file => { - const translatedFile = createI18nFile(file.originalFilePath, file.messages); - stream.queue(translatedFile); - }); - } - ); - }, function () { - Promise.all(parsePromises) - .then(() => { this.queue(null); }) - .catch(reason => { throw new Error(reason); }); - }); -} - -export function createI18nFile(originalFilePath: string, messages: any): File { // {{SQL CARBON EDIT}} Needed for locfunc. - let result = Object.create(null); +export function createI18nFile(name: string, messages: any): File { // {{SQL CARBON EDIT}} Needed for locfunc. + const result = Object.create(null); result[''] = [ '--------------------------------------------------------------------------------------------', 'Copyright (c) Microsoft Corporation. All rights reserved.', @@ -1169,7 +912,7 @@ export function createI18nFile(originalFilePath: string, messages: any): File { content = content.replace(/\n/g, '\r\n'); } return new File({ - path: path.join(originalFilePath + '.i18n.json'), + path: path.join(name + '.i18n.json'), contents: Buffer.from(content, 'utf8') }); } @@ -1177,7 +920,7 @@ export function createI18nFile(originalFilePath: string, messages: any): File { export interface I18nPack { // {{SQL CARBON EDIT}} Needed in locfunc. version: string; contents: { - [path: string]: Map; + [path: string]: Record; }; } @@ -1188,38 +931,48 @@ export interface TranslationPath { resourceName: string; } -export function prepareI18nPackFiles(externalExtensions: Map, resultingTranslationPaths: TranslationPath[], pseudo = false): NodeJS.ReadWriteStream { - const parsePromises: Promise[] = []; +function getRecordFromL10nJsonFormat(l10nJsonFormat: l10nJsonFormat): Record { + const record: Record = {}; + for (const key of Object.keys(l10nJsonFormat).sort()) { + const value = l10nJsonFormat[key]; + record[key] = typeof value === 'string' ? value : value.message; + } + return record; +} + +export function prepareI18nPackFiles(resultingTranslationPaths: TranslationPath[]): NodeJS.ReadWriteStream { + const parsePromises: Promise[] = []; const mainPack: I18nPack = { version: i18nPackVersion, contents: {} }; - const extensionsPacks: Map = {}; + const extensionsPacks: Record = {}; const errors: any[] = []; return through(function (this: ThroughStream, xlf: File) { - const project = path.basename(path.dirname(path.dirname(xlf.relative))); - const resource = path.basename(xlf.relative, '.xlf'); + let project = path.basename(path.dirname(path.dirname(xlf.relative))); + // strip `-new` since vscode-extensions-loc uses the `-new` suffix to indicate that it's from the new loc pipeline + const resource = path.basename(path.basename(xlf.relative, '.xlf'), '-new'); + if (EXTERNAL_EXTENSIONS.find(e => e === resource)) { + project = extensionsProject; + } const contents = xlf.contents.toString(); log(`Found ${project}: ${resource}`); - const parsePromise = pseudo ? XLF.parsePseudo(contents) : XLF.parse(contents); + const parsePromise = getL10nFilesFromXlf(contents); parsePromises.push(parsePromise); parsePromise.then( resolvedFiles => { resolvedFiles.forEach(file => { - const path = file.originalFilePath; + const path = file.name; const firstSlash = path.indexOf('/'); if (project === extensionsProject) { + // resource will be the extension id let extPack = extensionsPacks[resource]; if (!extPack) { extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} }; } - const externalId = externalExtensions[resource]; - if (!externalId) { // internal extension: remove 'extensions/extensionId/' segnent - const secondSlash = path.indexOf('/', firstSlash + 1); - extPack.contents[path.substr(secondSlash + 1)] = file.messages; - } else { - extPack.contents[path] = file.messages; - } + // remove 'extensions/extensionId/' segment + const secondSlash = path.indexOf('/', firstSlash + 1); + extPack.contents[path.substring(secondSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); } else { - mainPack.contents[path.substr(firstSlash + 1)] = file.messages; + mainPack.contents[path.substring(firstSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); } }); } @@ -1236,17 +989,11 @@ export function prepareI18nPackFiles(externalExtensions: Map, resultingT resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' }); this.queue(translatedMainFile); - for (const extension in extensionsPacks) { - const translatedExtFile = createI18nFile(`extensions/${extension}`, extensionsPacks[extension]); + for (const extensionId in extensionsPacks) { + const translatedExtFile = createI18nFile(`extensions/${extensionId}`, extensionsPacks[extensionId]); this.queue(translatedExtFile); - const externalExtensionId = externalExtensions[extension]; - if (externalExtensionId) { - resultingTranslationPaths.push({ id: externalExtensionId, resourceName: `extensions/${extension}.i18n.json` }); - } else { - resultingTranslationPaths.push({ id: `vscode.${extension}`, resourceName: `extensions/${extension}.i18n.json` }); - } - + resultingTranslationPaths.push({ id: extensionId, resourceName: `extensions/${extensionId}.i18n.json` }); } this.queue(null); }) @@ -1257,7 +1004,7 @@ export function prepareI18nPackFiles(externalExtensions: Map, resultingT } export function prepareIslFiles(language: Language, innoSetupConfig: InnoSetup): ThroughStream { - const parsePromises: Promise[] = []; + const parsePromises: Promise[] = []; return through(function (this: ThroughStream, xlf: File) { const stream = this; @@ -1266,7 +1013,7 @@ export function prepareIslFiles(language: Language, innoSetupConfig: InnoSetup): parsePromise.then( resolvedFiles => { resolvedFiles.forEach(file => { - const translatedFile = createIslFile(file.originalFilePath, file.messages, language, innoSetupConfig); + const translatedFile = createIslFile(file.name, file.messages, language, innoSetupConfig); stream.queue(translatedFile); }); } @@ -1282,13 +1029,13 @@ export function prepareIslFiles(language: Language, innoSetupConfig: InnoSetup): }); } -function createIslFile(originalFilePath: string, messages: Map, language: Language, innoSetup: InnoSetup): File { +function createIslFile(name: string, messages: l10nJsonFormat, language: Language, innoSetup: InnoSetup): File { const content: string[] = []; let originalContent: TextModel; - if (path.basename(originalFilePath) === 'Default') { - originalContent = new TextModel(fs.readFileSync(originalFilePath + '.isl', 'utf8')); + if (path.basename(name) === 'Default') { + originalContent = new TextModel(fs.readFileSync(name + '.isl', 'utf8')); } else { - originalContent = new TextModel(fs.readFileSync(originalFilePath + '.en.isl', 'utf8')); + originalContent = new TextModel(fs.readFileSync(name + '.en.isl', 'utf8')); } originalContent.lines.forEach(line => { if (line.length > 0) { @@ -1311,7 +1058,7 @@ function createIslFile(originalFilePath: string, messages: Map, language } }); - const basename = path.basename(originalFilePath); + const basename = path.basename(name); const filePath = `${basename}.${language.id}.isl`; const encoded = iconv.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage); diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index 02b255f439..171f795334 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -51,11 +51,13 @@ const CORE_TYPES = [ 'BigInt64Array', 'btoa', 'atob', + 'AbortController', 'AbortSignal', 'MessageChannel', 'MessagePort', 'URL', - 'URLSearchParams' + 'URLSearchParams', + 'ReadonlyArray', ]; // Types that are defined in a common layer but are known to be only // available in native environments should not be allowed in browser @@ -64,7 +66,9 @@ const NATIVE_TYPES = [ 'INativeEnvironmentService', 'AbstractNativeEnvironmentService', 'INativeWindowConfiguration', - 'ICommonNativeHostService' + 'ICommonNativeHostService', + 'INativeHostService', + 'IMainProcessService' ]; const RULES = [ // Tests: skip @@ -228,7 +232,7 @@ function checkFile(program, sourceFile, rule) { } if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); hasErrors = true; return; } @@ -252,7 +256,7 @@ function checkFile(program, sourceFile, rule) { for (const disallowedDefinition of rule.disallowedDefinitions) { if (definitionFileName.indexOf(disallowedDefinition) >= 0) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); hasErrors = true; return; } @@ -289,3 +293,4 @@ for (const sourceFile of program.getSourceFiles()) { if (hasErrors) { process.exit(1); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGF5ZXJzQ2hlY2tlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImxheWVyc0NoZWNrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxpQ0FBaUM7QUFDakMsMkJBQThDO0FBQzlDLCtCQUE4QztBQUM5Qyx5Q0FBa0M7QUFFbEMsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBQ0YsK0ZBQStGO0FBQy9GLG1EQUFtRDtBQUNuRCw0RUFBNEU7QUFDNUUsaUVBQWlFO0FBQ2pFLEVBQUU7QUFDRixnR0FBZ0c7QUFDaEcsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBRUYsbUZBQW1GO0FBQ25GLHdGQUF3RjtBQUN4RixNQUFNLFVBQVUsR0FBRztJQUNsQixTQUFTO0lBQ1QsWUFBWTtJQUNaLGNBQWM7SUFDZCxhQUFhO0lBQ2IsZUFBZTtJQUNmLFNBQVM7SUFDVCxTQUFTO0lBQ1QsT0FBTztJQUNQLGtCQUFrQjtJQUNsQixRQUFRO0lBQ1IsYUFBYTtJQUNiLGFBQWE7SUFDYixNQUFNO0lBQ04sZ0JBQWdCO0lBQ2hCLE9BQU87SUFDUCxZQUFZO0lBQ1osYUFBYTtJQUNiLGFBQWE7SUFDYixXQUFXO0lBQ1gsWUFBWTtJQUNaLFlBQVk7SUFDWixjQUFjO0lBQ2QsY0FBYztJQUNkLG1CQUFtQjtJQUNuQixnQkFBZ0I7SUFDaEIsZUFBZTtJQUNmLE1BQU07SUFDTixNQUFNO0lBQ04saUJBQWlCO0lBQ2pCLGFBQWE7SUFDYixnQkFBZ0I7SUFDaEIsYUFBYTtJQUNiLEtBQUs7SUFDTCxpQkFBaUI7SUFDakIsZUFBZTtDQUNmLENBQUM7QUFFRixvRUFBb0U7QUFDcEUsb0VBQW9FO0FBQ3BFLE1BQU0sWUFBWSxHQUFHO0lBQ3BCLGtCQUFrQjtJQUNsQiwyQkFBMkI7SUFDM0Isa0NBQWtDO0lBQ2xDLDRCQUE0QjtJQUM1QiwwQkFBMEI7SUFDMUIsb0JBQW9CO0lBQ3BCLHFCQUFxQjtDQUNyQixDQUFDO0FBRUYs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 \ No newline at end of file diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index 62e961107a..c06b23e766 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -52,11 +52,13 @@ const CORE_TYPES = [ 'BigInt64Array', 'btoa', 'atob', + 'AbortController', 'AbortSignal', 'MessageChannel', 'MessagePort', 'URL', - 'URLSearchParams' + 'URLSearchParams', + 'ReadonlyArray', ]; // Types that are defined in a common layer but are known to be only @@ -66,7 +68,9 @@ const NATIVE_TYPES = [ 'INativeEnvironmentService', 'AbstractNativeEnvironmentService', 'INativeWindowConfiguration', - 'ICommonNativeHostService' + 'ICommonNativeHostService', + 'INativeHostService', + 'IMainProcessService' ]; const RULES: IRule[] = [ @@ -268,7 +272,7 @@ function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); hasErrors = true; return; @@ -295,7 +299,7 @@ function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) if (definitionFileName.indexOf(disallowedDefinition) >= 0) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}) Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); hasErrors = true; return; diff --git a/build/lib/locFunc.js b/build/lib/locFunc.js index 63a7e81506..e15651790c 100644 --- a/build/lib/locFunc.js +++ b/build/lib/locFunc.js @@ -105,7 +105,7 @@ function modifyI18nPackFiles(existingTranslationFolder, resultingTranslationPath let rawResource = path.basename(xlf.relative, '.xlf'); let resource = rawResource.substring(0, rawResource.lastIndexOf('.')); let contents = xlf.contents.toString(); - let parsePromise = pseudo ? i18n.XLF.parsePseudo(contents) : i18n.XLF.parse(contents); + let parsePromise = pseudo ? i18n.XLF.parsePseudo(contents) : i18n.XLF.org_parse(contents); parsePromises.push(parsePromise); parsePromise.then(resolvedFiles => { resolvedFiles.forEach(file => { @@ -397,3 +397,4 @@ function renameVscodeLangpacks() { return Promise.resolve(); } exports.renameVscodeLangpacks = renameVscodeLangpacks; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9jRnVuYy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImxvY0Z1bmMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsbUNBQW1DO0FBQ25DLDZCQUE2QjtBQUM3Qiw2QkFBNkI7QUFDN0Isc0NBQXVDO0FBQ3ZDLG9DQUFxQztBQUNyQywrQkFBZ0M7QUFDaEMseUJBQXlCO0FBQ3pCLDhCQUE4QjtBQUM5QixpQ0FBaUM7QUFDakMsNkJBQTZCO0FBQzdCLGdDQUFnQztBQUVoQzs7R0FFRztBQUVILG9IQUFvSDtBQUNwSCxNQUFNLHVCQUF1QixHQUFHO0lBQy9CLEtBQUs7Q0FDTCxDQUFDO0FBRUYsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUM7QUFFbkQsK0VBQStFO0FBQy9FLFNBQWdCLHNCQUFzQjtJQUNyQyxNQUFNLG9CQUFvQixHQUFjLElBQUksQ0FBQyxJQUFJLENBQUMscUJBQXFCLENBQUU7U0FDdkUsR0FBRyxDQUFDLFlBQVksQ0FBQyxFQUFFO1FBQ25CLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsWUFBWSxDQUFDLENBQUMsQ0FBQztRQUNqRSxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLFlBQVksQ0FBQyxDQUFDO1FBQ2pELE9BQU8sRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLElBQUksRUFBRSxZQUFZLEVBQUUsQ0FBQztJQUNuRCxDQUFDLENBQUMsQ0FBQztJQUVKLE1BQU0sY0FBYyxHQUFHLG9CQUFvQixDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsRUFBRTtRQUMxRCxPQUFPLEdBQUcsQ0FBQyxlQUFlLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQzthQUN2QyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFF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 \ No newline at end of file diff --git a/build/lib/locFunc.ts b/build/lib/locFunc.ts index d58ee29b97..10a8f8327c 100644 --- a/build/lib/locFunc.ts +++ b/build/lib/locFunc.ts @@ -111,13 +111,13 @@ function updateMainI18nFile(existingTranslationFilePath: string, originalFilePat export function modifyI18nPackFiles(existingTranslationFolder: string, resultingTranslationPaths: i18n.TranslationPath[], pseudo = false): NodeJS.ReadWriteStream { let parsePromises: Promise[] = []; let mainPack: i18n.I18nPack = { version: i18n.i18nPackVersion, contents: {} }; - let extensionsPacks: i18n.Map = {}; + let extensionsPacks: i18n.StringMap = {}; let errors: any[] = []; return es.through(function (this: es.ThroughStream, xlf: File) { let rawResource = path.basename(xlf.relative, '.xlf'); let resource = rawResource.substring(0, rawResource.lastIndexOf('.')); let contents = xlf.contents.toString(); - let parsePromise = pseudo ? i18n.XLF.parsePseudo(contents) : i18n.XLF.parse(contents); + let parsePromise = pseudo ? i18n.XLF.parsePseudo(contents) : i18n.XLF.org_parse(contents); parsePromises.push(parsePromise); parsePromise.then( resolvedFiles => { diff --git a/build/lib/mangleTypeScript.js b/build/lib/mangleTypeScript.js new file mode 100644 index 0000000000..cebbda3100 --- /dev/null +++ b/build/lib/mangleTypeScript.js @@ -0,0 +1,529 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Mangler = void 0; +const ts = require("typescript"); +const path = require("path"); +// import * as fs from 'fs'; // {{SQL CARBON EDIT}} - disable unused +// import { argv } from 'process'; +const source_map_1 = require("source-map"); +const url_1 = require("url"); +class ShortIdent { + static _keywords = new Set(['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', + 'default', 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', + 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return', 'static', 'super', 'switch', 'this', 'throw', + 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield']); + static _alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + _value = 0; + _isNameTaken; + constructor(isNameTaken) { + this._isNameTaken = name => ShortIdent._keywords.has(name) || isNameTaken(name); + } + next() { + const candidate = ShortIdent.convert(this._value); + this._value++; + if (this._isNameTaken(candidate)) { + // try again + return this.next(); + } + return candidate; + } + static convert(n) { + const base = this._alphabet.length; + let result = ''; + do { + const rest = n % base; + result += this._alphabet[rest]; + n = (n / base) | 0; + } while (n > 0); + return result; + } +} +var FieldType; +(function (FieldType) { + FieldType[FieldType["Public"] = 0] = "Public"; + FieldType[FieldType["Protected"] = 1] = "Protected"; + FieldType[FieldType["Private"] = 2] = "Private"; +})(FieldType || (FieldType = {})); +class ClassData { + fileName; + node; + fields = new Map(); + replacements; + parent; + children; + constructor(fileName, node) { + // analyse all fields (properties and methods). Find usages of all protected and + // private ones and keep track of all public ones (to prevent naming collisions) + this.fileName = fileName; + this.node = node; + const candidates = []; + for (const member of node.members) { + if (ts.isMethodDeclaration(member)) { + // method `foo() {}` + candidates.push(member); + } + else if (ts.isPropertyDeclaration(member)) { + // property `foo = 234` + candidates.push(member); + } + else if (ts.isGetAccessor(member)) { + // getter: `get foo() { ... }` + candidates.push(member); + } + else if (ts.isSetAccessor(member)) { + // setter: `set foo() { ... }` + candidates.push(member); + } + else if (ts.isConstructorDeclaration(member)) { + // constructor-prop:`constructor(private foo) {}` + for (const param of member.parameters) { + if (hasModifier(param, ts.SyntaxKind.PrivateKeyword) + || hasModifier(param, ts.SyntaxKind.ProtectedKeyword) + || hasModifier(param, ts.SyntaxKind.PublicKeyword) + || hasModifier(param, ts.SyntaxKind.ReadonlyKeyword)) { + candidates.push(param); + } + } + } + } + for (const member of candidates) { + const ident = ClassData._getMemberName(member); + if (!ident) { + continue; + } + const type = ClassData._getFieldType(member); + this.fields.set(ident, { type, pos: member.name.getStart() }); + } + } + static _getMemberName(node) { + if (!node.name) { + return undefined; + } + const { name } = node; + let ident = name.getText(); + if (name.kind === ts.SyntaxKind.ComputedPropertyName) { + if (name.expression.kind !== ts.SyntaxKind.StringLiteral) { + // unsupported: [Symbol.foo] or [abc + 'field'] + return; + } + // ['foo'] + ident = name.expression.getText().slice(1, -1); + } + return ident; + } + static _getFieldType(node) { + if (hasModifier(node, ts.SyntaxKind.PrivateKeyword)) { + return 2 /* FieldType.Private */; + } + else if (hasModifier(node, ts.SyntaxKind.ProtectedKeyword)) { + return 1 /* FieldType.Protected */; + } + else { + return 0 /* FieldType.Public */; + } + } + static _shouldMangle(type) { + return type === 2 /* FieldType.Private */ + || type === 1 /* FieldType.Protected */; + } + static makeImplicitPublicActuallyPublic(data, reportViolation) { + // TS-HACK + // A subtype can make an inherited protected field public. To prevent accidential + // mangling of public fields we mark the original (protected) fields as public... + for (const [name, info] of data.fields) { + if (info.type !== 0 /* FieldType.Public */) { + continue; + } + let parent = data.parent; + while (parent) { + if (parent.fields.get(name)?.type === 1 /* FieldType.Protected */) { + const parentPos = parent.node.getSourceFile().getLineAndCharacterOfPosition(parent.fields.get(name).pos); + const infoPos = data.node.getSourceFile().getLineAndCharacterOfPosition(info.pos); + reportViolation(name, `'${name}' from ${parent.fileName}:${parentPos.line + 1}`, `${data.fileName}:${infoPos.line + 1}`); + parent.fields.get(name).type = 0 /* FieldType.Public */; + } + parent = parent.parent; + } + } + } + static fillInReplacement(data) { + if (data.replacements) { + // already done + return; + } + // fill in parents first + if (data.parent) { + ClassData.fillInReplacement(data.parent); + } + data.replacements = new Map(); + const identPool = new ShortIdent(name => { + // locally taken + if (data._isNameTaken(name)) { + return true; + } + // parents + let parent = data.parent; + while (parent) { + if (parent._isNameTaken(name)) { + return true; + } + parent = parent.parent; + } + // children + if (data.children) { + const stack = [...data.children]; + while (stack.length) { + const node = stack.pop(); + if (node._isNameTaken(name)) { + return true; + } + if (node.children) { + stack.push(...node.children); + } + } + } + return false; + }); + for (const [name, info] of data.fields) { + if (ClassData._shouldMangle(info.type)) { + const shortName = identPool.next(); + data.replacements.set(name, shortName); + } + } + } + // a name is taken when a field that doesn't get mangled exists or + // when the name is already in use for replacement + _isNameTaken(name) { + if (this.fields.has(name) && !ClassData._shouldMangle(this.fields.get(name).type)) { + // public field + return true; + } + if (this.replacements) { + for (const shortName of this.replacements.values()) { + if (shortName === name) { + // replaced already (happens wih super types) + return true; + } + } + } + if (this.node.getSourceFile().identifiers instanceof Map) { + // taken by any other usage + if (this.node.getSourceFile().identifiers.has(name)) { + return true; + } + } + return false; + } + lookupShortName(name) { + let value = this.replacements.get(name); + let parent = this.parent; + while (parent) { + if (parent.replacements.has(name) && parent.fields.get(name)?.type === 1 /* FieldType.Protected */) { + value = parent.replacements.get(name) ?? value; + } + parent = parent.parent; + } + return value; + } + // --- parent chaining + addChild(child) { + this.children ??= []; + this.children.push(child); + child.parent = this; + } +} +class StaticLanguageServiceHost { + projectPath; + _cmdLine; + _scriptSnapshots = new Map(); + constructor(projectPath) { + this.projectPath = projectPath; + const existingOptions = {}; + const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); + if (parsed.error) { + throw parsed.error; + } + this._cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, path.dirname(projectPath), existingOptions); + if (this._cmdLine.errors.length > 0) { + throw parsed.error; + } + } + getCompilationSettings() { + return this._cmdLine.options; + } + getScriptFileNames() { + return this._cmdLine.fileNames; + } + getScriptVersion(_fileName) { + return '1'; + } + getProjectVersion() { + return '1'; + } + getScriptSnapshot(fileName) { + let result = this._scriptSnapshots.get(fileName); + if (result === undefined) { + const content = ts.sys.readFile(fileName); + if (content === undefined) { + return undefined; + } + result = ts.ScriptSnapshot.fromString(content); + this._scriptSnapshots.set(fileName, result); + } + return result; + } + getCurrentDirectory() { + return path.dirname(this.projectPath); + } + getDefaultLibFileName(options) { + return ts.getDefaultLibFilePath(options); + } + directoryExists = ts.sys.directoryExists; + getDirectories = ts.sys.getDirectories; + fileExists = ts.sys.fileExists; + readFile = ts.sys.readFile; + readDirectory = ts.sys.readDirectory; + // this is necessary to make source references work. + realpath = ts.sys.realpath; +} +/** + * TypeScript2TypeScript transformer that mangles all private and protected fields + * + * 1. Collect all class fields (properties, methods) + * 2. Collect all sub and super-type relations between classes + * 3. Compute replacement names for each field + * 4. Lookup rename locations for these fields + * 5. Prepare and apply edits + */ +class Mangler { + projectPath; + log; + allClassDataByKey = new Map(); + service; + constructor(projectPath, log = () => { }) { + this.projectPath = projectPath; + this.log = log; + this.service = ts.createLanguageService(new StaticLanguageServiceHost(projectPath)); + } + computeNewFileContents(strictImplicitPublicHandling) { + // STEP: find all classes and their field info + const visit = (node) => { + if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) { + const anchor = node.name ?? node; + const key = `${node.getSourceFile().fileName}|${anchor.getStart()}`; + if (this.allClassDataByKey.has(key)) { + throw new Error('DUPE?'); + } + this.allClassDataByKey.set(key, new ClassData(node.getSourceFile().fileName, node)); + } + ts.forEachChild(node, visit); + }; + for (const file of this.service.getProgram().getSourceFiles()) { + if (!file.isDeclarationFile) { + ts.forEachChild(file, visit); + } + } + this.log(`Done collecting classes: ${this.allClassDataByKey.size}`); + // STEP: connect sub and super-types + const setupParents = (data) => { + const extendsClause = data.node.heritageClauses?.find(h => h.token === ts.SyntaxKind.ExtendsKeyword); + if (!extendsClause) { + // no EXTENDS-clause + return; + } + const info = this.service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); + if (!info || info.length === 0) { + // throw new Error('SUPER type not found'); + return; + } + if (info.length !== 1) { + // inherits from declared/library type + return; + } + const [definition] = info; + const key = `${definition.fileName}|${definition.textSpan.start}`; + const parent = this.allClassDataByKey.get(key); + if (!parent) { + // throw new Error(`SUPER type not found: ${key}`); + return; + } + parent.addChild(data); + }; + for (const data of this.allClassDataByKey.values()) { + setupParents(data); + } + // STEP: make implicit public (actually protected) field really public + const violations = new Map(); + let violationsCauseFailure = false; + for (const data of this.allClassDataByKey.values()) { + ClassData.makeImplicitPublicActuallyPublic(data, (name, what, why) => { + const arr = violations.get(what); + if (arr) { + arr.push(why); + } + else { + violations.set(what, [why]); + } + if (strictImplicitPublicHandling && !strictImplicitPublicHandling.has(name)) { + violationsCauseFailure = true; + } + }); + } + for (const [why, whys] of violations) { + this.log(`WARN: ${why} became PUBLIC because of: ${whys.join(' , ')}`); + } + if (violationsCauseFailure) { + const message = 'Protected fields have been made PUBLIC. This hurts minification and is therefore not allowed. Review the WARN messages further above'; + this.log(`ERROR: ${message}`); + throw new Error(message); + } + // STEP: compute replacement names for each class + for (const data of this.allClassDataByKey.values()) { + ClassData.fillInReplacement(data); + } + this.log(`Done creating replacements`); + const editsByFile = new Map(); + const appendEdit = (fileName, edit) => { + const edits = editsByFile.get(fileName); + if (!edits) { + editsByFile.set(fileName, [edit]); + } + else { + edits.push(edit); + } + }; + for (const data of this.allClassDataByKey.values()) { + if (hasModifier(data.node, ts.SyntaxKind.DeclareKeyword)) { + continue; + } + fields: for (const [name, info] of data.fields) { + if (!ClassData._shouldMangle(info.type)) { + continue fields; + } + // TS-HACK: protected became public via 'some' child + // and because of that we might need to ignore this now + let parent = data.parent; + while (parent) { + if (parent.fields.get(name)?.type === 0 /* FieldType.Public */) { + continue fields; + } + parent = parent.parent; + } + const newText = data.lookupShortName(name); + const locations = this.service.findRenameLocations(data.fileName, info.pos, false, false, true) ?? []; + for (const loc of locations) { + appendEdit(loc.fileName, { + newText: (loc.prefixText || '') + newText + (loc.suffixText || ''), + offset: loc.textSpan.start, + length: loc.textSpan.length + }); + } + } + } + this.log(`Done preparing edits: ${editsByFile.size} files`); + // STEP: apply all rename edits (per file) + const result = new Map(); + let savedBytes = 0; + for (const item of this.service.getProgram().getSourceFiles()) { + const { mapRoot, sourceRoot } = this.service.getProgram().getCompilerOptions(); + const projectDir = path.dirname(this.projectPath); + const sourceMapRoot = mapRoot ?? (0, url_1.pathToFileURL)(sourceRoot ?? projectDir).toString(); + // source maps + let generator; + let newFullText; + const edits = editsByFile.get(item.fileName); + if (!edits) { + // just copy + newFullText = item.getFullText(); + } + else { + // source map generator + const relativeFileName = normalize(path.relative(projectDir, item.fileName)); + const mappingsByLine = new Map(); + // apply renames + edits.sort((a, b) => b.offset - a.offset); + const characters = item.getFullText().split(''); + let lastEdit; + for (const edit of edits) { + if (lastEdit && lastEdit.offset === edit.offset) { + // + if (lastEdit.length !== edit.length || lastEdit.newText !== edit.newText) { + this.log('ERROR: Overlapping edit', item.fileName, edit.offset, edits); + throw new Error('OVERLAPPING edit'); + } + else { + continue; + } + } + lastEdit = edit; + const mangledName = characters.splice(edit.offset, edit.length, edit.newText).join(''); + savedBytes += mangledName.length - edit.newText.length; + // source maps + const pos = item.getLineAndCharacterOfPosition(edit.offset); + let mappings = mappingsByLine.get(pos.line); + if (!mappings) { + mappings = []; + mappingsByLine.set(pos.line, mappings); + } + mappings.unshift({ + source: relativeFileName, + original: { line: pos.line + 1, column: pos.character }, + generated: { line: pos.line + 1, column: pos.character }, + name: mangledName + }, { + source: relativeFileName, + original: { line: pos.line + 1, column: pos.character + edit.length }, + generated: { line: pos.line + 1, column: pos.character + edit.newText.length }, + }); + } + // source map generation, make sure to get mappings per line correct + generator = new source_map_1.SourceMapGenerator({ file: path.basename(item.fileName), sourceRoot: sourceMapRoot }); + generator.setSourceContent(relativeFileName, item.getFullText()); + for (const [, mappings] of mappingsByLine) { + let lineDelta = 0; + for (const mapping of mappings) { + generator.addMapping({ + ...mapping, + generated: { line: mapping.generated.line, column: mapping.generated.column - lineDelta } + }); + lineDelta += mapping.original.column - mapping.generated.column; + } + } + newFullText = characters.join(''); + } + result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); + } + this.log(`Done: ${savedBytes / 1000}kb saved`); + return result; + } +} +exports.Mangler = Mangler; +// --- ast utils +function hasModifier(node, kind) { + const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + return Boolean(modifiers?.find(mode => mode.kind === kind)); +} +function normalize(path) { + return path.replace(/\\/g, '/'); +} +// async function _run() { +// const projectPath = path.join(__dirname, '../../src/tsconfig.json'); +// const projectBase = path.dirname(projectPath); +// const newProjectBase = path.join(path.dirname(projectBase), path.basename(projectBase) + '2'); +// for await (const [fileName, contents] of new Mangler(projectPath, console.log).computeNewFileContents(new Set(['saveState']))) { +// const newFilePath = path.join(newProjectBase, path.relative(projectBase, fileName)); +// await fs.promises.mkdir(path.dirname(newFilePath), { recursive: true }); +// await fs.promises.writeFile(newFilePath, contents.out); +// if (contents.sourceMap) { +// await fs.promises.writeFile(newFilePath + '.map', contents.sourceMap); +// } +// } +// } +// if (__filename === argv[1]) { +// _run(); +// } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFuZ2xlVHlwZVNjcmlwdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm1hbmdsZVR5cGVTY3JpcHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsaUNBQWlDO0FBQ2pDLDZCQUE2QjtBQUM3QixvRUFBb0U7QUFDcEUsa0NBQWtDO0FBQ2xDLDJDQUF5RDtBQUN6RCw2QkFBb0M7QUFFcEMsTUFBTSxVQUFVO0lBRVAsTUFBTSxDQUFDLFNBQVMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxDQUFDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLFVBQVUsRUFBRSxVQUFVO1FBQzlHLFNBQVMsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLFVBQVUsRUFBRSxJQUFJO1FBQ25HLFFBQVEsRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxRQUFRLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsT0FBTztRQUMxRyxNQUFNLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQztJQUU1RCxNQUFNLENBQUMsU0FBUyxHQUFHLHNEQUFzRCxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUVwRixNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQ0YsWUFBWSxDQUE0QjtJQUV6RCxZQUFZLFdBQXNDO1FBQ2pELElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxXQUFXLENBQUMsSUFB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 \ No newline at end of file diff --git a/build/lib/mangleTypeScript.ts b/build/lib/mangleTypeScript.ts new file mode 100644 index 0000000000..bfeb404f57 --- /dev/null +++ b/build/lib/mangleTypeScript.ts @@ -0,0 +1,604 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as ts from 'typescript'; +import * as path from 'path'; +// import * as fs from 'fs'; // {{SQL CARBON EDIT}} - disable unused +// import { argv } from 'process'; +import { Mapping, SourceMapGenerator } from 'source-map'; +import { pathToFileURL } from 'url'; + +class ShortIdent { + + private static _keywords = new Set(['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', + 'default', 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', + 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return', 'static', 'super', 'switch', 'this', 'throw', + 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield']); + + private static _alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); + + private _value = 0; + private readonly _isNameTaken: (name: string) => boolean; + + constructor(isNameTaken: (name: string) => boolean) { + this._isNameTaken = name => ShortIdent._keywords.has(name) || isNameTaken(name); + } + + next(): string { + const candidate = ShortIdent.convert(this._value); + this._value++; + if (this._isNameTaken(candidate)) { + // try again + return this.next(); + } + return candidate; + } + + private static convert(n: number): string { + const base = this._alphabet.length; + let result = ''; + do { + const rest = n % base; + result += this._alphabet[rest]; + n = (n / base) | 0; + } while (n > 0); + return result; + } +} + +const enum FieldType { + Public, + Protected, + Private +} + +class ClassData { + + fields = new Map(); + + private replacements: Map | undefined; + + parent: ClassData | undefined; + children: ClassData[] | undefined; + + constructor( + readonly fileName: string, + readonly node: ts.ClassDeclaration | ts.ClassExpression, + ) { + // analyse all fields (properties and methods). Find usages of all protected and + // private ones and keep track of all public ones (to prevent naming collisions) + + const candidates: (ts.NamedDeclaration)[] = []; + for (const member of node.members) { + if (ts.isMethodDeclaration(member)) { + // method `foo() {}` + candidates.push(member); + + } else if (ts.isPropertyDeclaration(member)) { + // property `foo = 234` + candidates.push(member); + + } else if (ts.isGetAccessor(member)) { + // getter: `get foo() { ... }` + candidates.push(member); + + } else if (ts.isSetAccessor(member)) { + // setter: `set foo() { ... }` + candidates.push(member); + + } else if (ts.isConstructorDeclaration(member)) { + // constructor-prop:`constructor(private foo) {}` + for (const param of member.parameters) { + if (hasModifier(param, ts.SyntaxKind.PrivateKeyword) + || hasModifier(param, ts.SyntaxKind.ProtectedKeyword) + || hasModifier(param, ts.SyntaxKind.PublicKeyword) + || hasModifier(param, ts.SyntaxKind.ReadonlyKeyword) + ) { + candidates.push(param); + } + } + } + } + for (const member of candidates) { + const ident = ClassData._getMemberName(member); + if (!ident) { + continue; + } + const type = ClassData._getFieldType(member); + this.fields.set(ident, { type, pos: member.name!.getStart() }); + } + } + + private static _getMemberName(node: ts.NamedDeclaration): string | undefined { + if (!node.name) { + return undefined; + } + const { name } = node; + let ident = name.getText(); + if (name.kind === ts.SyntaxKind.ComputedPropertyName) { + if (name.expression.kind !== ts.SyntaxKind.StringLiteral) { + // unsupported: [Symbol.foo] or [abc + 'field'] + return; + } + // ['foo'] + ident = name.expression.getText().slice(1, -1); + } + + return ident; + } + + private static _getFieldType(node: ts.Node): FieldType { + if (hasModifier(node, ts.SyntaxKind.PrivateKeyword)) { + return FieldType.Private; + } else if (hasModifier(node, ts.SyntaxKind.ProtectedKeyword)) { + return FieldType.Protected; + } else { + return FieldType.Public; + } + } + + static _shouldMangle(type: FieldType): boolean { + return type === FieldType.Private + || type === FieldType.Protected + ; + } + + static makeImplicitPublicActuallyPublic(data: ClassData, reportViolation: (name: string, what: string, why: string) => void): void { + // TS-HACK + // A subtype can make an inherited protected field public. To prevent accidential + // mangling of public fields we mark the original (protected) fields as public... + for (const [name, info] of data.fields) { + if (info.type !== FieldType.Public) { + continue; + } + let parent: ClassData | undefined = data.parent; + while (parent) { + if (parent.fields.get(name)?.type === FieldType.Protected) { + const parentPos = parent.node.getSourceFile().getLineAndCharacterOfPosition(parent.fields.get(name)!.pos); + const infoPos = data.node.getSourceFile().getLineAndCharacterOfPosition(info.pos); + reportViolation(name, `'${name}' from ${parent.fileName}:${parentPos.line + 1}`, `${data.fileName}:${infoPos.line + 1}`); + + parent.fields.get(name)!.type = FieldType.Public; + } + parent = parent.parent; + } + } + } + + static fillInReplacement(data: ClassData) { + + if (data.replacements) { + // already done + return; + } + + // fill in parents first + if (data.parent) { + ClassData.fillInReplacement(data.parent); + } + + data.replacements = new Map(); + + const identPool = new ShortIdent(name => { + + // locally taken + if (data._isNameTaken(name)) { + return true; + } + + // parents + let parent: ClassData | undefined = data.parent; + while (parent) { + if (parent._isNameTaken(name)) { + return true; + } + parent = parent.parent; + } + + // children + if (data.children) { + const stack = [...data.children]; + while (stack.length) { + const node = stack.pop()!; + if (node._isNameTaken(name)) { + return true; + } + if (node.children) { + stack.push(...node.children); + } + } + } + + return false; + }); + + for (const [name, info] of data.fields) { + if (ClassData._shouldMangle(info.type)) { + const shortName = identPool.next(); + data.replacements.set(name, shortName); + } + } + } + + // a name is taken when a field that doesn't get mangled exists or + // when the name is already in use for replacement + private _isNameTaken(name: string) { + if (this.fields.has(name) && !ClassData._shouldMangle(this.fields.get(name)!.type)) { + // public field + return true; + } + if (this.replacements) { + for (const shortName of this.replacements.values()) { + if (shortName === name) { + // replaced already (happens wih super types) + return true; + } + } + } + if ((this.node.getSourceFile()).identifiers instanceof Map) { + // taken by any other usage + if ((this.node.getSourceFile()).identifiers.has(name)) { + return true; + } + } + return false; + } + + lookupShortName(name: string): string { + let value = this.replacements!.get(name)!; + let parent = this.parent; + while (parent) { + if (parent.replacements!.has(name) && parent.fields.get(name)?.type === FieldType.Protected) { + value = parent.replacements!.get(name)! ?? value; + } + parent = parent.parent; + } + return value; + } + + // --- parent chaining + + addChild(child: ClassData) { + this.children ??= []; + this.children.push(child); + child.parent = this; + } +} + +class StaticLanguageServiceHost implements ts.LanguageServiceHost { + + private readonly _cmdLine: ts.ParsedCommandLine; + private readonly _scriptSnapshots: Map = new Map(); + + constructor(readonly projectPath: string) { + const existingOptions: Partial = {}; + const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); + if (parsed.error) { + throw parsed.error; + } + this._cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, path.dirname(projectPath), existingOptions); + if (this._cmdLine.errors.length > 0) { + throw parsed.error; + } + } + getCompilationSettings(): ts.CompilerOptions { + return this._cmdLine.options; + } + getScriptFileNames(): string[] { + return this._cmdLine.fileNames; + } + getScriptVersion(_fileName: string): string { + return '1'; + } + getProjectVersion(): string { + return '1'; + } + getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { + let result: ts.IScriptSnapshot | undefined = this._scriptSnapshots.get(fileName); + if (result === undefined) { + const content = ts.sys.readFile(fileName); + if (content === undefined) { + return undefined; + } + result = ts.ScriptSnapshot.fromString(content); + this._scriptSnapshots.set(fileName, result); + } + return result; + } + getCurrentDirectory(): string { + return path.dirname(this.projectPath); + } + getDefaultLibFileName(options: ts.CompilerOptions): string { + return ts.getDefaultLibFilePath(options); + } + directoryExists = ts.sys.directoryExists; + getDirectories = ts.sys.getDirectories; + fileExists = ts.sys.fileExists; + readFile = ts.sys.readFile; + readDirectory = ts.sys.readDirectory; + // this is necessary to make source references work. + realpath = ts.sys.realpath; +} + +export interface MangleOutput { + out: string; + sourceMap?: string; +} + +/** + * TypeScript2TypeScript transformer that mangles all private and protected fields + * + * 1. Collect all class fields (properties, methods) + * 2. Collect all sub and super-type relations between classes + * 3. Compute replacement names for each field + * 4. Lookup rename locations for these fields + * 5. Prepare and apply edits + */ +export class Mangler { + + private readonly allClassDataByKey = new Map(); + + private readonly service: ts.LanguageService; + + constructor(readonly projectPath: string, readonly log: typeof console.log = () => { }) { + this.service = ts.createLanguageService(new StaticLanguageServiceHost(projectPath)); + } + + computeNewFileContents(strictImplicitPublicHandling?: Set): Map { + + // STEP: find all classes and their field info + + const visit = (node: ts.Node): void => { + if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) { + const anchor = node.name ?? node; + const key = `${node.getSourceFile().fileName}|${anchor.getStart()}`; + if (this.allClassDataByKey.has(key)) { + throw new Error('DUPE?'); + } + this.allClassDataByKey.set(key, new ClassData(node.getSourceFile().fileName, node)); + } + ts.forEachChild(node, visit); + }; + + for (const file of this.service.getProgram()!.getSourceFiles()) { + if (!file.isDeclarationFile) { + ts.forEachChild(file, visit); + } + } + this.log(`Done collecting classes: ${this.allClassDataByKey.size}`); + + + // STEP: connect sub and super-types + + const setupParents = (data: ClassData) => { + const extendsClause = data.node.heritageClauses?.find(h => h.token === ts.SyntaxKind.ExtendsKeyword); + if (!extendsClause) { + // no EXTENDS-clause + return; + } + + const info = this.service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); + if (!info || info.length === 0) { + // throw new Error('SUPER type not found'); + return; + } + + if (info.length !== 1) { + // inherits from declared/library type + return; + } + + const [definition] = info; + const key = `${definition.fileName}|${definition.textSpan.start}`; + const parent = this.allClassDataByKey.get(key); + if (!parent) { + // throw new Error(`SUPER type not found: ${key}`); + return; + } + parent.addChild(data); + }; + for (const data of this.allClassDataByKey.values()) { + setupParents(data); + } + + // STEP: make implicit public (actually protected) field really public + const violations = new Map(); + let violationsCauseFailure = false; + for (const data of this.allClassDataByKey.values()) { + ClassData.makeImplicitPublicActuallyPublic(data, (name: string, what, why) => { + const arr = violations.get(what); + if (arr) { + arr.push(why); + } else { + violations.set(what, [why]); + } + + if (strictImplicitPublicHandling && !strictImplicitPublicHandling.has(name)) { + violationsCauseFailure = true; + } + }); + } + for (const [why, whys] of violations) { + this.log(`WARN: ${why} became PUBLIC because of: ${whys.join(' , ')}`); + } + if (violationsCauseFailure) { + const message = 'Protected fields have been made PUBLIC. This hurts minification and is therefore not allowed. Review the WARN messages further above'; + this.log(`ERROR: ${message}`); + throw new Error(message); + } + + // STEP: compute replacement names for each class + for (const data of this.allClassDataByKey.values()) { + ClassData.fillInReplacement(data); + } + this.log(`Done creating replacements`); + + // STEP: prepare rename edits + type Edit = { newText: string; offset: number; length: number }; + const editsByFile = new Map(); + + const appendEdit = (fileName: string, edit: Edit) => { + const edits = editsByFile.get(fileName); + if (!edits) { + editsByFile.set(fileName, [edit]); + } else { + edits.push(edit); + } + }; + + for (const data of this.allClassDataByKey.values()) { + + if (hasModifier(data.node, ts.SyntaxKind.DeclareKeyword)) { + continue; + } + + fields: for (const [name, info] of data.fields) { + if (!ClassData._shouldMangle(info.type)) { + continue fields; + } + + // TS-HACK: protected became public via 'some' child + // and because of that we might need to ignore this now + let parent = data.parent; + while (parent) { + if (parent.fields.get(name)?.type === FieldType.Public) { + continue fields; + } + parent = parent.parent; + } + + const newText = data.lookupShortName(name); + const locations = this.service.findRenameLocations(data.fileName, info.pos, false, false, true) ?? []; + for (const loc of locations) { + appendEdit(loc.fileName, { + newText: (loc.prefixText || '') + newText + (loc.suffixText || ''), + offset: loc.textSpan.start, + length: loc.textSpan.length + }); + } + } + } + + this.log(`Done preparing edits: ${editsByFile.size} files`); + + // STEP: apply all rename edits (per file) + const result = new Map(); + let savedBytes = 0; + + for (const item of this.service.getProgram()!.getSourceFiles()) { + + const { mapRoot, sourceRoot } = this.service.getProgram()!.getCompilerOptions(); + const projectDir = path.dirname(this.projectPath); + const sourceMapRoot = mapRoot ?? pathToFileURL(sourceRoot ?? projectDir).toString(); + + // source maps + let generator: SourceMapGenerator | undefined; + + let newFullText: string; + const edits = editsByFile.get(item.fileName); + if (!edits) { + // just copy + newFullText = item.getFullText(); + + } else { + // source map generator + const relativeFileName = normalize(path.relative(projectDir, item.fileName)); + const mappingsByLine = new Map(); + + // apply renames + edits.sort((a, b) => b.offset - a.offset); + const characters = item.getFullText().split(''); + + let lastEdit: Edit | undefined; + + for (const edit of edits) { + if (lastEdit && lastEdit.offset === edit.offset) { + // + if (lastEdit.length !== edit.length || lastEdit.newText !== edit.newText) { + this.log('ERROR: Overlapping edit', item.fileName, edit.offset, edits); + throw new Error('OVERLAPPING edit'); + } else { + continue; + } + } + lastEdit = edit; + const mangledName = characters.splice(edit.offset, edit.length, edit.newText).join(''); + savedBytes += mangledName.length - edit.newText.length; + + // source maps + const pos = item.getLineAndCharacterOfPosition(edit.offset); + + + let mappings = mappingsByLine.get(pos.line); + if (!mappings) { + mappings = []; + mappingsByLine.set(pos.line, mappings); + } + mappings.unshift({ + source: relativeFileName, + original: { line: pos.line + 1, column: pos.character }, + generated: { line: pos.line + 1, column: pos.character }, + name: mangledName + }, { + source: relativeFileName, + original: { line: pos.line + 1, column: pos.character + edit.length }, + generated: { line: pos.line + 1, column: pos.character + edit.newText.length }, + }); + } + + // source map generation, make sure to get mappings per line correct + generator = new SourceMapGenerator({ file: path.basename(item.fileName), sourceRoot: sourceMapRoot }); + generator.setSourceContent(relativeFileName, item.getFullText()); + for (const [, mappings] of mappingsByLine) { + let lineDelta = 0; + for (const mapping of mappings) { + generator.addMapping({ + ...mapping, + generated: { line: mapping.generated.line, column: mapping.generated.column - lineDelta } + }); + lineDelta += mapping.original.column - mapping.generated.column; + } + } + + newFullText = characters.join(''); + } + result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); + } + + this.log(`Done: ${savedBytes / 1000}kb saved`); + return result; + } +} + +// --- ast utils + +function hasModifier(node: ts.Node, kind: ts.SyntaxKind) { + const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined; + return Boolean(modifiers?.find(mode => mode.kind === kind)); +} + +function normalize(path: string): string { + return path.replace(/\\/g, '/'); +} + +// async function _run() { + +// const projectPath = path.join(__dirname, '../../src/tsconfig.json'); +// const projectBase = path.dirname(projectPath); +// const newProjectBase = path.join(path.dirname(projectBase), path.basename(projectBase) + '2'); + +// for await (const [fileName, contents] of new Mangler(projectPath, console.log).computeNewFileContents(new Set(['saveState']))) { +// const newFilePath = path.join(newProjectBase, path.relative(projectBase, fileName)); +// await fs.promises.mkdir(path.dirname(newFilePath), { recursive: true }); +// await fs.promises.writeFile(newFilePath, contents.out); +// if (contents.sourceMap) { +// await fs.promises.writeFile(newFilePath + '.map', contents.sourceMap); +// } +// } +// } + +// if (__filename === argv[1]) { +// _run(); +// } diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js index 7dcdcfbdbb..8db9b92ea3 100644 --- a/build/lib/monaco-api.js +++ b/build/lib/monaco-api.js @@ -104,7 +104,10 @@ function hasModifier(modifiers, kind) { return false; } function isStatic(ts, member) { - return hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword); + if (ts.canHaveModifiers(member)) { + return hasModifier(ts.getModifiers(member), ts.SyntaxKind.StaticKeyword); + } + return false; } function isDefaultExport(ts, declaration) { return (hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword) @@ -492,12 +495,17 @@ class FSProvider { } exports.FSProvider = FSProvider; class CacheEntry { + sourceFile; + mtime; constructor(sourceFile, mtime) { this.sourceFile = sourceFile; this.mtime = mtime; } } class DeclarationResolver { + _fsProvider; + ts; + _sourceFileCache; constructor(_fsProvider) { this._fsProvider = _fsProvider; this.ts = require('typescript'); @@ -553,6 +561,10 @@ function run3(resolver) { } exports.run3 = run3; class TypeScriptLanguageServiceHost { + _ts; + _libs; + _files; + _compilerOptions; constructor(ts, libs, files, compilerOptions) { this._ts = ts; this._libs = libs; @@ -612,3 +624,4 @@ function execute() { return r; } exports.execute = execute; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9uYWNvLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm1vbmFjby1hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcseUJBQXlCO0FBRXpCLDZCQUE2QjtBQUM3QixzQ0FBc0M7QUFDdEMsMENBQTBDO0FBRTFDLE1BQU0sSUFBSSxHQUFHLEdBQUcsQ0FBQztBQUVqQixNQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsa0JBQWtCLENBQUMsQ0FBQztBQUUxQyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxXQUFXLENBQUMsQ0FBQztBQUNqQyxRQUFBLFdBQVcsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSw4QkFBOEIsQ0FBQyxDQUFDO0FBQ2hGLE1BQU0sZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsMEJBQTBCLENBQUMsQ0FBQztBQUUxRSxTQUFTLE1BQU0sQ0FBQyxPQUFZLEVBQUUsR0FBRyxJQUFXO0lBQzNDLFFBQVEsQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLGVBQWUsQ0FBQyxFQUFFLE9BQU8sRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO0FBQ2hFLENBQUM7QUFPRCxTQUFTLGFBQWEsQ0FBQyxFQUErQixFQUFFLENBQW9CO0lBQzNFLE9BQU8sQ0FDTixDQUFDLENBQUMsSUFBSSxLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsb0JBQW9CO1dBQzFDLENBQUMsQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyxlQUFlO1dBQ3hDLENBQUMsQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0I7V0FDekMsQ0FBQyxDQUFDLElBQUksS0FBSyxFQUFFLENBQUMsVUFBVSxDQUFDLG9CQUFvQjtXQUM3QyxDQUFDLENBQUMsSUFBSSxLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsbUJBQW1CO1dBQzVDLENBQUMsQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyxpQkFBaUIsQ0FDN0MsQ0FBQztBQUNILENBQUM7QUFFRCxTQUFTLHlCQUF5QixDQUFDLEVBQStCLEVBQUUsVUFBeUIsRUFBRSxPQUE2QztJQUMzSSxJQUFJLElBQUksR0FBRyxLQUFLLENBQUM7SUFFakIsTUFBTSxLQUFLLEdBQUcsQ0FBQyxJQUFhLEVBQVEsRUFBRTtRQUNyQyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU87U0FDUDtRQUVELFFBQVEsSUFBSSxDQUFDLElBQUksRUFBRTtZQUNsQixLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsb0JBQW9CLENBQUM7WUFDeEMsS0FBSyxFQUFFLENBQUMsVUFBVSxDQUFDLGVBQWUsQ0FBQztZQUNuQyxLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsZ0JBQWdCLENBQUM7WUFDcEMsS0FBSyxFQUFFLENBQUMsVUFBVSxDQUFDLGlCQUFpQixDQUFDO1lBQ3JDLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyxvQkFBb0IsQ0FBQztZQUN4QyxLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsbUJBQW1CLENBQUM7WUFDdkMsS0FBSyxFQUFFLENBQUMsVUFBVSxDQUFDLGlCQUFpQjtnQkFDbkMsSUFBSSxHQUFHLE9BQU8sQ0FBb0IsSUFBSSxDQUFDLENBQUM7U0FDekM7UUFFRCxJQUFJLElBQUksRUFBRTtZQUNULE9BQU87U0FDUDtRQUNELEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzlCLENBQUMsQ0FBQztJQUVGLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztBQUNuQixDQUFDO0FBR0QsU0FBUywwQkFBMEIsQ0FBQyxFQUErQixFQUFFLFVBQXlCO0lBQzdGLE1BQU0sR0FBRyxHQUF3QixFQUFFLENBQUM7SUFDcEMseUJBQXlCLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxDQUFDLElBQUksRUFBRSxFQUFFO1FBQ2xELElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxFQUFFLENBQUMsVUFBVSxDQUFDLG9CQUFvQixJQUFJLElBQUksQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0IsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsaUJBQWlCLEVBQUU7WUFDdEosTUFBTSxvQkFBb0IsR0FBNEIsSUFBSSxDQUFDO1lBQzNELE1BQU0sV0FBVyxHQUFHLG9CQUFvQixDQUFDLEdBQUcsQ0FBQztZQUM3QyxNQUFNLFNBQVMsR0FBRyxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDO1lBQ2hELE1BQU0sVUFBVSxHQUFHLFdBQVcsQ0FBQyxVQUFVLEVBQUUsRUFBRSxHQUFHLEVBQUUsV0FBVyxFQUFFLEdBQUcsRUFBRSxTQUFTLEVBQUUsQ0FBQyxDQUFDO1lBRWpGLElBQUksVUFBVSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRTtnQkFDM0MsR0FBRyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUNmO1NBQ0Q7YUFBTTtZQUNOLE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFDL0MsSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFO2dCQUN6QyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQ2Y7U0FDRDtRQUNELE9BQU8sS0FBSyxDQUFDLFlBQVksQ0FBQztJQUMzQixDQUFDLENBQUMsQ0FBQztJQUNILE9BQU8sR0FBRyxDQUFDO0FBQ1osQ0FBQztBQUdELFNBQVMsc0JBQXNCLENBQUMsRUFBK0IsRUFBRSxVQUF5QixFQUFFLFFBQWdCO0lBQzNHLElBQUksTUFBTSxHQUE2QixJQUFJLENBQUM7SUFDNUMseUJBQXlCLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxDQUFDLElBQUksRUFBRSxFQUFFO1FBQ2xELElBQUksYUFBYSxDQUFDLEVBQUUsRUFBRSxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsSUFBSSxFQUFFO1lBQ3pDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEtBQUssUUFBUSxFQUFFO2dCQUNoQyxNQUFNLEdBQUcsSUFBSSxDQUFDO2dCQUNkLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQzthQUNyQjtZQUNELE9BQU8sS0FBSyxDQUFDLFlBQVksQ0FBQztTQUMxQjtRQUNELCtCQUErQjtRQUMvQixJQUFJLFdBQVcsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUN6RCxNQUFNLEdBQUcsSUFBSSxDQUFDO1lBQ2QsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDO1NBQ3JCO1FBQ0QsT0FBTyxLQUFLLENBQUMsWUFBWSxDQUFDO0lBQzNCLENBQUMsQ0FBQyxDQUFDO0lBQ0gsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDO0FBR0QsU0FBUyxXQUFXLENBQUMsVUFBeUIsRUFBRSxJQUFrQztJQUNqRixPQUFPLFVBQVUsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDL0QsQ0FBQztBQUVELFNBQVMsV0FBVyxDQUFDLFNBQWlELEVBQUUsSUFBbUI7SUFDMUYsSUFBSSxTQUFTLEVBQUU7UUFDZCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsU0FBUyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUMxQyxNQUFNLEdBQUcsR0FBRyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDekIsSUFBSSxHQUFHLENBQUMsSUFBSSxLQUFLLElBQUksRUFBRTtnQkFDdEIsT0FBTyxJQUFJLENBQUM7YUFDWjtTQUNEO0tBQ0Q7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNkLENBQUM7QUFFRCxTQUFTLFFBQVEsQ0FBQyxFQUErQixFQUFFLE1BQXdDO0lBQzFGLElBQUksRUFBRSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxFQUFFO1FBQ2hDLE9BQU8sV0FBVyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRSxDQUFDLFVBQVUsQ0FBQyxhQUFhLENBQUMsQ0FBQztLQUN6RTtJQUNELE9BQU8sS0FBSyxDQUFDO0FBQ2QsQ0FBQztBQUVELFNBQVMsZUFBZSxDQUFDLEVBQStCLEVBQUUsV0FBMEQ7SUFDbkgsT0FBTyxDQUNOLFdBQVcsQ0FBQyxXQUFXLENBQUMsU0FBUyxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsY0FBYyxDQUFDO1dBQzdELFdBQVcsQ0FBQyxXQUFXLENBQUMsU0FBUyxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsYUFBYSxDQUFDLENBQ2xFLENBQUM7QUFDSCxDQUFDO0FBRUQsU0FBUyxrQ0FBa0MsQ0FBQyxFQUErQixFQUFFLFVBQXlCLEVBQUUsV0FBOEIsRUFBRSxVQUFrQixFQUFFLEtBQWUsRUFBRSxLQUFtQjtJQUMvTCxJQUFJLE1BQU0sR0FBRyxXQUFXLENBQUMsVUFBVSxFQUFFLFdBQVcsQ0FBQyxDQUFDO0lBQ2xELElBQUksV0FBVyxDQUFDLElBQUksS0FBSyxFQUFFLENBQUMsVUFBVSxDQUFDLG9CQUFvQixJQUFJLFdBQVcsQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0IsRUFBRTtRQUNuSCxNQUFNLG9CQUFvQixHQUFrRCxXQUFXLENBQUM7UUFFeEYsTUFBTSxjQUFjLEdBQUcsQ0FDdEIsZUFBZSxDQUFDLEVBQUUsRUFBRSxvQkFBb0IsQ0FBQztZQUN4QyxDQUFDLENBQUMsR0FBRyxVQUFVLFVBQVU7WUFDekIsQ0FBQyxDQUFDLEdBQUcsVUFBVSxJQUFJLFdBQVcsQ0FBQyxJQUFLLENBQUMsSUFBSSxFQUFFLENBQzVDLENBQUM7UUFFRixJQUFJLGdCQUFnQixHQUFHLGNBQWMsQ0FBQztRQUN0QyxNQUFNLGlCQUFpQixHQUFHLENBQUMsb0JBQW9CLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQyxvQkFBb0IsQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNqSCxJQUFJLGlCQUFpQixHQUFHLENBQUMsRUFBRTtZQUMxQixNQUFNLEdBQUcsR0FBYSxFQUFFLENBQUM7WUFDekIsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLGlCQUFpQixFQUFFLENBQUMsRUFBRSxFQUFFO2dCQUMzQyxHQUFHLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO2FBQ2hCO1lBQ0QsZ0JBQWdCLEdBQUcsR0FBRyxnQkFBZ0IsSUFBSSxHQUFHLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUM7U0FDM0Q7UUFFRCxNQUFNLE9BQU8sR0FBbUQsb0JBQW9CLENBQUMsT0FBTyxDQUFDO1FBQzdGLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQyxNQUFNLEVBQUUsRUFBRTtZQUMxQixJQUFJO2dCQUNILE1BQU0sVUFBVSxHQUFHLFdBQVcsQ0FBQyxVQUFVLEVBQUUsTUFBTSxDQUFDLENBQUM7Z0JBQ25ELElBQUksVUFBVSxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLElBQUksVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsSUFBSSxDQUFDLEVBQUU7b0JBQy9FLE1BQU0sR0FBRyxNQUFNLENBQUMsT0FBTyxDQUFDLFVBQVUsRUFBRSxFQUFFLENBQUMsQ0FBQztpQkFDeEM7cUJBQU07b0JBQ04sTUFBTSxVQUFVLEdBQXNDLE1BQU0sQ0FBQyxJQUFLLENBQUMsSUFBSSxDQUFDO29CQUN4RSxNQUFNLFlBQVksR0FBRyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxLQUFLLFVBQVUsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLFVBQVUsRUFBRSxDQUFDLENBQUM7b0JBQzdGLElBQUksUUFBUSxDQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsRUFBRTt3QkFDekIsS0FBSyxDQUFDLElBQUksQ0FBQyxPQUFPLGNBQWMsR0FBRyxZQUFZLEdBQUcsQ0FBQyxDQUFDO3FCQUNwRDt5QkFBTTt3QkFDTixLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsZ0JBQWdCLE1BQU0sWUFBWSxHQUFHLENBQUMsQ0FBQztxQkFDM0Q7aUJBQ0Q7YUFDRDtZQUFDLE9BQU8sR0FBRyxFQUFFO2dCQUNiLFNBQVM7YUFDVDtRQUNGLENBQUMsQ0FBQyxDQUFDO0tBQ0g7SUFDRCxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxrQkFBa0IsRUFBRSxTQUFTLENBQUMsQ0FBQztJQUN2RCxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxrQkFBa0IsRUFBRSxTQUFTLENBQUMsQ0FBQztJQUN2RCxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDekMsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQztJQUN6QyxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtRQUN0QyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUU7WUFDM0Isd0JBQXdCO1lBQ3hCLFNBQVM7U0FDVDtRQUNELEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztLQUN4QztJQUNELE1BQU0sR0FBRyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBRTFCLElBQUksV0FBVyxDQUFDLElBQUksS0FBSyxFQUFFLENBQUMsVUFBVSxDQUFDLGVBQWUsRUFBRTtRQUN2RCxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQyxZQUFZLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFDOUMsS0FBSyxDQUFDLElBQUksQ0FBQztZQUNWLFFBQVEsRUFBRSxXQUFXLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUM7WUFDOUMsSUFBSSxFQUFFLE1BQU07U0FDWixDQUFDLENBQUM7S0FDSDtJQUVELE9BQU8sTUFBTSxDQUFDO0FBQ2YsQ0FBQztBQUVELFNBQVMsTUFBTSxDQUFDLEVBQStCLEVBQUUsSUFBWSxFQUFFLElBQVk7SUFDMUUsTUFBTSxhQUFhLEdBQUcsS0FBSyxDQUFDO0lBRTVCLElBQUksR0FBRyxTQUFTLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQzdCLElBQUksQ0FBQyxhQUFhLEVBQUU7UUFDbkIsT0FBTyxJQUFJLENBQUM7S0FDWjtJQUVELHdCQUF3QjtJQUN4QixNQUFNLFVBQVUsR0FBRyxFQUFFLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxFQUFFLENBQUMsWUFBWSxDQUFDLE1BQU0sRUFBRSxxQkFBcUIsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUU1RyxnREFBZ0Q7SUFDaEQsTUFBTSxLQUFLLEdBQVMsRUFBRyxDQUFDLFVBQVUsQ0FBQyxjQUFjLENBQUMsVUFBVSxFQUFFLGVBQWUsQ0FBQyxLQUFLLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUU3RixvQ0FBb0M7SUFDcEMsT0FBTyxVQUFVLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBRS9CLFNBQVMsZ0JBQWdCLENBQUMsSUFBWTtRQUNyQyxJQUFJLEdBQUcsR0FBRyxDQUFDLENBQUM7UUFDWixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUNyQyxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssR0FBRyxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssR0FBRyxFQUFFO2dCQUNyRCxHQUFHLEVBQUUsQ0FBQzthQUNOO1lBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUcsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLEdBQUcsRUFBRTtnQkFDckQsR0FBRyxFQUFFLENBQUM7YUFDTjtTQUNEO1FBQ0QsT0FBTyxHQUFHLENBQUM7SUFDWixDQUFDO0lBRUQsU0FBUyxTQUFTLENBQUMsQ0FBUyxFQUFFLEdBQVc7UUFDeEMsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ1gsS0FBSyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxHQUFHLEdBQUcsRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUM3QixDQUFDLElBQUksQ0FBQyxDQUFDO1NBQ1A7UUFDRCxPQUFPLENBQUMsQ0FBQztJQUNWLENBQUM7SUFFRCxTQUFTLFNBQVMsQ0FBQyxJQUFZLEVBQUUsSUFBWTtRQUM1QyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQy9CLElBQUksU0FBUyxHQUFHLEtBQUssQ0FBQztRQUN0QixJQUFJLG9CQUFvQixHQUFHLENBQUMsQ0FBQztRQUM3QixJQUFJLE1BQU0sR0FBRyxDQUFDLENBQUM7UUFDZixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUN0QyxJQUFJLElBQUksR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQztZQUN2QyxJQUFJLE1BQU0sR0FBRyxLQUFLLENBQUM7WUFDbkIsSUFBSSxVQUFVLEdBQUcsQ0FBQyxDQUFDO1lBQ25CLEdBQUc7Z0JBQ0YsTUFBTSxHQUFHLEtBQUssQ0FBQztnQkFDZixJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxLQUFLLE1BQU0sRUFBRTtvQkFDcEMsSUFBSSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7b0JBQ3pCLFVBQVUsRUFBRSxDQUFDO29CQUNiLE1BQU0sR0FBRyxJQUFJLENBQUM7aUJBQ2Q7Z0JBQ0QsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksRUFBRTtvQkFDNUIsSUFBSSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUM7b0JBQ3pCLFVBQVUsRUFBRSxDQUFDO29CQUNiLE1BQU0sR0FBRyxJQUFJLENBQUM7aUJBQ2Q7YUFDRCxRQUFRLE1BQU0sRUFBRTtZQUVqQixJQUFJLElBQUksQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO2dCQUN0QixTQUFTO2FBQ1Q7WUFFRCxJQUFJLFNBQVMsRUFBRTtnQkFDZCxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUU7b0JBQ3RCLFNBQVMsR0FBRyxLQUFLLENBQUM7aUJBQ2xCO2dCQUNELEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxTQUFTLENBQUMsSUFBSSxFQUFFLFVBQVUsR0FBRyxvQkFBb0IsQ0FBQyxHQUFHLElBQUksQ0FBQztnQkFDckUsU0FBUzthQUNUO1lBRUQsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFO2dCQUN0QixTQUFTLEdBQUcsSUFBSSxDQUFDO2dCQUNqQixvQkFBb0IsR0FBRyxNQUFNLEdBQUcsVUFBVSxDQUFDO2dCQUMzQyxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsU0FBUyxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsR0FBRyxJQUFJLENBQUM7Z0JBQzFDLFNBQVM7YUFDVDtZQUVELE1BQU0sR0FBRyxHQUFHLGdCQUFnQixDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ25DLElBQUksbUJBQW1CLEdBQUcsS0FBSyxDQUFDO1lBQ2hDLElBQUksb0JBQW9CLEdBQUcsS0FBSyxDQUFDO1lBQ2pDLElBQUksR0FBRyxHQUFHLENBQUMsRUFBRTtnQkFDWixJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUU7b0JBQ3RCLG1CQUFtQixHQUFHLElBQUksQ0FBQztpQkFDM0I7cUJBQU07b0JBQ04sb0JBQW9CLEdBQUcsSUFBSSxDQUFDO2lCQUM1QjthQUNEO2lCQUFNLElBQUksR0FBRyxLQUFLLENBQUMsRUFBRTtnQkFDckIsb0JBQW9CLEdBQUcsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUN4QztZQUNELElBQUksaUJBQWlCLEdBQUcsS0FBSyxDQUFDO1lBQzlCLElBQUksR0FBRyxHQUFHLENBQUMsRUFBRTtnQkFDWixpQkFBaUIsR0FBRyxJQUFJLENBQUM7YUFDekI7aUJBQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxFQUFFO2dCQUNyQixpQkFBaUIsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO2FBQ3BDO1lBRUQsSUFBSSxvQkFBb0IsRUFBRTtnQkFDekIsTUFBTSxFQUFFLENBQUM7YUFDVDtZQUVELEtBQUssQ0FBQyxDQUFDLENBQUMsR0FBRyxTQUFTLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxHQUFHLElBQUksQ0FBQztZQUUxQyxJQUFJLG1CQUFtQixFQUFFO2dCQUN4QixNQUFNLEVBQUUsQ0FBQzthQUNUO1lBQ0QsSUFBSSxpQkFBaUIsRUFBRTtnQkFDdEIsTUFBTSxFQUFFLENBQUM7YUFDVDtTQUNEO1FBQ0QsT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3pCLENBQUM7SUFFRCxTQUFTLGVBQWUsQ0FBQyxPQUE4QjtRQUN0RCxpRUFBaUU7UUFDakUsNERBQTREO1FBQzVELE9BQVEsRUFBVSxDQUFDLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUN6RCxDQUFDO0lBRUQsU0FBUyxVQUFVLENBQUMsSUFBWSxFQUFFLEtBQXNCO1FBQ3ZELDhDQUE4QztRQUM5QyxJQUFJLE1BQU0sR0FBRyxJQUFJLENBQUM7UUFDbEIsS0FBSyxJQUFJLENBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQzNDLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN4QixNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO1lBQ2hELE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztZQUNsRSxNQUFNLEdBQUcsSUFBSSxHQUFHLE1BQU0sQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1NBQ3RDO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDZixDQUFDO0FBQ0YsQ0FBQztBQUVELFNBQVMsNEJBQTRCLENBQUMsVUFBOEI7SUFDbkUsT0FBTyxDQUFDLEdBQVcsRUFBRSxFQUFFO1FBQ3RCLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxVQUFVLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFO1lBQzNDLEdBQUcsR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztTQUN0RDtRQUNELE9BQU8sR0FBRyxDQUFDO0lBQ1osQ0FBQyxDQUFDO0FBQ0gsQ0FBQztBQUVELFNBQVMsY0FBYyxDQUFDLElBQVk7SUFDbkMsSUFBSSxHQUFHLElBQUksSUFBSSxFQUFFLENBQUM7SUFDbEIsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUN0QyxNQUFNLFVBQVUsR0FBdUIsRUFBRSxDQUFDO0lBQzFDLGFBQWEsQ0FBQyxPQUFPLENBQUMsQ0FBQyxZQUFZLEVBQUUsRUFBRTtRQUN0QyxJQUFJLFlBQVksQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1lBQzlCLE9BQU87U0FDUDtRQUNELE1BQU0sTUFBTSxHQUFHLFlBQVksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDeEMsSUFBSSxPQUFPLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3hCLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUU3QixPQUFPLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyx5Q0FBeUMsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUM3RSxPQUFPLEdBQUcsS0FBSyxHQUFHLE9BQU8sR0FBRyxLQUFLLENBQUM7UUFDbEMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRSxVQUFVLENBQUMsQ0FBQyxDQUFDO0lBQ3pELENBQUMsQ0FBQyxDQUFDO0lBRUgsT0FBTyw0QkFBNEIsQ0FBQyxVQUFVLENBQUMsQ0FBQztBQUNqRCxDQUFDO0FBYUQsU0FBUyx1QkFBdUIsQ0FBQyxFQUErQixFQUFFLE1BQWMsRUFBRSxnQkFBa0M7SUFDbkgsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7SUFFakQsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNqQyxNQUFNLE1BQU0sR0FBYSxFQUFFLENBQUM7SUFFNUIsSUFBSSxZQUFZLEdBQUcsQ0FBQyxDQUFDO0lBQ3JCLE1BQU0sWUFBWSxHQUFhLEVBQUUsQ0FBQztJQUNsQyxNQUFNLEtBQUssR0FBYSxFQUFFLENBQUM7SUFFM0IsSUFBSSxNQUFNLEdBQUcsS0FBSyxDQUFDO0lBRW5CLEtBQUssQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDMUIsS0FBSyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUUxQixNQUFNLG1CQUFtQixHQUFHLENBQUMsUUFBZ0IsRUFBRSxFQUFFO1FBQ2hELE1BQU0sVUFBVSxHQUFHLEdBQUcsR0FBRyxDQUFDLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFDMUMsWUFBWSxDQUFDLElBQUksQ0FBQyxlQUFlLFVBQVUsWUFBWSxRQUFRLENBQUMsT0FBTyxDQUFDLFVBQVUsRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDN0YsT0FBTyxVQUFVLENBQUM7SUFDbkIsQ0FBQyxDQUFDO0lBRUYsTUFBTSxLQUFLLEdBQWlCLEVBQUUsQ0FBQztJQUMvQixJQUFJLE9BQU8sR0FBa0IsSUFBSSxDQUFDO0lBRWxDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUU7UUFFcEIsSUFBSSxNQUFNLEVBQUU7WUFDWCxPQUFPO1NBQ1A7UUFFRCxNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLGtCQUFrQixDQUFDLENBQUM7UUFDMUMsSUFBSSxFQUFFLEVBQUU7WUFDUCxPQUFPLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1NBQ2hCO1FBRUQsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQywwQ0FBMEMsQ0FBQyxDQUFDO1FBQ2xFLElBQUksRUFBRSxFQUFFO1lBQ1AsTUFBTSxRQUFRLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3ZCLE1BQU0sVUFBVSxHQUFHLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQzlDLElBQUksQ0FBQyxVQUFVLEVBQUU7Z0JBQ2hCLE1BQU0sQ0FBQyxrQkFBa0IsSUFBSSxFQUFFLENBQUMsQ0FBQztnQkFDakMsTUFBTSxDQUFDLGVBQWUsUUFBUSxFQUFFLENBQUMsQ0FBQztnQkFDbEMsTUFBTSxHQUFHLElBQUksQ0FBQztnQkFDZCxPQUFPO2FBQ1A7WUFFRCxNQUFNLFVBQVUsR0FBRyxtQkFBbUIsQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUVqRCxNQUFNLFFBQVEsR0FBRyxjQUFjLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFdkMsTUFBTSxTQUFTLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNuQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQzlCLFFBQVEsR0FBRyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQzNCLElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUU7b0JBQzFCLE9BQU87aUJBQ1A7Z0JBQ0QsTUFBTSxXQUFXLEdBQUcsc0JBQXNCLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxRQUFRLENBQUMsQ0FBQztnQkFDckUsSUFBSSxDQUFDLFdBQVcsRUFBRTtvQkFDakIsTUFBTSxDQUFDLGtCQUFrQixJQUFJLEVBQUUsQ0FBQyxDQUFDO29CQUNqQyxNQUFNLENBQUMsZUFBZSxRQUFRLEVBQUUsQ0FBQyxDQUFDO29CQUNsQyxNQUFNLEdBQUcsSUFBSSxDQUFDO29CQUNkLE9BQU87aUJBQ1A7Z0JBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsa0NBQWtDLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbEgsQ0FBQyxDQUFDLENBQUM7WUFDSCxPQUFPO1NBQ1A7UUFFRCxNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLDZDQUE2QyxDQUFDLENBQUM7UUFDckUsSUFBSSxFQUFFLEVBQUU7WUFDUCxNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDdkIsTUFBTSxVQUFVLEdBQUcsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDOUMsSUFBSSxDQUFDLFVBQVUsRUFBRTtnQkFDaEIsTUFBTSxDQUFDLGtCQUFrQixJQUFJLEVBQUUsQ0FBQyxDQUFDO2dCQUNqQyxNQUFNLENBQUMsZUFBZSxRQUFRLEVBQUUsQ0FBQyxDQUFDO2dCQUNsQyxNQUFNLEdBQUcsSUFBSSxDQUFDO2dCQUNkLE9BQU87YUFDUDtZQUVELE1BQU0sVUFBVSxHQUFHLG1CQUFtQixDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBRWpELE1BQU0sUUFBUSxHQUFHLGNBQWMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUV2QyxNQUFNLFNBQVMsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ25DLE1BQU0saUJBQWlCLEdBQW9DLEVBQUUsQ0FBQztZQUM5RCxNQUFNLGlCQUFpQixHQUFhLEVBQUUsQ0FBQztZQUN2QyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQzlCLFFBQVEsR0FBRyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQzNCLElBQUksUUFBUSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUU7b0JBQzFCLE9BQU87aUJBQ1A7Z0JBQ0QsaUJBQWlCLENBQUMsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDO2dCQUNuQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7WUFDbEMsQ0FBQyxDQUFDLENBQUM7WUFFSCwwQkFBMEIsQ0FBQyxFQUFFLEVBQUUsVUFBVSxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsV0FBVyxFQUFFLEVBQUU7Z0JBQ2xFLElBQUksYUFBYSxDQUFDLEVBQUUsRUFBRSxXQUFXLENBQUMsSUFBSSxXQUFXLENBQUMsSUFBSSxFQUFFO29CQUN2RCxJQUFJLGlCQUFpQixDQUFDLFdBQVcsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUU7d0JBQzdDLE9BQU87cUJBQ1A7aUJBQ0Q7cUJBQU07b0JBQ04sK0JBQStCO29CQUMvQixNQUFNLFFBQVEsR0FBRyxXQUFXLENBQUMsVUFBVSxFQUFFLFdBQVcsQ0FBQyxDQUFDO29CQUN0RCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsaUJBQWlCLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFO3dCQUNsRCxJQUFJLFFBQVEsQ0FBQyxPQUFPLENBQUMsaUJBQWlCLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUU7NEJBQ2hELE9BQU87eUJBQ1A7cUJBQ0Q7aUJBQ0Q7Z0JBQ0QsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsa0NBQWtDLENBQUMsRUFBRSxFQUFFLFVBQVUsRUFBRSxXQUFXLEVBQUUsVUFBVSxFQUFFLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbEgsQ0FBQyxDQUFDLENBQUM7WUFDSCxPQUFPO1NBQ1A7UUFFRCxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ25CLENBQUMsQ0FBQyxDQUFDO0lBRUgsSUFBSSxNQUFNLEVBQUU7UUFDWCxPQUFPLElBQUksQ0FBQztLQUNaO0lBRUQsSUFBSSxPQUFPLEtBQUssSUFBSSxFQUFFO1FBQ3JCLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDYixNQUFNLENBQUMsZ0dBQWdHLENBQUMsQ0FBQztTQUN6RzthQUFNO1lBQ04sTUFBTSxDQUFDLHNEQUFzRCxPQUFPLDRCQUE0QixJQUFJLEdBQUcsQ0FBQyxDQUFDO1NBQ3pHO1FBQ0QsT0FBTyxJQUFJLENBQUM7S0FDWjtJQUVELElBQUksU0FBUyxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbEMsU0FBUyxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMsVUFBVSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ2pELFNBQVMsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLFdBQVcsRUFBRSxTQUFTLENBQUMsQ0FBQztJQUN0RCxTQUFTLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDckQsU0FBUyxHQUFHLE1BQU0sQ0FBQyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQ3hDLFNBQVMsR0FBRyxTQUFTLENBQUMsS0FBSyxDQUFDLFlBQVksQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUVyRCxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRSxFQUFFO1FBQ3JCLElBQUksRUFBRSxDQUFDLFFBQVEsR0FBRyxFQUFFLENBQUMsUUFBUSxFQUFFO1lBQzlCLE9BQU8sQ0FBQyxDQUFDLENBQUM7U0FDVjtRQUNELElBQUksRUFBRSxDQUFDLFFBQVEsR0FBRyxFQUFFLENBQUMsUUFBUSxFQUFFO1lBQzlCLE9BQU8sQ0FBQyxDQUFDO1NBQ1Q7UUFDRCxPQUFPLENBQUMsQ0FBQztJQUNWLENBQUMsQ0FBQyxDQUFDO0lBRUgsSUFBSSxXQUFXLEdBQUc7UUFDakIsaUdBQWlHO1FBQ2pHLCtEQUErRDtRQUMvRCxrR0FBa0c7UUFDbEcsa0dBQWtHO1FBQ2xHLEVBQUU7UUFDRixvREFBb0Q7UUFDcEQsRUFBRTtLQUNGLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDNUMsV0FBVyxHQUFHLFdBQVcsQ0FBQyxLQUFLLENBQUMsWUFBWSxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3pELFdBQVcsR0FBRyxNQUFNLENBQUMsRUFBRSxFQUFFLFdBQVcsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUM1QyxXQUFXLEdBQUcsV0FBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFekQsT0FBTztRQUNOLE1BQU0sRUFBRSxTQUFTO1FBQ2pCLFlBQVksRUFBRSxHQUFHLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRTtRQUNqRSxLQUFLLEVBQUUsV0FBVztLQUNsQixDQUFDO0FBQ0gsQ0FBQztBQVVELFNBQVMsSUFBSSxDQUFDLEVBQStCLEVBQUUsZ0JBQWtDO0lBQ2hGLE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsbUJBQVcsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQ3ZELE1BQU0sQ0FBQyxHQUFHLHVCQUF1QixDQUFDLEVBQUUsRUFBRSxNQUFNLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQztJQUNoRSxJQUFJLENBQUMsQ0FBQyxFQUFFO1FBQ1AsT0FBTyxJQUFJLENBQUM7S0FDWjtJQUVELE1BQU0sTUFBTSxHQUFHLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDeEIsTUFBTSxZQUFZLEdBQUcsQ0FBQyxDQUFDLFlBQVksQ0FBQztJQUNwQyxNQUFNLEtBQUssR0FBRyxDQUFDLENBQUMsS0FBSyxDQUFDO0lBRXRCLE1BQU0sY0FBYyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztJQUNwRSxNQUFNLEdBQUcsR0FBRyxjQUFjLENBQUMsT0FBTyxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUNuRCxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsT0FBTyxDQUFDLFFBQVEsRUFBRSxJQUFJLENBQUMsQ0FBQztJQUM3QyxNQUFNLFNBQVMsR0FBRyxDQUFDLEdBQUcsS0FBSyxLQUFLLENBQUMsQ0FBQztJQUVsQyxPQUFPO1FBQ04sT0FBTyxFQUFFLE1BQU07UUFDZixZQUFZLEVBQUUsWUFBWTtRQUMxQixLQUFLLEVBQUUsS0FBSztRQUNaLFFBQVEsRUFBRSxnQkFBZ0I7UUFDMUIsU0FBUztLQUNULENBQUM7QUFDSCxDQUFDO0FBRUQsTUFBYSxVQUFVO0lBQ2YsVUFBVSxDQUFDLFFBQWdCO1FBQ2pDLE9BQU8sRUFBRSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBQ00sUUFBUSxDQUFDLFFBQWdCO1FBQy9CLE9BQU8sRUFBRSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUM5QixDQUFDO0lBQ00sWUFBWSxDQUFDLFNBQWlCLEVBQUUsUUFBZ0I7UUFDdEQsT0FBTyxFQUFFLENBQUMsWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2xDLENBQUM7Q0FDRDtBQVZELGdDQVVDO0FBRUQsTUFBTSxVQUFVO0lBRUU7SUFDQTtJQUZqQixZQUNpQixVQUF5QixFQUN6QixLQUFhO1FBRGIsZUFBVSxHQUFWLFVBQVUsQ0FBZTtRQUN6QixVQUFLLEdBQUwsS0FBSyxDQUFRO0lBQzFCLENBQUM7Q0FDTDtBQUVELE1BQWEsbUJBQW1CO0lBS0Y7SUFIYixFQUFFLENBQThCO0lBQ3hDLGdCQUFnQixDQUE0QztJQUVwRSxZQUE2QixXQUF1QjtRQUF2QixnQkFBVyxHQUFYLFdBQVcsQ0FBWTtRQUNuRCxJQUFJLENBQUMsRUFBRSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQWdDLENBQUM7UUFDL0QsSUFBSSxDQUFDLGdCQUFnQixHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDN0MsQ0FBQztJQUVNLGVBQWUsQ0FBQyxRQUFnQjtRQUN0QyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDO0lBQ3hDLENBQUM7SUFFTSx3QkFBd0IsQ0FBQyxRQUFnQjtRQUMvQyxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsRUFBRTtZQUNwQyxvRkFBb0Y7WUFDcEYsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsQ0FBQztZQUM3QyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDbEUsSUFBSSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxDQUFFLENBQUMsS0FBSyxLQUFLLEtBQUssRUFBRTtnQkFDckQsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxHQUFHLElBQUksQ0FBQzthQUN2QztTQUNEO1FBQ0QsSUFBSSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsRUFBRTtZQUNyQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLEdBQUcsSUFBSSxDQUFDLHlCQUF5QixDQUFDLFFBQVEsQ0FBQyxDQUFDO1NBQzNFO1FBQ0QsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUUsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUM3RixDQUFDO0lBRU8sWUFBWSxDQUFDLFFBQWdCO1FBQ3BDLElBQUksVUFBVSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRTtZQUM5QixPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1NBQ2hDO1FBQ0QsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLFFBQVEsS0FBSyxDQUFDLENBQUM7SUFDekMsQ0FBQztJQUVPLHlCQUF5QixDQUFDLFFBQWdCO1FBQ2pELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDN0MsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxFQUFFO1lBQzNDLE9BQU8sSUFBSSxDQUFDO1NBQ1o7UUFDRCxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDbEUsSUFBSSxVQUFVLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxFQUFFO1lBQzlCLGdEQUFnRDtZQUNoRCxNQUFNLFlBQVksR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDbEYsT0FBTyxJQUFJLFVBQVUsQ0FDcEIsSUFBSSxDQUFDLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsWUFBWSxFQUFFLElBQUksQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxFQUMxRSxLQUFLLENBQ0wsQ0FBQztTQUNGO1FBQ0QsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxZQUFZLENBQUMsUUFBUSxFQUFFLFFBQVEsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ2xGLE1BQU0sT0FBTyxHQUFhO1lBQ3pCLFNBQVMsRUFBRSxZQUFZO1NBQ3ZCLENBQUM7UUFDRixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsRUFBRSxDQUFDLHFCQUFxQixDQUFDLElBQUksNkJBQTZCLENBQUMsSUFBSSxDQUFDLEVBQUUsRUFBRSxFQUFFLEVBQUUsT0FBTyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7UUFDM0csTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLGFBQWEsQ0FBQyxTQUFTLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDOUUsT0FBTyxJQUFJLFVBQVUsQ0FDcEIsSUFBSSxDQUFDLEVBQUUsQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsSUFBSSxFQUFFLElBQUksQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxFQUNsRSxLQUFLLENBQ0wsQ0FBQztJQUNILENBQUM7Q0FDRDtBQTdERCxrREE2REM7QUFFRCxTQUFnQixJQUFJLENBQUMsUUFBNkI7SUFDakQsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLFFBQWdCLEVBQUUsRUFBRSxDQUFDLFFBQVEsQ0FBQyx3QkFBd0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUMzRixPQUFPLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxFQUFFLGdCQUFnQixDQUFDLENBQUM7QUFDNUMsQ0FBQztBQUhELG9CQUdDO0FBUUQsTUFBTSw2QkFBNkI7SUFFakIsR0FBRyxDQUE4QjtJQUNqQyxLQUFLLENBQVU7SUFDZixNQUFNLENBQVc7SUFDakIsZ0JBQWdCLENBQXFCO0lBRXRELFlBQVksRUFBK0IsRUFBRSxJQUFhLEVBQUUsS0FBZSxFQUFFLGVBQW1DO1FBQy9HLElBQUksQ0FBQyxHQUFHLEdBQUcsRUFBRSxDQUFDO1FBQ2QsSUFBSSxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUM7UUFDbEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxLQUFLLENBQUM7UUFDcEIsSUFBSSxDQUFDLGdCQUFnQixHQUFHLGVBQWUsQ0FBQztJQUN6QyxDQUFDO0lBRUQsNENBQTRDO0lBRTVDLHNCQUFzQjtRQUNyQixPQUFPLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQztJQUM5QixDQUFDO0lBQ0Qsa0JBQWtCO1FBQ2pCLE9BQU8sQ0FDTCxFQUFlO2FBQ2QsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO2FBQy9CLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUNsQyxDQUFDO0lBQ0gsQ0FBQztJQUNELGdCQUFnQixDQUFDLFNBQWlCO1FBQ2pDLE9BQU8sR0FBRyxDQUFDO0lBQ1osQ0FBQztJQUNELGlCQUFpQjtRQUNoQixPQUFPLEdBQUcsQ0FBQztJQUNaLENBQUM7SUFDRCxpQkFBaUIsQ0FBQyxRQUFnQjtRQUNqQyxJQUFJLElBQUksQ0FBQyxNQUFNLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxFQUFFO1lBQ3pDLE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQztTQUNqRTthQUFNLElBQUksSUFBSSxDQUFDLEtBQUssQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLEVBQUU7WUFDL0MsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO1NBQ2hFO2FBQU07WUFDTixPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsY0FBYyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsQ0FBQztTQUM5QztJQUNGLENBQUM7SUFDRCxhQUFhLENBQUMsU0FBaUI7UUFDOUIsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7SUFDL0IsQ0FBQztJQUNELG1CQUFtQjtRQUNsQixPQUFPLEVBQUUsQ0FBQztJQUNYLENBQUM7SUFDRCxxQkFBcUIsQ0FBQyxRQUE0QjtRQUNqRCxPQUFPLGdCQUFnQixDQUFDO0lBQ3pCLENBQUM7SUFDRCxvQkFBb0IsQ0FBQyxRQUFnQjtRQUNwQyxPQUFPLFFBQVEsS0FBSyxJQUFJLENBQUMscUJBQXFCLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLENBQUM7SUFDdkUsQ0FBQztJQUNELFFBQVEsQ0FBQyxJQUFZLEVBQUUsU0FBa0I7UUFDeEMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDOUMsQ0FBQztJQUNELFVBQVUsQ0FBQyxJQUFZO1FBQ3RCLE9BQU8sSUFBSSxJQUFJLElBQUksQ0FBQyxNQUFNLElBQUksSUFBSSxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUM7SUFDbEQsQ0FBQztDQUNEO0FBRUQsU0FBZ0IsT0FBTztJQUN0QixNQUFNLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxtQkFBbUIsQ0FBQyxJQUFJLFVBQVUsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUMxRCxJQUFJLENBQUMsQ0FBQyxFQUFFO1FBQ1AsTUFBTSxJQUFJLEtBQUssQ0FBQyxnREFBZ0QsQ0FBQyxDQUFDO0tBQ2xFO0lBQ0QsT0FBTyxDQUFDLENBQUM7QUFDVixDQUFDO0FBTkQsMEJBTUMifQ== \ No newline at end of file diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index 4f6fce7a42..b0409b20f2 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -115,7 +115,7 @@ function getNodeText(sourceFile: ts.SourceFile, node: { pos: number; end: number return sourceFile.getFullText().substring(node.pos, node.end); } -function hasModifier(modifiers: ts.NodeArray | undefined, kind: ts.SyntaxKind): boolean { +function hasModifier(modifiers: readonly ts.ModifierLike[] | undefined, kind: ts.SyntaxKind): boolean { if (modifiers) { for (let i = 0; i < modifiers.length; i++) { const mod = modifiers[i]; @@ -128,7 +128,10 @@ function hasModifier(modifiers: ts.NodeArray | undefined, kind: } function isStatic(ts: typeof import('typescript'), member: ts.ClassElement | ts.TypeElement): boolean { - return hasModifier(member.modifiers, ts.SyntaxKind.StaticKeyword); + if (ts.canHaveModifiers(member)) { + return hasModifier(ts.getModifiers(member), ts.SyntaxKind.StaticKeyword); + } + return false; } function isDefaultExport(ts: typeof import('typescript'), declaration: ts.InterfaceDeclaration | ts.ClassDeclaration): boolean { diff --git a/build/lib/nls.js b/build/lib/nls.js index 08eba891ae..9cb8557395 100644 --- a/build/lib/nls.js +++ b/build/lib/nls.js @@ -95,18 +95,22 @@ var _nls; return { line: position.line - 1, character: position.column }; } class SingleFileServiceHost { + options; + filename; + file; + lib; constructor(ts, options, filename, contents) { this.options = options; this.filename = filename; - this.getCompilationSettings = () => this.options; - this.getScriptFileNames = () => [this.filename]; - this.getScriptVersion = () => '1'; - this.getScriptSnapshot = (name) => name === this.filename ? this.file : this.lib; - this.getCurrentDirectory = () => ''; - this.getDefaultLibFileName = () => 'lib.d.ts'; this.file = ts.ScriptSnapshot.fromString(contents); this.lib = ts.ScriptSnapshot.fromString(''); } + getCompilationSettings = () => this.options; + getScriptFileNames = () => [this.filename]; + getScriptVersion = () => '1'; + getScriptSnapshot = (name) => name === this.filename ? this.file : this.lib; + getCurrentDirectory = () => ''; + getDefaultLibFileName = () => 'lib.d.ts'; readFile(path, _encoding) { if (path === this.filename) { return this.file.getText(0, this.file.getLength()); @@ -208,6 +212,8 @@ var _nls; }; } class TextModel { + lines; + lineEndings; constructor(contents) { const regex = /\r\n|\r|\n/g; let index = 0; @@ -355,3 +361,4 @@ var _nls; } _nls.patchFiles = patchFiles; })(_nls || (_nls = {})); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmxzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsibmxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBR2hHLGdDQUFnQztBQUNoQywrQ0FBK0M7QUFDL0MsOEJBQThCO0FBQzlCLGlDQUFpQztBQUNqQyw2QkFBOEI7QUFNOUIsSUFBSyxpQkFLSjtBQUxELFdBQUssaUJBQWlCO0lBQ3JCLHVEQUFHLENBQUE7SUFDSCwyRUFBYSxDQUFBO0lBQ2IscURBQUUsQ0FBQTtJQUNGLHlFQUFZLENBQUE7QUFDYixDQUFDLEVBTEksaUJBQWlCLEtBQWpCLGlCQUFpQixRQUtyQjtBQUVELFNBQVMsT0FBTyxDQUFDLEVBQStCLEVBQUUsSUFBYSxFQUFFLEVBQXdDO0lBQ3hHLE1BQU0sTUFBTSxHQUFjLEVBQUUsQ0FBQztJQUU3QixTQUFTLElBQUksQ0FBQyxJQUFhO1FBQzFCLE1BQU0sVUFBVSxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUU1QixJQUFJLFVBQVUsS0FBSyxpQkFBaUIsQ0FBQyxHQUFHLElBQUksVUFBVSxLQUFLLGlCQUFpQixDQUFDLGFBQWEsRUFBRTtZQUMzRixNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1NBQ2xCO1FBRUQsSUFBSSxVQUFVLEtBQUssaUJBQWlCLENBQUMsYUFBYSxJQUFJLFVBQVUsS0FBSyxpQkFBaUIsQ0FBQyxZQUFZLEVBQUU7WUFDcEcsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7U0FDNUI7SUFDRixDQUFDO0lBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ1gsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDO0FBRUQsU0FBUyxLQUFLLENBQW1CLE1BQVM7SUFDekMsTUFBTSxNQUFNLEdBQU0sRUFBRSxDQUFDO0lBQ3JCLEtBQUssTUFBTSxFQUFFLElBQUksTUFBTSxFQUFFO1FBQ3hCLE1BQU0sQ0FBQyxFQUFFLENB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 \ No newline at end of file diff --git a/build/lib/node.js b/build/lib/node.js index ace6c12c84..9d03698cbe 100644 --- a/build/lib/node.js +++ b/build/lib/node.js @@ -15,3 +15,4 @@ const arch = process.arch; const node = platform === 'win32' ? 'node.exe' : 'node'; const nodePath = path.join(root, '.build', 'node', `v${version}`, `${platform}-${arch}`, node); console.log(nodePath); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibm9kZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm5vZGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyw2QkFBNkI7QUFDN0IseUJBQXlCO0FBRXpCLE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0FBQ25ELE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRSxTQUFTLENBQUMsQ0FBQztBQUN4RCxNQUFNLE1BQU0sR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLFVBQVUsRUFBRSxNQUFNLENBQUMsQ0FBQztBQUNuRCxNQUFNLE9BQU8sR0FBRyx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFekQsTUFBTSxRQUFRLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQztBQUNsQyxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDO0FBRTFCLE1BQU0sSUFBSSxHQUFHLFFBQVEsS0FBSyxPQUFPLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDO0FBQ3hELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsSUFBSSxPQUFPLEVBQUUsRUFBRSxHQUFHLFFBQVEsSUFBSSxJQUFJLEVBQUUsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUUvRixPQUFPLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDIn0= \ No newline at end of file diff --git a/build/lib/optimize.js b/build/lib/optimize.js index f359028f8d..c1f52adf96 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -4,7 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.minifyTask = exports.optimizeTask = exports.loaderConfig = void 0; +exports.minifyTask = exports.optimizeTask = exports.optimizeLoaderTask = exports.loaderConfig = void 0; const es = require("event-stream"); const gulp = require("gulp"); const concat = require("gulp-concat"); @@ -83,7 +83,7 @@ function loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo) { files.push(new VinylFile({ path: 'fake2', base: '.', - contents: Buffer.from(`require.config(${JSON.stringify(externalLoaderInfo, undefined, 2)});`) + contents: Buffer.from(emitExternalLoaderInfo(externalLoaderInfo)) })); } for (const file of files) { @@ -93,6 +93,17 @@ function loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo) { })) .pipe(concat('vs/loader.js'))); } +function emitExternalLoaderInfo(externalLoaderInfo) { + const externalBaseUrl = externalLoaderInfo.baseUrl; + externalLoaderInfo.baseUrl = '$BASE_URL'; + // If defined, use the runtime configured baseUrl. + const code = ` +(function() { + const baseUrl = require.getConfig().baseUrl || ${JSON.stringify(externalBaseUrl)}; + require.config(${JSON.stringify(externalLoaderInfo, undefined, 2)}); +})();`; + return code.replace('"$BASE_URL"', 'baseUrl'); +} function toConcatStream(src, bundledFileHeader, sources, dest, fileContentMapper) { const useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest); // If a bundle ends up including in any of the sources our copyright, then @@ -137,56 +148,94 @@ const DEFAULT_FILE_HEADER = [ ' * Copyright (C) Microsoft Corporation. All rights reserved.', ' *--------------------------------------------------------*/' ].join('\n'); -function optimizeTask(opts) { +function optimizeAMDTask(opts) { const src = opts.src; const entryPoints = opts.entryPoints; const resources = opts.resources; const loaderConfig = opts.loaderConfig; const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER; - const bundleLoader = (typeof opts.bundleLoader === 'undefined' ? true : opts.bundleLoader); - const out = opts.out; const fileContentMapper = opts.fileContentMapper || ((contents, _path) => contents); - return function () { - const sourcemaps = require('gulp-sourcemaps'); - const bundlesStream = es.through(); // this stream will contain the bundled files - const resourcesStream = es.through(); // this stream will contain the resources - const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json - bundle.bundle(entryPoints, loaderConfig, function (err, result) { - if (err || !result) { - return bundlesStream.emit('error', JSON.stringify(err)); + const sourcemaps = require('gulp-sourcemaps'); + const bundlesStream = es.through(); // this stream will contain the bundled files + const resourcesStream = es.through(); // this stream will contain the resources + const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json + bundle.bundle(entryPoints, loaderConfig, function (err, result) { + if (err || !result) { + return bundlesStream.emit('error', JSON.stringify(err)); + } + toBundleStream(src, bundledFileHeader, result.files, fileContentMapper).pipe(bundlesStream); + // Remove css inlined resources + const filteredResources = resources.slice(); + result.cssInlinedResources.forEach(function (resource) { + if (process.env['VSCODE_BUILD_VERBOSE']) { + log('optimizer', 'excluding inlined: ' + resource); } - toBundleStream(src, bundledFileHeader, result.files, fileContentMapper).pipe(bundlesStream); - // Remove css inlined resources - const filteredResources = resources.slice(); - result.cssInlinedResources.forEach(function (resource) { - if (process.env['VSCODE_BUILD_VERBOSE']) { - log('optimizer', 'excluding inlined: ' + resource); - } - filteredResources.push('!' + resource); - }); - gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream); - const bundleInfoArray = []; - if (opts.bundleInfo) { - bundleInfoArray.push(new VinylFile({ - path: 'bundleInfo.json', - base: '.', - contents: Buffer.from(JSON.stringify(result.bundleData, null, '\t')) - })); - } - es.readArray(bundleInfoArray).pipe(bundleInfoStream); + filteredResources.push('!' + resource); }); - const result = es.merge(loader(src, bundledFileHeader, bundleLoader, opts.externalLoaderInfo), bundlesStream, resourcesStream, bundleInfoStream); - return result - .pipe(sourcemaps.write('./', { - sourceRoot: undefined, - addComment: true, - includeContent: true - })) - .pipe(opts.languages && opts.languages.length ? (0, i18n_1.processNlsFiles)({ - fileHeader: bundledFileHeader, - languages: opts.languages - }) : es.through()) - .pipe(gulp.dest(out)); + gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream); + const bundleInfoArray = []; + if (opts.bundleInfo) { + bundleInfoArray.push(new VinylFile({ + path: 'bundleInfo.json', + base: '.', + contents: Buffer.from(JSON.stringify(result.bundleData, null, '\t')) + })); + } + es.readArray(bundleInfoArray).pipe(bundleInfoStream); + }); + const result = es.merge(loader(src, bundledFileHeader, false, opts.externalLoaderInfo), bundlesStream, resourcesStream, bundleInfoStream); + return result + .pipe(sourcemaps.write('./', { + sourceRoot: undefined, + addComment: true, + includeContent: true + })) + .pipe(opts.languages && opts.languages.length ? (0, i18n_1.processNlsFiles)({ + fileHeader: bundledFileHeader, + languages: opts.languages + }) : es.through()); +} +function optimizeCommonJSTask(opts) { + const esbuild = require('esbuild'); + const src = opts.src; + const entryPoints = opts.entryPoints; + return gulp.src(entryPoints, { base: `${src}`, allowEmpty: true }) + .pipe(es.map((f, cb) => { + esbuild.build({ + entryPoints: [f.path], + bundle: true, + platform: opts.platform, + write: false, + external: opts.external + }).then(res => { + const jsFile = res.outputFiles[0]; + f.contents = Buffer.from(jsFile.contents); + cb(undefined, f); + }); + })); +} +function optimizeManualTask(options) { + const concatenations = options.map(opt => { + return gulp + .src(opt.src) + .pipe(concat(opt.out)); + }); + return es.merge(...concatenations); +} +function optimizeLoaderTask(src, out, bundleLoader, bundledFileHeader = '', externalLoaderInfo) { + return () => loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo).pipe(gulp.dest(out)); +} +exports.optimizeLoaderTask = optimizeLoaderTask; +function optimizeTask(opts) { + return function () { + const optimizers = [optimizeAMDTask(opts.amd)]; + if (opts.commonJS) { + optimizers.push(optimizeCommonJSTask(opts.commonJS)); + } + if (opts.manual) { + optimizers.push(optimizeManualTask(opts.manual)); + } + return es.merge(...optimizers).pipe(gulp.dest(opts.out)); }; } exports.optimizeTask = optimizeTask; @@ -213,9 +262,16 @@ function minifyTask(src, sourceMapBaseUrl) { }).then(res => { const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path)); const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path)); - f.contents = Buffer.from(jsFile.contents); - f.sourceMap = JSON.parse(sourceMapFile.text); - cb(undefined, f); + const contents = Buffer.from(jsFile.contents); + const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g); + if (unicodeMatch) { + cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`)); + } + else { + f.contents = contents; + f.sourceMap = JSON.parse(sourceMapFile.text); + cb(undefined, f); + } }, cb); }), jsFilter.restore, cssFilter, postcss([cssnano({ preset: 'default' })]), cssFilter.restore, svgFilter, // {{SQL CARBON EDIT}} - Disable the removeViewBox option because some SVG files ADS needs will not scale properly when the view box information is removed. @@ -237,3 +293,4 @@ function minifyTask(src, sourceMapBaseUrl) { }; } exports.minifyTask = minifyTask; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3B0aW1pemUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJvcHRpbWl6ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxtQ0FBbUM7QUFDbkMsNkJBQTZCO0FBQzdCLHNDQUFzQztBQUN0QyxzQ0FBc0M7QUFDdEMsc0NBQXNDO0FBQ3RDLDBDQUEwQztBQUMxQyw2QkFBNkI7QUFDN0IsNkJBQTZCO0FBQzdCLG1DQUFtQztBQUNuQyxtQ0FBbUM7QUFDbkMsaUNBQW1EO0FBQ25ELG1DQUE0QztBQUM1QywrQkFBK0I7QUFFL0IsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsT0FBTyxDQUFDLENBQUM7QUFFckQsU0FBUyxHQUFHLENBQUMsTUFBYyxFQUFFLE9BQWU7SUFDM0MsUUFBUSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsR0FBRyxHQUFHLE1BQU0sR0FBRyxHQUFHLENBQUMsRUFBRSxPQUFPLENBQUMsQ0FBQztBQUN4RCxDQUFDO0FBRUQsU0FBZ0IsWUFBWTtJQUMzQixNQUFNLE1BQU0sR0FBUTtRQUNuQixLQUFLLEVBQUU7WUFDTixJQUFJLEVBQUUsY0FBYztZQUNwQixLQUFLLEVBQUUsZUFBZTtZQUN0QixRQUFRLEVBQUUsUUFBUTtZQUNsQixRQUFRLEVBQUUsUUFBUSxDQUFDLHNCQUFzQjtTQUN6QztRQUNELGlCQUFpQixFQUFFLGFBQWEsQ0FBQywyQ0FBMkM7S0FDNUUsQ0FBQztJQUVGLE1BQU0sQ0FBQyxRQUFRLENBQUMsR0FBRyxFQUFFLGVBQWUsRUFBRSxJQUFJLEVBQUUsQ0FBQztJQUU3QyxPQUFPLE1BQU0sQ0FBQztBQUNmLENBQUM7QUFkRCxvQ0FjQztBQUVELE1BQU0sdUJBQXVCLEdBQUcsd0NBQXdDLENBQUM7QUFFekUsU0FBUyxZQUFZLENBQUMsR0FBVyxFQUFFLElBQVksRUFBRSxXQUErQjtJQUMvRSxPQUFPLENBQ04sSUFBSTtTQUNGLEdBQUcsQ0FB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 \ No newline at end of file diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index 16ba020199..a9b7d59dc5 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -56,7 +56,7 @@ function loaderPlugin(src: string, base: string, amdModuleId: string | undefined ); } -function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, externalLoaderInfo?: any): NodeJS.ReadWriteStream { +function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, externalLoaderInfo?: util.IExternalLoaderInfo): NodeJS.ReadWriteStream { let loaderStream = gulp.src(`${src}/vs/loader.js`, { base: `${src}` }); if (bundleLoader) { loaderStream = es.merge( @@ -97,7 +97,7 @@ function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, e files.push(new VinylFile({ path: 'fake2', base: '.', - contents: Buffer.from(`require.config(${JSON.stringify(externalLoaderInfo, undefined, 2)});`) + contents: Buffer.from(emitExternalLoaderInfo(externalLoaderInfo)) })); } for (const file of files) { @@ -109,6 +109,19 @@ function loader(src: string, bundledFileHeader: string, bundleLoader: boolean, e ); } +function emitExternalLoaderInfo(externalLoaderInfo: util.IExternalLoaderInfo): string { + const externalBaseUrl = externalLoaderInfo.baseUrl; + externalLoaderInfo.baseUrl = '$BASE_URL'; + + // If defined, use the runtime configured baseUrl. + const code = ` +(function() { + const baseUrl = require.getConfig().baseUrl || ${JSON.stringify(externalBaseUrl)}; + require.config(${JSON.stringify(externalLoaderInfo, undefined, 2)}); +})();`; + return code.replace('"$BASE_URL"', 'baseUrl'); +} + function toConcatStream(src: string, bundledFileHeader: string, sources: bundle.IFile[], dest: string, fileContentMapper: (contents: string, path: string) => string): NodeJS.ReadWriteStream { const useSourcemaps = /\.js$/.test(dest) && !/\.nls\.js$/.test(dest); @@ -155,7 +168,7 @@ function toBundleStream(src: string, bundledFileHeader: string, bundles: bundle. })); } -export interface IOptimizeTaskOpts { +export interface IOptimizeAMDTaskOpts { /** * The folder to read files from. */ @@ -172,7 +185,7 @@ export interface IOptimizeTaskOpts { /** * Additional info we append to the end of the loader */ - externalLoaderInfo?: any; + externalLoaderInfo?: util.IExternalLoaderInfo; /** * (true by default - append css and nls to loader) */ @@ -186,16 +199,12 @@ export interface IOptimizeTaskOpts { */ bundleInfo: boolean; /** - * (out folder name) - */ - out: string; - /** - * (out folder name) + * Language configuration. */ languages?: Language[]; /** * File contents interceptor - * @param contents The contens of the file + * @param contents The contents of the file * @param path The absolute file path, always using `/`, even on Windows */ fileContentMapper?: (contents: string, path: string) => string; @@ -207,67 +216,164 @@ const DEFAULT_FILE_HEADER = [ ' *--------------------------------------------------------*/' ].join('\n'); -export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream { +function optimizeAMDTask(opts: IOptimizeAMDTaskOpts): NodeJS.ReadWriteStream { const src = opts.src; const entryPoints = opts.entryPoints; const resources = opts.resources; const loaderConfig = opts.loaderConfig; const bundledFileHeader = opts.header || DEFAULT_FILE_HEADER; - const bundleLoader = (typeof opts.bundleLoader === 'undefined' ? true : opts.bundleLoader); - const out = opts.out; const fileContentMapper = opts.fileContentMapper || ((contents: string, _path: string) => contents); - return function () { - const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); + const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); - const bundlesStream = es.through(); // this stream will contain the bundled files - const resourcesStream = es.through(); // this stream will contain the resources - const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json + const bundlesStream = es.through(); // this stream will contain the bundled files + const resourcesStream = es.through(); // this stream will contain the resources + const bundleInfoStream = es.through(); // this stream will contain bundleInfo.json - bundle.bundle(entryPoints, loaderConfig, function (err, result) { - if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); } + bundle.bundle(entryPoints, loaderConfig, function (err, result) { + if (err || !result) { return bundlesStream.emit('error', JSON.stringify(err)); } - toBundleStream(src, bundledFileHeader, result.files, fileContentMapper).pipe(bundlesStream); + toBundleStream(src, bundledFileHeader, result.files, fileContentMapper).pipe(bundlesStream); - // Remove css inlined resources - const filteredResources = resources.slice(); - result.cssInlinedResources.forEach(function (resource) { - if (process.env['VSCODE_BUILD_VERBOSE']) { - log('optimizer', 'excluding inlined: ' + resource); - } - filteredResources.push('!' + resource); - }); - gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream); - - const bundleInfoArray: VinylFile[] = []; - if (opts.bundleInfo) { - bundleInfoArray.push(new VinylFile({ - path: 'bundleInfo.json', - base: '.', - contents: Buffer.from(JSON.stringify(result.bundleData, null, '\t')) - })); + // Remove css inlined resources + const filteredResources = resources.slice(); + result.cssInlinedResources.forEach(function (resource) { + if (process.env['VSCODE_BUILD_VERBOSE']) { + log('optimizer', 'excluding inlined: ' + resource); } - es.readArray(bundleInfoArray).pipe(bundleInfoStream); + filteredResources.push('!' + resource); }); + gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream); - const result = es.merge( - loader(src, bundledFileHeader, bundleLoader, opts.externalLoaderInfo), - bundlesStream, - resourcesStream, - bundleInfoStream - ); + const bundleInfoArray: VinylFile[] = []; + if (opts.bundleInfo) { + bundleInfoArray.push(new VinylFile({ + path: 'bundleInfo.json', + base: '.', + contents: Buffer.from(JSON.stringify(result.bundleData, null, '\t')) + })); + } + es.readArray(bundleInfoArray).pipe(bundleInfoStream); + }); - return result - .pipe(sourcemaps.write('./', { - sourceRoot: undefined, - addComment: true, - includeContent: true - })) - .pipe(opts.languages && opts.languages.length ? processNlsFiles({ - fileHeader: bundledFileHeader, - languages: opts.languages - }) : es.through()) - .pipe(gulp.dest(out)); + const result = es.merge( + loader(src, bundledFileHeader, false, opts.externalLoaderInfo), + bundlesStream, + resourcesStream, + bundleInfoStream + ); + + return result + .pipe(sourcemaps.write('./', { + sourceRoot: undefined, + addComment: true, + includeContent: true + })) + .pipe(opts.languages && opts.languages.length ? processNlsFiles({ + fileHeader: bundledFileHeader, + languages: opts.languages + }) : es.through()); +} + +export interface IOptimizeCommonJSTaskOpts { + /** + * The paths to consider for optimizing. + */ + entryPoints: string[]; + /** + * The folder to read files from. + */ + src: string; + /** + * ESBuild `platform` option: https://esbuild.github.io/api/#platform + */ + platform: 'browser' | 'node' | 'neutral'; + /** + * ESBuild `external` option: https://esbuild.github.io/api/#external + */ + external: string[]; +} + +function optimizeCommonJSTask(opts: IOptimizeCommonJSTaskOpts): NodeJS.ReadWriteStream { + const esbuild = require('esbuild') as typeof import('esbuild'); + + const src = opts.src; + const entryPoints = opts.entryPoints; + + return gulp.src(entryPoints, { base: `${src}`, allowEmpty: true }) + .pipe(es.map((f: any, cb) => { + esbuild.build({ + entryPoints: [f.path], + bundle: true, + platform: opts.platform, + write: false, + external: opts.external + }).then(res => { + const jsFile = res.outputFiles[0]; + f.contents = Buffer.from(jsFile.contents); + + cb(undefined, f); + }); + })); +} + +export interface IOptimizeManualTaskOpts { + /** + * The paths to consider for concatenation. The entries + * will be concatenated in the order they are provided. + */ + src: string[]; + /** + * Destination target to concatenate the entryPoints into. + */ + out: string; +} + +function optimizeManualTask(options: IOptimizeManualTaskOpts[]): NodeJS.ReadWriteStream { + const concatenations = options.map(opt => { + return gulp + .src(opt.src) + .pipe(concat(opt.out)); + }); + + return es.merge(...concatenations); +} + +export function optimizeLoaderTask(src: string, out: string, bundleLoader: boolean, bundledFileHeader = '', externalLoaderInfo?: util.IExternalLoaderInfo): () => NodeJS.ReadWriteStream { + return () => loader(src, bundledFileHeader, bundleLoader, externalLoaderInfo).pipe(gulp.dest(out)); +} + +export interface IOptimizeTaskOpts { + /** + * Destination folder for the optimized files. + */ + out: string; + /** + * Optimize AMD modules (using our AMD loader). + */ + amd: IOptimizeAMDTaskOpts; + /** + * Optimize CommonJS modules (using esbuild). + */ + commonJS?: IOptimizeCommonJSTaskOpts; + /** + * Optimize manually by concatenating files. + */ + manual?: IOptimizeManualTaskOpts[]; +} + +export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream { + return function () { + const optimizers = [optimizeAMDTask(opts.amd)]; + if (opts.commonJS) { + optimizers.push(optimizeCommonJSTask(opts.commonJS)); + } + + if (opts.manual) { + optimizers.push(optimizeManualTask(opts.manual)); + } + + return es.merge(...optimizers).pipe(gulp.dest(opts.out)); }; } @@ -302,10 +408,16 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => const jsFile = res.outputFiles.find(f => /\.js$/.test(f.path))!; const sourceMapFile = res.outputFiles.find(f => /\.js\.map$/.test(f.path))!; - f.contents = Buffer.from(jsFile.contents); - f.sourceMap = JSON.parse(sourceMapFile.text); + const contents = Buffer.from(jsFile.contents); + const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g); + if (unicodeMatch) { + cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`)); + } else { + f.contents = contents; + f.sourceMap = JSON.parse(sourceMapFile.text); - cb(undefined, f); + cb(undefined, f); + } }, cb); }), jsFilter.restore, diff --git a/build/lib/policies.js b/build/lib/policies.js index b1cc3661b9..732a91f3d7 100644 --- a/build/lib/policies.js +++ b/build/lib/policies.js @@ -13,6 +13,7 @@ const Parser = require("tree-sitter"); const node_fetch_1 = require("node-fetch"); const { typescript } = require('tree-sitter-typescript'); const product = require('../../product.json'); +const packageJson = require('../../package.json'); function isNlsString(value) { return value ? typeof value !== 'string' : false; } @@ -40,6 +41,12 @@ function renderADMLString(prefix, moduleName, nlsString, translations) { return `${value}`; } class BasePolicy { + policyType; + name; + category; + minimumVersion; + description; + moduleName; constructor(policyType, name, category, minimumVersion, description, moduleName) { this.policyType = policyType; this.name = name; @@ -95,6 +102,7 @@ class BooleanPolicy extends BasePolicy { } } class IntPolicy extends BasePolicy { + defaultValue; static from(name, category, minimumVersion, description, moduleName, settingNode) { const type = getStringProperty(settingNode, 'type'); if (type !== 'number') { @@ -139,6 +147,8 @@ class StringPolicy extends BasePolicy { } } class StringEnumPolicy extends BasePolicy { + enum_; + enumDescriptions; static from(name, category, minimumVersion, description, moduleName, settingNode) { const type = getStringProperty(settingNode, 'type'); if (type !== 'string') { @@ -408,16 +418,11 @@ const Languages = { 'tr': 'tr-tr', 'pl': 'pl-pl', }; -async function getLatestStableVersion(updateUrl) { - const res = await (0, node_fetch_1.default)(`${updateUrl}/api/update/darwin/stable/latest`); - const { name: version } = await res.json(); - return version; -} async function getSpecificNLS(resourceUrlTemplate, languageId, version) { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, - version, + version: `${version[0]}.${version[1]}.${version[2]}`, path: 'extension/translations/main.i18n.json' }; const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key]); @@ -428,23 +433,47 @@ async function getSpecificNLS(resourceUrlTemplate, languageId, version) { const { contents: result } = await res.json(); return result; } -function previousVersion(version) { - const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)$/.exec(version); - return `${major}.${parseInt(minor) - 1}.${patch}`; +function parseVersion(version) { + const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version); + return [parseInt(major), parseInt(minor), parseInt(patch)]; } -async function getNLS(resourceUrlTemplate, languageId, version) { - try { - return await getSpecificNLS(resourceUrlTemplate, languageId, version); +function compareVersions(a, b) { + if (a[0] !== b[0]) { + return a[0] - b[0]; } - catch (err) { - if (/\[404\]/.test(err.message)) { - console.warn(`Language pack ${languageId}@${version} is missing. Downloading previous version...`); - return await getSpecificNLS(resourceUrlTemplate, languageId, previousVersion(version)); - } - else { - throw err; - } + if (a[1] !== b[1]) { + return a[1] - b[1]; } + return a[2] - b[2]; +} +async function queryVersions(serviceUrl, languageId) { + const res = await (0, node_fetch_1.default)(`${serviceUrl}/extensionquery`, { + method: 'POST', + headers: { + 'Accept': 'application/json;api-version=3.0-preview.1', + 'Content-Type': 'application/json', + 'User-Agent': 'VS Code Build', + }, + body: JSON.stringify({ + filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }], + flags: 0x1 + }) + }); + if (res.status !== 200) { + throw new Error(`[${res.status}] Error querying for extension: ${languageId}`); + } + const result = await res.json(); + return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions); +} +async function getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) { + const versions = await queryVersions(extensionGalleryServiceUrl, languageId); + const nextMinor = [version[0], version[1] + 1, 0]; + const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0); + const latestCompatibleVersion = compatibleVersions.at(-1); // order is newest to oldest + if (!latestCompatibleVersion) { + throw new Error(`No compatible language pack found for ${languageId} for version ${version}`); + } + return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } async function parsePolicies() { const parser = new Parser(); @@ -461,9 +490,9 @@ async function parsePolicies() { return policies; } async function getTranslations() { - const updateUrl = product.updateUrl; - if (!updateUrl) { - console.warn(`Skipping policy localization: No 'updateUrl' found in 'product.json'.`); + const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl; + if (!extensionGalleryServiceUrl) { + console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`); return []; } const resourceUrlTemplate = product.extensionsGallery?.resourceUrlTemplate; @@ -471,9 +500,9 @@ async function getTranslations() { console.warn(`Skipping policy localization: No 'resourceUrlTemplate' found in 'product.json'.`); return []; } - const version = await getLatestStableVersion(updateUrl); + const version = parseVersion(packageJson.version); const languageIds = Object.keys(Languages); - return await Promise.all(languageIds.map(languageId => getNLS(resourceUrlTemplate, languageId, version) + return await Promise.all(languageIds.map(languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) .then(languageTranslations => ({ languageId, languageTranslations })))); } async function main() { @@ -495,3 +524,4 @@ if (require.main === module) { process.exit(1); }); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9saWNpZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJwb2xpY2llcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLGlEQUFzQztBQUN0QywyQkFBb0M7QUFDcEMsNkJBQTZCO0FBQzdCLGlDQUFpQztBQUNqQyw2Q0FBeUM7QUFDekMsc0NBQXNDO0FBQ3RDLDJDQUErQjtBQUMvQixNQUFNLEVBQUUsVUFBVSxFQUFFLEdBQUcsT0FBTyxDQUFDLHdCQUF3QixDQUFDLENBQUM7QUFDekQsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBQUM7QUFDOUMsTUFBTSxXQUFXLEdBQUcsT0FBTyxDQUFDLG9CQUFvQixDQUFDLENBQUM7QUFJbEQsU0FBUyxXQUFXLENBQUMsS0FBcUM7SUFDekQsT0FBTyxLQUFLLENBQUMsQ0FBQyxDQUFDLE9BQU8sS0FBSyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO0FBQ2xELENBQUM7QUFFRCxTQUFTLGFBQWEsQ0FBQyxLQUE2QjtJQUNuRCxPQUFPLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3pDLENBQUM7QUFFRCxTQUFTLGdCQUFnQixDQUFDLEtBQTZCO0lBQ3RELE9BQU8sS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0FBQ3pDLENBQUM7QUFPRCxJQUFLLFVBRUo7QUFGRCxXQUFLLFVBQVU7SUFDZCx1REFBVSxDQUFBO0FBQ1gsQ0FBQyxFQUZJLFVBQVUsS0FBVixVQUFVLFFBRWQ7QUFVRCxTQUFTLGdCQUFnQixDQUFDLE1BQWMsRUFBRSxVQUFrQixFQUFFLFNBQW9CLEVBQUUsWUFBbUM7SUFDdEgsSUFBSSxLQUF5QixDQUFDO0lBRTlCLElBQUksWUFBWSxFQUFF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 \ No newline at end of file diff --git a/build/lib/policies.ts b/build/lib/policies.ts index 487ef611fb..8cc954ce54 100644 --- a/build/lib/policies.ts +++ b/build/lib/policies.ts @@ -12,6 +12,7 @@ import * as Parser from 'tree-sitter'; import fetch from 'node-fetch'; const { typescript } = require('tree-sitter-typescript'); const product = require('../../product.json'); +const packageJson = require('../../package.json'); type NlsString = { value: string; nlsKey: string }; @@ -587,17 +588,13 @@ const Languages = { type LanguageTranslations = { [moduleName: string]: { [nlsKey: string]: string } }; type Translations = { languageId: string; languageTranslations: LanguageTranslations }[]; -async function getLatestStableVersion(updateUrl: string) { - const res = await fetch(`${updateUrl}/api/update/darwin/stable/latest`); - const { name: version } = await res.json() as { name: string }; - return version; -} +type Version = [number, number, number]; -async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: string) { +async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version) { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, - version, + version: `${version[0]}.${version[1]}.${version[2]}`, path: 'extension/translations/main.i18n.json' }; @@ -612,22 +609,50 @@ async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, v return result; } -function previousVersion(version: string): string { - const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)$/.exec(version)!; - return `${major}.${parseInt(minor) - 1}.${patch}`; +function parseVersion(version: string): Version { + const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!; + return [parseInt(major), parseInt(minor), parseInt(patch)]; } -async function getNLS(resourceUrlTemplate: string, languageId: string, version: string) { - try { - return await getSpecificNLS(resourceUrlTemplate, languageId, version); - } catch (err) { - if (/\[404\]/.test(err.message)) { - console.warn(`Language pack ${languageId}@${version} is missing. Downloading previous version...`); - return await getSpecificNLS(resourceUrlTemplate, languageId, previousVersion(version)); - } else { - throw err; - } +function compareVersions(a: Version, b: Version): number { + if (a[0] !== b[0]) { return a[0] - b[0]; } + if (a[1] !== b[1]) { return a[1] - b[1]; } + return a[2] - b[2]; +} + +async function queryVersions(serviceUrl: string, languageId: string): Promise { + const res = await fetch(`${serviceUrl}/extensionquery`, { + method: 'POST', + headers: { + 'Accept': 'application/json;api-version=3.0-preview.1', + 'Content-Type': 'application/json', + 'User-Agent': 'VS Code Build', + }, + body: JSON.stringify({ + filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }], + flags: 0x1 + }) + }); + + if (res.status !== 200) { + throw new Error(`[${res.status}] Error querying for extension: ${languageId}`); } + + const result = await res.json() as { results: [{ extensions: { versions: { version: string }[] }[] }] }; + return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions); +} + +async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: string, languageId: string, version: Version) { + const versions = await queryVersions(extensionGalleryServiceUrl, languageId); + const nextMinor: Version = [version[0], version[1] + 1, 0]; + const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0); + const latestCompatibleVersion = compatibleVersions.at(-1)!; // order is newest to oldest + + if (!latestCompatibleVersion) { + throw new Error(`No compatible language pack found for ${languageId} for version ${version}`); + } + + return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } async function parsePolicies(): Promise { @@ -649,10 +674,10 @@ async function parsePolicies(): Promise { } async function getTranslations(): Promise { - const updateUrl = product.updateUrl; + const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl; - if (!updateUrl) { - console.warn(`Skipping policy localization: No 'updateUrl' found in 'product.json'.`); + if (!extensionGalleryServiceUrl) { + console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`); return []; } @@ -663,11 +688,11 @@ async function getTranslations(): Promise { return []; } - const version = await getLatestStableVersion(updateUrl); + const version = parseVersion(packageJson.version); const languageIds = Object.keys(Languages); return await Promise.all(languageIds.map( - languageId => getNLS(resourceUrlTemplate, languageId, version) + languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) .then(languageTranslations => ({ languageId, languageTranslations })) )); } diff --git a/build/lib/preLaunch.js b/build/lib/preLaunch.js index 5eb804f843..a4e54ee639 100644 --- a/build/lib/preLaunch.js +++ b/build/lib/preLaunch.js @@ -53,3 +53,4 @@ if (require.main === module) { process.exit(1); }); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJlTGF1bmNoLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicHJlTGF1bmNoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7QUFFaEcsWUFBWTtBQUVaLDZCQUE2QjtBQUM3QixpREFBc0M7QUFDdEMsMkJBQW9DO0FBRXBDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxRQUFRLEtBQUssT0FBTyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQztBQUNoRSxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFFcEQsU0FBUyxVQUFVLENBQUMsT0FBZSxFQUFFLE9BQThCLEVBQUU7SUFDcEUsT0FBTyxJQUFJLE9BQU8sQ0FBTyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtRQUM1QyxNQUFNLEtBQUssR0FBRyxJQUFBLHFCQUFLLEVBQUMsT0FBTyxFQUFFLElBQUksRUFBRSxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsT0FBTyxDQUFDLEdBQUcsRUFBRSxDQUFDLENBQUM7UUFDekYsS0FBSyxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDbkUsS0FBSyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDM0IsQ0FBQyxDQUFDLENBQUM7QUFDSixDQUFDO0FBRUQsS0FBSyxVQUFVLE1BQU0sQ0FBQyxNQUFjO0lBQ25DLElBQUk7UUFDSCxNQUFNLGFBQUUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUMxQyxPQUFPLElBQUksQ0FBQztLQUNaO0lBQUMsTUFBTTtRQUNQLE9BQU8sS0FBSyxDQUFDO0tBQ2I7QUFDRixDQUFDO0FBRUQsS0FBSyxVQUFVLGlCQUFpQjtJQUMvQixJQUFJLENBQUMsQ0FBQyxNQUFNLE1BQU0sQ0FBQyxjQUFjLENBQUMsQ0FBQyxFQUFFO1FBQ3BDLE1BQU0sVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDO0tBQ3ZCO0FBQ0YsQ0FBQztBQUVELEtBQUssVUFBVSxXQUFXO0lBQ3pCLE1BQU0sVUFBVSxDQUFDLElBQUksRUFBRSxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUM7QUFDdEMsQ0FBQztBQUVELEtBQUssVUFBVSxjQUFjO0lBQzVCLElBQUksQ0FBQyxDQUFDLE1BQU0sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUU7UUFDM0IsTUFBTSxVQUFVLENBQUMsSUFBSSxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztLQUNwQztBQUNGLENBQUM7QUFFRCxLQUFLLFVBQVUsSUFBSTtJQUNsQixNQUFNLGlCQUFpQixFQUFFLENBQUM7SUFDMUIsTUFBTSxXQUFXLEVBQUUsQ0FBQztJQUNwQixNQUFNLGNBQWMsRUFBRSxDQUFDO0lBRXZCLDREQUE0RDtJQUM1RCxNQUFNLEVBQUUsb0JBQW9CLEVBQUUsR0FBRyxPQUFPLENBQUMscUJBQXFCLENBQUMsQ0FBQztJQUNoRSxNQUFNLG9CQUFvQixFQUFFLENBQUM7QUFDOUIsQ0FBQztBQUVELElBQUksT0FBTyxDQUFDLElBQUksS0FBSyxNQUFNLEVBQUU7SUFDNUIsSUFBSSxFQUFFLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1FBQ2xCLE9BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDbkIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNqQixDQUFDLENBQUMsQ0FBQztDQUNIIn0= \ No newline at end of file diff --git a/build/lib/reporter.js b/build/lib/reporter.js index 94df910908..3c061ff0da 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -6,18 +6,18 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.createReporter = void 0; const es = require("event-stream"); -const _ = require("underscore"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); const fs = require("fs"); const path = require("path"); class ErrorLog { + id; constructor(id) { this.id = id; - this.allErrors = []; - this.startTime = null; - this.count = 0; } + allErrors = []; + startTime = null; + count = 0; onStart() { if (this.count++ > 0) { return; @@ -32,7 +32,7 @@ class ErrorLog { this.log(); } log() { - const errors = _.flatten(this.allErrors); + const errors = this.allErrors.flat(); const seen = new Set(); errors.map(err => { if (!seen.has(err)) { @@ -100,3 +100,4 @@ function createReporter(id) { return result; } exports.createReporter = createReporter; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwb3J0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJyZXBvcnRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxtQ0FBbUM7QUFDbkMsc0NBQXNDO0FBQ3RDLDBDQUEwQztBQUMxQyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBRTdCLE1BQU0sUUFBUTtJQUNNO0lBQW5CLFlBQW1CLEVBQVU7UUFBVixPQUFFLEdBQUYsRUFBRSxDQUFRO0lBQzdCLENBQUM7SUFDRCxTQUFTLEdBQWUsRUFBRSxDQUFDO0lBQzNCLFNBQVMsR0FBa0IsSUFBSSxDQUFDO0lBQ2hDLEtBQUssR0FBRyxDQUFDLENBQUM7SUFFVixPQUFPO1FBQ04sSUFBSSxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxFQUFFO1lBQ3JCLE9BQU87U0FDUDtRQUVELElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUN0QyxRQUFRLENBQUMsWUFBWSxVQUFVLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxHQUFHLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsSUFBSSxJQUFJLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUM1RyxDQUFDO0lBRUQsS0FBSztRQUNKLElBQUksRUFBRSxJQUFJLENBQUMsS0FBSyxHQUFHLENBQUMsRUFBRTtZQUNyQixPQUFPO1NBQ1A7UUFFRCxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7SUFDWixDQUFDO0lBRUQsR0FBRztRQUNGLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDckMsTUFBTSxJQUFJLEdBQUcsSUFBSSxHQUFHLEVBQVUsQ0FBQztRQUUvQixNQUFNLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxF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 \ No newline at end of file diff --git a/build/lib/reporter.ts b/build/lib/reporter.ts index 93e300e473..743de413d3 100644 --- a/build/lib/reporter.ts +++ b/build/lib/reporter.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as es from 'event-stream'; -import * as _ from 'underscore'; import * as fancyLog from 'fancy-log'; import * as ansiColors from 'ansi-colors'; import * as fs from 'fs'; @@ -35,7 +34,7 @@ class ErrorLog { } log(): void { - const errors = _.flatten(this.allErrors); + const errors = this.allErrors.flat(); const seen = new Set(); errors.map(err => { diff --git a/build/lib/rollup.js b/build/lib/rollup.js index e83d01b22d..501791dfa3 100644 --- a/build/lib/rollup.js +++ b/build/lib/rollup.js @@ -89,3 +89,4 @@ function rollupAngular(root) { }); } exports.rollupAngular = rollupAngular; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm9sbHVwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsicm9sbHVwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLHlCQUF5QjtBQUN6QixpQ0FBaUM7QUFDakMsNkJBQTZCO0FBRTdCLDhCQUE4QjtBQUM5QixNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsNEJBQTRCLENBQUMsQ0FBQztBQUMxRCxNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsd0JBQXdCLENBQUMsQ0FBQztBQVVuRCxLQUFLLFVBQVUsWUFBWSxDQUFDLE9BQXVCO0lBQ2xELE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUM7SUFDdEMsSUFBSTtRQUNILE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUM7UUFDcEMsTUFBTSxlQUFlLEdBQUcsT0FBTyxDQUFDLGVBQWUsQ0FBQztRQUVoRCxNQUFNLEVBQUUsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLGVBQWUsRUFBRTtZQUN4QyxTQUFTLEVBQUUsSUFBSTtTQUNmLENBQUMsQ0FBQztRQUVILE1BQU0sY0FBYyxHQUFHLE9BQU8sQ0FBQyxjQUFjLENBQUM7UUFDOUMsTUFBTSxhQUFhLEdBQUcsR0FBRyxjQUFjLE1BQU0sQ0FBQztRQUM5QyxNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsUUFBUSxJQUFJLEVBQUUsQ0FBQztRQUV4QyxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLGVBQWUsRUFBRSxjQUFjLENBQUMsQ0FBQztRQUNyRSxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLGVBQWUsRUFBRSxhQUFhLENBQUMsQ0FBQztRQUVuRSxNQUFNLE1BQU0sR0FBRyxNQUFNLE1BQU0sQ0FBQyxNQUFNLENBQUM7WUFDbEMsS0FBSyxFQUFFLFNBQVM7WUFDaEIsT0FBTyxFQUFFO2dCQUNSLFdBQVcsRUFBRTtnQkFDYixRQUFRLEVBQUU7YUFDVjtZQUNE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 \ No newline at end of file diff --git a/build/lib/snapshotLoader.js b/build/lib/snapshotLoader.js index 70c27a87bf..781ce77118 100644 --- a/build/lib/snapshotLoader.js +++ b/build/lib/snapshotLoader.js @@ -53,3 +53,4 @@ var snaps; cp.execFileSync(mksnapshot, [wrappedInputFilepath, `--startup_blob`, startupBlobFilepath]); } })(snaps || (snaps = {})); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic25hcHNob3RMb2FkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJzbmFwc2hvdExvYWRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7QUFFaEcsSUFBVSxLQUFLLENBMkRkO0FBM0RELFdBQVUsS0FBSztJQUVkLE1BQU0sRUFBRSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN6QixNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDN0IsTUFBTSxFQUFFLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3pCLE1BQU0sRUFBRSxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUVwQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSwyQkFBMkIsT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPLENBQUMsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxZQUFZLEVBQUUsQ0FBQyxDQUFDO0lBQ3JJLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO0lBQzlDLE1BQU0sSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLGFBQWEsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBRW5FLEVBQUU7SUFDRixJQUFJLGNBQXNCLENBQUM7SUFDM0IsSUFBSSxtQkFBMkIsQ0FBQztJQUVoQyxRQUFRLE9BQU8sQ0FBQyxRQUFRLEVBQUU7UUFDekIsS0FBSyxRQUFRO1lBQ1osY0FBYyxHQUFHLGlCQUFpQixPQUFPLENBQUMsUUFBUSw4Q0FBOEMsQ0FBQztZQUNqRyxtQkFBbUIsR0FBRyxpQkFBaUIsT0FBTyxDQUFDLFFBQVEsbUZBQW1GLENBQUM7WUFDM0ksTUFBTTtRQUVQLEtBQUssT0FBTyxDQUFDO1FBQ2IsS0FBSyxPQUFPO1lBQ1gsY0FBYyxHQUFHLFVBQVUsT0FBTyxDQUFDLFFBQVEsSUFBSSxJQUFJLGlDQUFpQyxDQUFDO1lBQ3JGLG1CQUFtQixHQUFHLFVBQVUsT0FBTyxDQUFDLFFBQVEsSUFBSSxJQUFJLG9CQUFvQixDQUFDO1lBQzdFLE1BQU07UUFFUDtZQUNDLE1BQU0sSUFBSSxLQUFLLENBQUMsa0JBQWtCLENBQUMsQ0FBQztLQUNyQztJQUVELGNBQWMsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxXQUFXLEVBQUUsY0FBYyxDQUFDLENBQUM7SUFDbkUsbUJBQW1CLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsV0FBVyxFQUFFLG1CQUFtQixDQUFDLENBQUM7SUFFN0UsY0FBYyxDQUFDLGNBQWMsRUFBRSxtQkFBbUIsQ0FBQyxDQUFDO0lBRXBELFNBQVMsY0FBYyxDQUFDLGNBQXNCLEVBQUUsbUJBQTJCO1FBRTFFLE1BQU0sU0FBUyxHQUFHLEVBQUUsQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDbEQsTUFBTSxnQkFBZ0IsR0FBRzs7OztLQUl0QixTQUFTLENBQUMsUUFBUSxFQUFFOzs7Ozs7Ozs7R0FTdEIsQ0FBQztRQUNGLE1BQU0sb0JBQW9CLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLEVBQUUsbUJBQW1CLENBQUMsQ0FBQztRQUN6RSxPQUFPLENBQUMsR0FBRyxDQUFDLG9CQUFvQixDQUFDLENBQUM7UUFDbEMsRUFBRSxDQUFDLGFBQWEsQ0FBQyxvQkFBb0IsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO1FBRXpELEVBQUUsQ0FBQyxZQUFZLENBQUMsVUFBVSxFQUFFLENBQUMsb0JBQW9CLEVBQUUsZ0JBQWdCLEVBQUUsbUJBQW1CLENBQUMsQ0FBQyxDQUFDO0lBQzVGLENBQUM7QUFDRixDQUFDLEVBM0RTLEtBQUssS0FBTCxLQUFLLFFBMkRkIn0= \ No newline at end of file diff --git a/build/lib/standalone.js b/build/lib/standalone.js index d65e722132..89b718a976 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -180,7 +180,7 @@ function createESMSourcesAndResources2(options) { + relativePath + fileContents.substring(end + 1)); } - fileContents = fileContents.replace(/import ([a-zA-z0-9]+) = require\(('[^']+')\);/g, function (_, m1, m2) { + fileContents = fileContents.replace(/import ([a-zA-Z0-9]+) = require\(('[^']+')\);/g, function (_, m1, m2) { return `import * as ${m1} from ${m2};`; }); write(getDestAbsoluteFilePath(file), fileContents); @@ -318,3 +318,4 @@ function transportCSS(module, enqueue, write) { return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; } } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhbmRhbG9uZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInN0YW5kYWxvbmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixxQ0FBcUM7QUFFckMsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsUUFBUSxDQUFDLENBQUM7QUFDakQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFFNUMsTUFBTSxRQUFRLEdBQStCLEVBQUUsQ0FBQztBQUVoRCxTQUFTLFNBQVMsQ0FBQyxRQUFnQixFQUFFLFFBQXlCO0lBQzdELFNBQVMsVUFBVSxDQUFDLE9BQWU7UUFDbEMsSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLEVBQUU7WUFDdEIsT0FBTztTQUNQO1FBQ0QsUUFBUSxDQUFDLE9BQU8sQ0FBQyxHQUFHLElBQUksQ0FBQztRQUV6QixVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO1FBQ2xDLElBQUksRUFBRSxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsRUFBRTtZQUMzQixPQUFPO1NBQ1A7UUFDRCxFQUFFLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3ZCLENBQUM7SUFDRCxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQ25DLEVBQUUsQ0FBQyxhQUFhLENBQUMsUUFBUSxFQUFFLFFBQVEsQ0FBQyxDQUFDO0FBQ3RDLENBQUM7QUFFRCxTQUFnQixhQUFhLENBQUMsT0FBdUQ7SUFDcEYsTUFBTSxFQUFFLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBZ0MsQ0FBQztJQUVoRSxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLHNCQUFzQixDQUFDLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQ2hILElBQUksZUFBdUMsQ0FBQztJQUM1QyxJQUFJLFFBQVEsQ0FBQyxPQUFPLEVBQUU7UUFDckIsZUFBZSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsZUFBZSxFQUFFLFFBQVEsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUN6SSxPQUFPLFFBQVEsQ0FBQyxPQUFPLENBQUM7S0FDeEI7U0FBTTtRQUNOLGVBQWUsR0FBRyxRQUFRLENBQUMsZUFBZSxDQUFDO0tBQzNDO0lBQ0QsUUFBUSxDQUFDLGVBQWUsR0FBRyxlQUFlLENBQUM7SUFFM0MsZUFBZSxDQUFDLE1BQU0sR0FBRyxLQUFLLENBQUM7SUFDL0IsZUFBZSxDQUFDLGNBQWMsR0FBRyxLQUFLLENBQUM7SUFDdkMsZUFBZSxDQUFDLGtCQUFrQixHQUFHLEtBQUssQ0FBQztJQUMzQyxlQUFlLENBQUMsV0FBVyxHQUFHLEtBQUssQ0FBQztJQUNwQyxlQUFlLENBQUMsZ0JBQWdCLEdBQUcsRUFBRSxDQUFDLG9CQUFvQixDQUFDLE9BQU8sQ0FBQztJQUduRSxPQUFPLENBQUMsZUFBZSxHQUFHLGVBQWUsQ0FBQztJQUUxQyxPQUFPLENBQUMsR0FBRyxDQUFDLHVDQUF1QyxHQUFHLENBQUMsa0JBQWtCLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUVqRyxrRUFBa0U7SUFDbEUsT0FBTyxDQUFDLE9BQU8sR0FBYyxRQUFRLENBQUMsT0FBUSxDQUFDLE1BQU0sQ0FBQyxZQUFZLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQztJQUVyRyxvREFBb0Q7SUFDcEQsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxlQUFlLEVBQUUsS0FBSyxDQUFDLEVBQUU7UUFDbEQsT0FBTyxDQUFDLGVBQWUsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBWSxFQUFFLEVBQUU7WUFDdEQsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsMEJBQTBCLElBQUksYUFBYSxDQUFDLENBQUM7UUFDbkUsQ0FBQyxDQUFDLENBQUM7S0FDSDtJQUVELE1BQU0sTUFBTSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbEMsS0FBSyxNQUFNLFFBQVEsSUFBSSxNQUFNLEVBQUU7UUFDOUIsSUFBSSxNQUFNLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxFQUFFO1lBQ3BDLFNBQVMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLEVBQUUsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7U0FDbkU7S0FDRDtJQUNELE1BQU0sTUFBTSxHQUFvQyxFQUFFLENBQUM7SUFDbkQsTUFBTSxRQUFRLEdBQUcsQ0FBQyxRQUFnQixFQUFFLEVBQUU7UUFDckMsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLEVBQUU7WUFDckIsT0FBTztTQUNQO1FBQ0QsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLElBQUksQ0FBQztRQUN4QixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFDekQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ3RELFNBQVMsQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQzlDLENBQUMsQ0FBQztJQUNGLE1BQU0sZUFBZSxHQUFHLENBQUMsUUFBZ0IsRUFBRSxRQUF5QixFQUFFLEVBQUU7UUFDdkUsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsRUFBRSxRQUFRLENBQUMsRUFBRSxRQUFRLENBQUMsQ0FBQztJQUM1RCxDQUFDLENBQUM7SUFDRixLQUFLLE1BQU0sUUFBUSxJQUFJLE1BQU0sRUFBRTtRQUM5QixJQUFJLE1BQU0sQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLEVBQUU7WUFDcEMsTUFBTSxZQUFZLEdBQUcsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1lBQ3RDLE1BQU0sSUFBSSxHQUFHLEVBQUUsQ0FBQyxjQUFjLENBQUMsWUFBWSxDQUFDLENBQUM7WUFFN0MsS0FBSyxJQUFJLENBQUMsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRTtnQkFDeEQsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQztnQkFFeEQsSUFBSSxnQkFBd0IsQ0FBQztnQkFDN0IsSUFBSSxXQUFXLENBQUMsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEVBQUU7b0JBQ3ZDLGdCQUFnQixHQUFHLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxTQUFTLENBQUMsTUFBTSxDQUFDLEdBQUcsTUFBTSxDQUFDO2lCQUN0RTtxQkFBTTtvQkFDTixnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQztpQkFDcEM7Z0JBQ0QsSUFBSSxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsRUFBRTtvQkFDL0MsZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxFQUFFLGdCQUFnQixDQUFDLENBQUM7aUJBQ3ZFO2dCQUVELElBQUksUUFBUSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFO29CQUNwQyxZQUFZLENBQUMsZ0JBQWdCLEVBQUUsUUFBUSxFQUFFLGVBQWUsQ0FBQyxDQUFDO2lCQUMxRDtxQkFBTTtvQkFDTixJQUFJLEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsV0FBVyxFQUFFLGdCQUFnQixHQUFHLEtBQUssQ0FBQyxDQUFDLEVBQUU7d0JBQzVFLFFBQVEsQ0FBQyxnQkFBZ0IsR0FBRyxLQUFLLENBQUMsQ0FBQztxQkFDbkM7aUJBQ0Q7YUFDRDtTQUNEO0tBQ0Q7SUFFRCxPQUFPLFFBQVEsQ0FBQyxlQUFlLENBQUMsZ0JBQWdCLENBQUM7SUFDakQsZUFBZSxDQUFDLGVBQWUsRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLFFBQVEsRUFBRSxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUV2RTtRQUNDLGlCQUFpQjtRQUNqQixXQUFXO1FBQ1gsY0FBYztRQUNkLGdCQUFnQjtRQUNoQixpQkFBaUI7UUFDakIsV0FBVztRQUNYLGdCQUFnQjtLQUNoQixDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUNyQixDQUFDO0FBOUZELHNDQThGQztBQVVELFNBQWdCLDZCQUE2QixDQUFDLE9BQWtCO0lBQy9ELE1BQU0sRUFBRSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQWdDLENBQUM7SUFFaEUsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQzNELE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMzRCxNQUFNLG9CQUFvQixHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLE9BQU8sQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO0lBRTlFLE1BQU0sdUJBQXVCLEdBQUcsQ0FBQyxJQUFZLEVBQVUsRUFBRTtRQUN4RCxNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDO1FBQy9ELElBQUksSUFBSSxLQUFLLGVBQWUsRUFBRTtZQUM3QixPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLGVBQWUsQ0FBQyxDQUFDO1NBQzlDO1FBQ0QsSUFBSSxPQUFPLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFO1lBQ3ZCLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUM7U0FDbkM7UUFDRCxPQUFPLElBQUksQ0FBQyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDOUMsQ0FBQyxDQUFDO0lBRUYsTUFBTSxRQUFRLEdBQUcsZ0JBQWdCLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDOUMsS0FBSyxNQUFNLElBQUksSUFBSSxRQUFRLEVBQUU7UUFFNUIsSUFBSSxPQUFPLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUMzRCxTQUFTO1NBQ1Q7UUFFRCxJQUFJLElBQUksS0FBSyxlQUFlLEVBQUU7WUFDN0IsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztZQUNyRixRQUFRLENBQUMsZUFBZSxDQUFDLE1BQU0sR0FBRyxLQUFLLENBQUM7WUFDeEMsUUFBUSxDQUFDLGVBQWUsQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxvQkFBb0IsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7WUFDdkgsS0FBSyxDQUFDLHVCQUF1QixDQUFDLElBQUksQ0FBQyxFQUFFLElBQUksQ0FBQyxTQUFTLENBQUMsUUFBUSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO1lBQzNFLFNBQVM7U0FDVDtRQUVELElBQUksVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxRQUFRLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksUUFBUSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUM5RiwrQkFBK0I7WUFDL0IsS0FBSyxDQUFDLHVCQUF1QixDQUFDLElBQUksQ0FBQyxFQUFFLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ25GLFNBQVM7U0FDVDtRQUVELElBQUksT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUN2Qix5QkFBeUI7WUFDekIsSUFBSSxZQUFZLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBRTNFLE1BQU0sSUFBSSxHQUFHLEVBQUUsQ0FBQyxjQUFjLENBQUMsWUFBWSxDQUFDLENBQUM7WUFFN0MsS0FBSyxJQUFJLENBQUMsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRTtnQkFDeEQsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQztnQkFDeEQsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUM7Z0JBQ3RDLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO2dCQUV0QyxJQUFJLGdCQUF3QixDQUFDO2dCQUM3QixJQUFJLFdBQVcsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsRUFBRTtvQkFDdkMsZ0JBQWdCLEdBQUcsZ0JBQWdCLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsR0FBRyxNQUFNLENBQUM7aUJBQ3RFO3FCQUFNO29CQUNOLGdCQUFnQixHQUFHLGdCQUFnQixDQUFDO2lCQUNwQztnQkFDRCxJQUFJLG1CQUFtQixDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFO29CQUMvQyxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsZ0JBQWdCLENBQUMsQ0FBQztpQkFDbkU7Z0JBRUQsSUFBSSxZQUFvQixDQUFDO2dCQUN6QixJQUFJLGdCQUFnQixLQUFLLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsRUFBRTtvQkFDaEUsWUFBWSxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztpQkFDekQ7cUJBQU0sSUFBSSxnQkFBZ0IsS0FBSyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxFQUFFO29CQUNyRixZQUFZLEdBQUcsUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztpQkFDMUU7cUJBQU07b0JBQ04sWUFBWSxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxnQkFBZ0IsQ0FBQyxDQUFDO2lCQUNuRTtnQkFDRCxZQUFZLEdBQUcsWUFBWSxDQUFDLE9BQU8sQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7Z0JBQ2hELElBQUksQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLEVBQUU7b0JBQzVDLFlBQVksR0FBRyxJQUFJLEdBQUcsWUFBWSxDQUFDO2lCQUNuQztnQkFDRCxZQUFZLEdBQUcsQ0FDZCxZQUFZLENBQUMsU0FBUyxDQUFDLENBQUMsRUFBRSxHQUFHLEdBQUcsQ0FBQyxDQUFDO3NCQUNoQyxZQUFZO3NCQUNaLFlBQVksQ0FBQyxTQUFTLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQyxDQUNqQyxDQUFDO2FBQ0Y7WUFFRCxZQUFZLEdBQUcsWUFBWSxDQUFDLE9BQU8sQ0FBQyxnREFBZ0QsRUFBRSxVQUFVLENBQUMsRUFBRSxFQUFFLEVBQUUsRUFBRTtnQkFDeEcsT0FBTyxlQUFlLEVBQUUsU0FBUyxFQUFFLEdBQUcsQ0FBQztZQUN4QyxDQUFDLENBQUMsQ0FBQztZQUVILEtBQUssQ0FBQyx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsRUFBRSxZQUFZLENBQUMsQ0FBQztZQUNuRCxTQUFTO1NBQ1Q7UUFFRCxPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixJQUFJLEVBQUUsQ0FBQyxDQUFDO0tBQ3JDO0lBR0QsU0FBUyxnQkFBZ0IsQ0FBQyxHQUFXO1FBQ3BDLElBQUksR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLEtBQUssSUFBSSxFQUFFO1lBQzlFLEdBQUcsSUFBSSxHQUFHLENBQUM7U0FDWDtRQUNELE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztRQUM1QixpQkFBaUIsQ0FBQyxHQUFHLEVBQUUsTUFBTSxFQUFFLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUMzQyxPQUFPLE1BQU0sQ0FBQztJQUNmLENBQUM7SUFFRCxTQUFTLGlCQUFpQixDQUFDLEdBQVcsRUFBRSxNQUFnQixFQUFFLE9BQWU7UUFDeEUsTUFBTSxLQUFLLEdBQUcsRUFBRSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNsQyxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUN0QyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUN0QyxJQUFJLEVBQUUsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsV0FBVyxFQUFFLEVBQUU7Z0JBQ3BDLGlCQUFpQixDQUFDLElBQUksRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFDLENBQUM7YUFDekM7aUJBQU07Z0JBQ04sTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7YUFDbEM7U0FDRDtJQUNGLENBQUM7SUFFRCxTQUFTLEtBQUssQ0FBQyxnQkFBd0IsRUFBRSxRQUF5QjtRQUNqRSxJQUFJLGlCQUFpQixDQUFDLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFO1lBQzdDLFFBQVEsR0FBRyxjQUFjLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7U0FDL0M7UUFDRCxTQUFTLENBQUMsZ0JBQWdCLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFFdEMsU0FBUyxjQUFjLENBQUMsWUFBb0I7WUFDM0MsTUFBTSxLQUFLLEdBQUcsWUFBWSxDQUFDLEtBQUssQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUMvQyxJQUFJLElBQUksR0FBRyxDQUFDLENBQUM7WUFDYixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtnQkFDdEMsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUN0QixJQUFJLElBQUksS0FBSyxDQUFDLEVBQUU7b0JBQ2YsSUFBSSx3QkFBd0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUU7d0JBQ3hDLElBQUksR0FBRyxDQUFDLENBQUM7d0JBQ1QsU0FBUztxQkFDVDtvQkFDRCxJQUFJLDBCQUEwQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRTt3QkFDMUMsSUFBSSxHQUFHLENBQUMsQ0FBQzt3QkFDVCxTQUFTO3FCQUNUO29CQUNELFNBQVM7aUJBQ1Q7Z0JBRUQsSUFBSSxJQUFJLEtBQUssQ0FBQyxFQUFFO29CQUNmLElBQUksc0JBQXNCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFO3dCQUN0QyxJQUFJLEdBQUcsQ0FBQyxDQUFDO3dCQUNULFNBQVM7cUJBQ1Q7b0JBQ0QsS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLEtBQUssR0FBRyxJQUFJLENBQUM7b0JBQ3hCLFNBQVM7aUJBQ1Q7Z0JBRUQsSUFBSSxJQUFJLEtBQUssQ0FBQyxFQUFFO29CQUNmLElBQUksd0JBQXdCLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFO3dCQUN4QyxJQUFJLEdBQUcsQ0FBQyxDQUFDO3dCQUNULFNBQVM7cUJBQ1Q7b0JBQ0QsS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsY0FBYyxFQUFFLFVBQVUsQ0FBQyxFQUFFLE1BQU07d0JBQzFELE9BQU8sTUFBTSxDQUFDO29CQUNmLENBQUMsQ0FBQyxDQUFDO2lCQUNIO2FBQ0Q7WUFFRCxPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekIsQ0FBQztJQUNGLENBQUM7QUFDRixDQUFDO0FBOUpELHNFQThKQztBQUVELFNBQVMsWUFBWSxDQUFDLE1BQWMsRUFBRSxPQUFpQyxFQUFFLEtBQXdEO0lBRWhJLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFO1FBQzFCLE9BQU8sS0FBSyxDQUFDO0tBQ2I7SUFFRCxNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sRUFBRSxNQUFNLENBQUMsQ0FBQztJQUM1QyxNQUFNLFlBQVksR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQzFELE1BQU0sZUFBZSxHQUFHLFFBQVEsQ0FBQyxDQUFDLDREQUE0RDtJQUU5RixNQUFNLFdBQVcsR0FBRyxvQkFBb0IsQ0FBQyxZQUFZLEVBQUUsZUFBZSxLQUFLLFFBQVEsQ0FBQyxDQUFDO0lBQ3JGLEtBQUssQ0FBQyxNQUFNLEVBQUUsV0FBVyxDQUFDLENBQUM7SUFDM0IsT0FBTyxJQUFJLENBQUM7SUFFWixTQUFTLG9CQUFvQixDQUFDLFFBQWdCLEVBQUUsV0FBb0I7UUFDbkUsT0FBTyxXQUFXLENBQUMsUUFBUSxFQUFFLENBQUMsR0FBRyxFQUFFLEVBQUU7WUFDcEMsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDO1lBQ2hELElBQUksU0FBUyxFQUFFO2dCQUNkLE1BQU0sZ0JBQWdCLEdBQUcsR0FBRyxTQUFTLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLDJCQUEyQjtnQkFDM0UsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxFQUFFLGdCQUFnQixDQUFDLENBQUM7Z0JBQ25FLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDbEIsT0FBTyxnQkFBZ0IsQ0FBQzthQUN4QjtZQUVELE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztZQUN2RCxNQUFNLFlBQVksR0FBRyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUM7WUFDcEUsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsZUFBZSxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUM7WUFDaEUsSUFBSSxJQUFJLEdBQUcsVUFBVSxHQUFHLFlBQVksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUM7WUFFeEQsSUFBSSxDQUFDLFdBQVcsSUFBSSxRQUFRLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFO2dCQUN2QywrRkFBK0Y7Z0JBQy9GLE1BQU0sT0FBTyxHQUFHLFlBQVksQ0FBQyxRQUFRLEVBQUU7cUJBQ3JDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDO3FCQUNuQixPQUFPLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQztxQkFDcEIsT0FBTyxDQUFDLElBQUksRUFBRSxLQUFLLENBQUM7cUJBQ3BCLE9BQU8sQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDO3FCQUNwQixPQUFPLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQztxQkFDcEIsT0FBTyxDQUFDLE1BQU0sRUFBRSxHQUFHLENBQUMsQ0FBQztnQkFDdkIsTUFBTSxXQUFXLEdBQUcsR0FBRyxHQUFHLE9BQU8sQ0FBQztnQkFDbEMsSUFBSSxXQUFXLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUU7b0JBQ3JDLElBQUksR0FBRyxXQUFXLENBQUM7aUJBQ25CO2FBQ0Q7WUFDRCxPQUFPLFFBQVEsR0FBRyxJQUFJLEdBQUcsSUFBSSxHQUFHLEdBQUcsQ0FBQztRQUNyQyxDQUFDLENBQUMsQ0FBQztJQUNKLENBQUM7SUFFRCxTQUFTLFdBQVcsQ0FBQyxRQUFnQixFQUFFLFFBQWlDO1FBQ3ZFLHFFQUFxRTtRQUNyRSxPQUFPLFFBQVEsQ0FBQyxPQUFPLENBQUMseUJBQXlCLEVBQUUsQ0FBQyxDQUFTLEVBQUUsR0FBRyxPQUFpQixFQUFFLEVBQUU7WUFDdEYsSUFBSSxHQUFHLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3JCLHFFQUFxRTtZQUNyRSxJQUFJLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssR0FBRyxJQUFJLEdBQUcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEtBQUssSUFBSSxFQUFFO2dCQUNwRCxHQUFHLEdBQUcsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQzthQUN2QjtZQUNELG9DQUFvQztZQUNwQyxPQUFPLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLEtBQUssSUFBSSxDQUFDLEVBQUU7Z0JBQ3JHLEdBQUcsR0FBRyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO2FBQ3ZDO1lBQ0QsMEJBQTBCO1lBQzFCLElBQUksR0FBRyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxLQUFLLEdBQUcsSUFBSSxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLEtBQUssSUFBSSxFQUFFO2dCQUM5RSxHQUFHLEdBQUcsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQzthQUN2QztZQUVELElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLEdBQUcsRUFBRSxTQUFTLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxHQUFHLEVBQUUsVUFBVSxDQUFDLEVBQUU7Z0JBQ2hHLEdBQUcsR0FBRyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUM7YUFDcEI7WUFFRCxPQUFPLE1BQU0sR0FBRyxHQUFHLEdBQUcsR0FBRyxDQUFDO1FBQzNCLENBQUMsQ0FBQyxDQUFDO0lBQ0osQ0FBQztJQUVELFNBQVMsV0FBVyxDQUFDLFFBQWdCLEVBQUUsTUFBYztRQUNwRCxPQUFPLFFBQVEsQ0FBQyxNQUFNLElBQUksTUFBTSxDQUFDLE1BQU0sSUFBSSxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsTUFBTSxDQUFDLEtBQUssTUFBTSxDQUFDO0lBQ3pGLENBQUM7QUFDRixDQUFDIn0= \ No newline at end of file diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 430785e828..2193eb706b 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -212,7 +212,7 @@ export function createESMSourcesAndResources2(options: IOptions2): void { ); } - fileContents = fileContents.replace(/import ([a-zA-z0-9]+) = require\(('[^']+')\);/g, function (_, m1, m2) { + fileContents = fileContents.replace(/import ([a-zA-Z0-9]+) = require\(('[^']+')\);/g, function (_, m1, m2) { return `import * as ${m1} from ${m2};`; }); diff --git a/build/lib/stats.js b/build/lib/stats.js index 45cc0eabf9..f47744748d 100644 --- a/build/lib/stats.js +++ b/build/lib/stats.js @@ -9,6 +9,9 @@ const es = require("event-stream"); const fancyLog = require("fancy-log"); const ansiColors = require("ansi-colors"); class Entry { + name; + totalCount; + totalSize; constructor(name, totalCount, totalSize) { this.name = name; this.totalCount = totalCount; @@ -71,3 +74,4 @@ function createStatsStream(group, log) { }); } exports.createStatsStream = createStatsStream; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RhdHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJzdGF0cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxtQ0FBbUM7QUFDbkMsc0NBQXNDO0FBQ3RDLDBDQUEwQztBQUcxQyxNQUFNLEtBQUs7SUFDVztJQUFxQjtJQUEyQjtJQUFyRSxZQUFxQixJQUFZLEVBQVMsVUFBa0IsRUFBUyxTQUFpQjtRQUFqRSxTQUFJLEdBQUosSUFBSSxDQUFRO1FBQVMsZUFBVSxHQUFWLFVBQVUsQ0FBUTtRQUFTLGNBQVMsR0FBVCxTQUFTLENBQVE7SUFBSSxDQUFDO0lBRTNGLFFBQVEsQ0FBQyxNQUFnQjtRQUN4QixJQUFJLENBQUMsTUFBTSxFQUFFO1lBQ1osSUFBSSxJQUFJLENBQUMsVUFBVSxLQUFLLENBQUMsRUFBRTtnQkFDMUIsT0FBTyxHQUFHLElBQUksQ0FBQyxJQUFJLEtBQUssSUFBSSxDQUFDLFNBQVMsUUFBUSxDQUFDO2FBQy9DO2lCQUFNO2dCQUNOLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxLQUFLLElBQUksQ0FBQyxVQUFVLGVBQWUsSUFBSSxDQUFDLFNBQVMsUUFBUSxDQUFDO2FBQzdFO1NBQ0Q7YUFBTTtZQUNOLElBQUksSUFBSSxDQUFDLFVBQVUsS0FBSyxDQUFDLEVBQUU7Z0JBQzFCLE9BQU8sY0FBYyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQzthQUUzRjtpQkFBTTtnQkFDTixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsVUFBVSxHQUFHLEdBQUc7b0JBQ2xDLENBQUMsQ0FBQyxVQUFVLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxFQUFFLENBQUM7b0JBQzlDLENBQUMsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztnQkFFOUMsT0FBTyxjQUFjLFVBQVUsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEtBQUssV0FBVyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQzthQUMzRztTQUNEO0lBQ0YsQ0FBQztDQUNEO0FBRUQsTUFBTSxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQWlCLENBQUM7QUFFMUMsU0FBZ0IsaUJBQWlCLENBQUMsS0FBYSxFQUFFLEdBQWE7SUFFN0QsTUFBTSxLQUFLLEdBQUcsSUFBSSxLQUFLLENBQUMsS0FBSyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztJQUNyQyxRQUFRLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLENBQUM7SUFFaEMsT0FBTyxFQUFFLENBQUMsT0FBTyxDQUFDLFVBQVUsSUFBSTtRQUMvQixNQUFNLElBQUksR0FBRyxJQUFZLENBQUM7UUFDMUIsSUFBSSxPQUFPLElBQUksQ0FBQyxJQUFJLEtBQUssUUFBUSxFQUFFO1lBQ2xDLEtBQUssQ0FBQyxVQUFVLElBQUksQ0FBQyxDQUFDO1lBQ3RCLElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLEVBQUU7Z0JBQ25DLEtBQUssQ0FBQyxTQUFTLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUM7YUFDeEM7aUJBQU0sSUFBSSxJQUFJLENBQUMsSUFBSSxJQUFJLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEtBQUssUUFBUSxFQUFFO2dCQUMzRCxLQUFLLENBQUMsU0FBUyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO2FBQ2xDO2lCQUFNO2dCQUNOLGdCQUFnQjthQUNoQjtTQUNEO1FBQ0QsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDekIsQ0FBQyxFQUFFO1FBQ0YsSUFBSSxHQUFHLEVBQUU7WUFDUixJQUFJLEtBQUssQ0FBQyxVQUFVLEtBQUssQ0FBQyxFQUFFO2dCQUMzQixRQUFRLENBQUMsY0FBYyxVQUFVLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO2FBRWhHO2lCQUFNO2dCQUNOLE1BQU0sS0FBSyxHQUFHLEtBQUssQ0FBQyxVQUFVLEdBQUcsR0FBRztvQkFDbkMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxRQUFRLEVBQUUsQ0FBQztvQkFDL0MsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO2dCQUUvQyxRQUFRLENBQUMsY0FBYyxVQUFVLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLFdBQVcsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQzthQUNoSDtTQUNEO1FBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNsQixDQUFDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFsQ0QsOENBa0NDIn0= \ No newline at end of file diff --git a/build/lib/stylelint/validateVariableNames.js b/build/lib/stylelint/validateVariableNames.js new file mode 100644 index 0000000000..c34a339ccc --- /dev/null +++ b/build/lib/stylelint/validateVariableNames.js @@ -0,0 +1,34 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getVariableNameValidator = void 0; +const fs_1 = require("fs"); +const path = require("path"); +const RE_VAR_PROP = /var\(\s*(--([\w\-\.]+))/g; +let knownVariables; +function getKnownVariableNames() { + if (!knownVariables) { + const knownVariablesFileContent = (0, fs_1.readFileSync)(path.join(__dirname, './vscode-known-variables.json'), 'utf8').toString(); + const knownVariablesInfo = JSON.parse(knownVariablesFileContent); + knownVariables = new Set([...knownVariablesInfo.colors, ...knownVariablesInfo.others]); + } + return knownVariables; +} +function getVariableNameValidator() { + const allVariables = getKnownVariableNames(); + return (value, report) => { + RE_VAR_PROP.lastIndex = 0; // reset lastIndex just to be sure + let match; + while (match = RE_VAR_PROP.exec(value)) { + const variableName = match[1]; + if (variableName && !allVariables.has(variableName)) { + report(variableName); + } + } + }; +} +exports.getVariableNameValidator = getVariableNameValidator; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGVWYXJpYWJsZU5hbWVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsidmFsaWRhdGVWYXJpYWJsZU5hbWVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLDJCQUFrQztBQUNsQyw2QkFBOEI7QUFFOUIsTUFBTSxXQUFXLEdBQUcsMEJBQTBCLENBQUM7QUFFL0MsSUFBSSxjQUF1QyxDQUFDO0FBQzVDLFNBQVMscUJBQXFCO0lBQzdCLElBQUksQ0FBQyxjQUFjLEVBQUU7UUFDcEIsTUFBTSx5QkFBeUIsR0FBRyxJQUFBLGlCQUFZLEVBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsK0JBQStCLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUN6SCxNQUFNLGtCQUFrQixHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztRQUNqRSxjQUFjLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxHQUFHLGtCQUFrQixDQUFDLE1BQU0sRUFBRSxHQUFHLGtCQUFrQixDQUFDLE1BQU0sQ0FBYSxDQUFDLENBQUM7S0FDbkc7SUFDRCxPQUFPLGNBQWMsQ0FBQztBQUN2QixDQUFDO0FBTUQsU0FBZ0Isd0JBQXdCO0lBQ3ZDLE1BQU0sWUFBWSxHQUFHLHFCQUFxQixFQUFFLENBQUM7SUFDN0MsT0FBTyxDQUFDLEtBQWEsRUFBRSxNQUF3QyxFQUFFLEVBQUU7UUFDbEUsV0FBVyxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUMsQ0FBQyxrQ0FBa0M7UUFDN0QsSUFBSSxLQUFLLENBQUM7UUFDVixPQUFPLEtBQUssR0FBRyxXQUFXLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxFQUFFO1lBQ3ZDLE1BQU0sWUFBWSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztZQUM5QixJQUFJLFlBQVksSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLEVBQUU7Z0JBQ3BELE1BQU0sQ0FBQyxZQUFZLENBQUMsQ0FBQzthQUNyQjtTQUNEO0lBQ0YsQ0FBQyxDQUFDO0FBQ0gsQ0FBQztBQVpELDREQVlDIn0= \ No newline at end of file diff --git a/build/lib/stylelint/validateVariableNames.ts b/build/lib/stylelint/validateVariableNames.ts new file mode 100644 index 0000000000..55bec47139 --- /dev/null +++ b/build/lib/stylelint/validateVariableNames.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { readFileSync } from 'fs'; +import path = require('path'); + +const RE_VAR_PROP = /var\(\s*(--([\w\-\.]+))/g; + +let knownVariables: Set | undefined; +function getKnownVariableNames() { + if (!knownVariables) { + const knownVariablesFileContent = readFileSync(path.join(__dirname, './vscode-known-variables.json'), 'utf8').toString(); + const knownVariablesInfo = JSON.parse(knownVariablesFileContent); + knownVariables = new Set([...knownVariablesInfo.colors, ...knownVariablesInfo.others] as string[]); + } + return knownVariables; +} + +export interface IValidator { + (value: string, report: (message: string) => void): void; +} + +export function getVariableNameValidator(): IValidator { + const allVariables = getKnownVariableNames(); + return (value: string, report: (unknwnVariable: string) => void) => { + RE_VAR_PROP.lastIndex = 0; // reset lastIndex just to be sure + let match; + while (match = RE_VAR_PROP.exec(value)) { + const variableName = match[1]; + if (variableName && !allVariables.has(variableName)) { + report(variableName); + } + } + }; +} + diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json new file mode 100644 index 0000000000..76e5c59f8a --- /dev/null +++ b/build/lib/stylelint/vscode-known-variables.json @@ -0,0 +1,744 @@ +{ + "colors": [ + "--vscode-activityBar-activeBackground", + "--vscode-activityBar-activeBorder", + "--vscode-activityBar-activeFocusBorder", + "--vscode-activityBar-background", + "--vscode-activityBar-border", + "--vscode-activityBar-dropBorder", + "--vscode-activityBar-foreground", + "--vscode-activityBar-inactiveForeground", + "--vscode-activityBarBadge-background", + "--vscode-activityBarBadge-foreground", + "--vscode-badge-background", + "--vscode-badge-foreground", + "--vscode-banner-background", + "--vscode-banner-foreground", + "--vscode-banner-iconForeground", + "--vscode-breadcrumb-activeSelectionForeground", + "--vscode-breadcrumb-background", + "--vscode-breadcrumb-focusForeground", + "--vscode-breadcrumb-foreground", + "--vscode-breadcrumbPicker-background", + "--vscode-button-background", + "--vscode-button-border", + "--vscode-button-foreground", + "--vscode-button-hoverBackground", + "--vscode-button-secondaryBackground", + "--vscode-button-secondaryForeground", + "--vscode-button-secondaryHoverBackground", + "--vscode-button-separator", + "--vscode-charts-blue", + "--vscode-charts-foreground", + "--vscode-charts-green", + "--vscode-charts-lines", + "--vscode-charts-orange", + "--vscode-charts-purple", + "--vscode-charts-red", + "--vscode-charts-yellow", + "--vscode-chat-requestBackground", + "--vscode-chat-requestBorder", + "--vscode-checkbox-background", + "--vscode-checkbox-border", + "--vscode-checkbox-foreground", + "--vscode-checkbox-selectBackground", + "--vscode-checkbox-selectBorder", + "--vscode-commandCenter-activeBackground", + "--vscode-commandCenter-activeBorder", + "--vscode-commandCenter-activeForeground", + "--vscode-commandCenter-background", + "--vscode-commandCenter-border", + "--vscode-commandCenter-foreground", + "--vscode-commandCenter-inactiveBorder", + "--vscode-commandCenter-inactiveForeground", + "--vscode-commentsView-resolvedIcon", + "--vscode-commentsView-unresolvedIcon", + "--vscode-contrastActiveBorder", + "--vscode-contrastBorder", + "--vscode-debugConsole-errorForeground", + "--vscode-debugConsole-infoForeground", + "--vscode-debugConsole-sourceForeground", + "--vscode-debugConsole-warningForeground", + "--vscode-debugConsoleInputIcon-foreground", + "--vscode-debugExceptionWidget-background", + "--vscode-debugExceptionWidget-border", + "--vscode-debugIcon-breakpointCurrentStackframeForeground", + "--vscode-debugIcon-breakpointDisabledForeground", + "--vscode-debugIcon-breakpointForeground", + "--vscode-debugIcon-breakpointStackframeForeground", + "--vscode-debugIcon-breakpointUnverifiedForeground", + "--vscode-debugIcon-continueForeground", + "--vscode-debugIcon-disconnectForeground", + "--vscode-debugIcon-pauseForeground", + "--vscode-debugIcon-restartForeground", + "--vscode-debugIcon-startForeground", + "--vscode-debugIcon-stepBackForeground", + "--vscode-debugIcon-stepIntoForeground", + "--vscode-debugIcon-stepOutForeground", + "--vscode-debugIcon-stepOverForeground", + "--vscode-debugIcon-stopForeground", + "--vscode-debugTokenExpression-boolean", + "--vscode-debugTokenExpression-error", + "--vscode-debugTokenExpression-name", + "--vscode-debugTokenExpression-number", + "--vscode-debugTokenExpression-string", + "--vscode-debugTokenExpression-value", + "--vscode-debugToolBar-background", + "--vscode-debugToolBar-border", + "--vscode-debugView-exceptionLabelBackground", + "--vscode-debugView-exceptionLabelForeground", + "--vscode-debugView-stateLabelBackground", + "--vscode-debugView-stateLabelForeground", + "--vscode-debugView-valueChangedHighlight", + "--vscode-descriptionForeground", + "--vscode-diffEditor-border", + "--vscode-diffEditor-diagonalFill", + "--vscode-diffEditor-insertedLineBackground", + "--vscode-diffEditor-insertedTextBackground", + "--vscode-diffEditor-insertedTextBorder", + "--vscode-diffEditor-removedLineBackground", + "--vscode-diffEditor-removedTextBackground", + "--vscode-diffEditor-removedTextBorder", + "--vscode-diffEditor-unchangedRegionBackground", + "--vscode-diffEditorGutter-insertedLineBackground", + "--vscode-diffEditorGutter-removedLineBackground", + "--vscode-diffEditorOverview-insertedForeground", + "--vscode-diffEditorOverview-removedForeground", + "--vscode-disabledForeground", + "--vscode-dropdown-background", + "--vscode-dropdown-border", + "--vscode-dropdown-foreground", + "--vscode-dropdown-listBackground", + "--vscode-editor-background", + "--vscode-editor-findMatchBackground", + "--vscode-editor-findMatchBorder", + "--vscode-editor-findMatchHighlightBackground", + "--vscode-editor-findMatchHighlightBorder", + "--vscode-editor-findRangeHighlightBackground", + "--vscode-editor-findRangeHighlightBorder", + "--vscode-editor-focusedStackFrameHighlightBackground", + "--vscode-editor-foldBackground", + "--vscode-editor-foreground", + "--vscode-editor-hoverHighlightBackground", + "--vscode-editor-inactiveSelectionBackground", + "--vscode-editor-inlineValuesBackground", + "--vscode-editor-inlineValuesForeground", + "--vscode-editor-lineHighlightBackground", + "--vscode-editor-lineHighlightBorder", + "--vscode-editor-linkedEditingBackground", + "--vscode-editor-rangeHighlightBackground", + "--vscode-editor-rangeHighlightBorder", + "--vscode-editor-selectionBackground", + "--vscode-editor-selectionForeground", + "--vscode-editor-selectionHighlightBackground", + "--vscode-editor-selectionHighlightBorder", + "--vscode-editor-snippetFinalTabstopHighlightBackground", + "--vscode-editor-snippetFinalTabstopHighlightBorder", + "--vscode-editor-snippetTabstopHighlightBackground", + "--vscode-editor-snippetTabstopHighlightBorder", + "--vscode-editor-stackFrameHighlightBackground", + "--vscode-editor-symbolHighlightBackground", + "--vscode-editor-symbolHighlightBorder", + "--vscode-editor-wordHighlightBackground", + "--vscode-editor-wordHighlightBorder", + "--vscode-editor-wordHighlightStrongBackground", + "--vscode-editor-wordHighlightStrongBorder", + "--vscode-editor-wordHighlightTextBackground", + "--vscode-editor-wordHighlightTextBorder", + "--vscode-editorActiveLineNumber-foreground", + "--vscode-editorBracketHighlight-foreground1", + "--vscode-editorBracketHighlight-foreground2", + "--vscode-editorBracketHighlight-foreground3", + "--vscode-editorBracketHighlight-foreground4", + "--vscode-editorBracketHighlight-foreground5", + "--vscode-editorBracketHighlight-foreground6", + "--vscode-editorBracketHighlight-unexpectedBracket-foreground", + "--vscode-editorBracketMatch-background", + "--vscode-editorBracketMatch-border", + "--vscode-editorBracketPairGuide-activeBackground1", + "--vscode-editorBracketPairGuide-activeBackground2", + "--vscode-editorBracketPairGuide-activeBackground3", + "--vscode-editorBracketPairGuide-activeBackground4", + "--vscode-editorBracketPairGuide-activeBackground5", + "--vscode-editorBracketPairGuide-activeBackground6", + "--vscode-editorBracketPairGuide-background1", + "--vscode-editorBracketPairGuide-background2", + "--vscode-editorBracketPairGuide-background3", + "--vscode-editorBracketPairGuide-background4", + "--vscode-editorBracketPairGuide-background5", + "--vscode-editorBracketPairGuide-background6", + "--vscode-editorCodeLens-foreground", + "--vscode-editorCommentsWidget-rangeActiveBackground", + "--vscode-editorCommentsWidget-rangeActiveBorder", + "--vscode-editorCommentsWidget-rangeBackground", + "--vscode-editorCommentsWidget-rangeBorder", + "--vscode-editorCommentsWidget-resolvedBorder", + "--vscode-editorCommentsWidget-unresolvedBorder", + "--vscode-editorCursor-background", + "--vscode-editorCursor-foreground", + "--vscode-editorError-background", + "--vscode-editorError-border", + "--vscode-editorError-foreground", + "--vscode-editorGhostText-background", + "--vscode-editorGhostText-border", + "--vscode-editorGhostText-foreground", + "--vscode-editorGroup-border", + "--vscode-editorGroup-dropBackground", + "--vscode-editorGroup-dropIntoPromptBackground", + "--vscode-editorGroup-dropIntoPromptBorder", + "--vscode-editorGroup-dropIntoPromptForeground", + "--vscode-editorGroup-emptyBackground", + "--vscode-editorGroup-focusedEmptyBorder", + "--vscode-editorGroupHeader-border", + "--vscode-editorGroupHeader-noTabsBackground", + "--vscode-editorGroupHeader-tabsBackground", + "--vscode-editorGroupHeader-tabsBorder", + "--vscode-editorGutter-addedBackground", + "--vscode-editorGutter-background", + "--vscode-editorGutter-commentGlyphForeground", + "--vscode-editorGutter-commentRangeForeground", + "--vscode-editorGutter-commentUnresolvedGlyphForeground", + "--vscode-editorGutter-deletedBackground", + "--vscode-editorGutter-foldingControlForeground", + "--vscode-editorGutter-modifiedBackground", + "--vscode-editorHint-border", + "--vscode-editorHint-foreground", + "--vscode-editorHoverWidget-background", + "--vscode-editorHoverWidget-border", + "--vscode-editorHoverWidget-foreground", + "--vscode-editorHoverWidget-highlightForeground", + "--vscode-editorHoverWidget-statusBarBackground", + "--vscode-editorIndentGuide-activeBackground", + "--vscode-editorIndentGuide-background", + "--vscode-editorInfo-background", + "--vscode-editorInfo-border", + "--vscode-editorInfo-foreground", + "--vscode-editorInlayHint-background", + "--vscode-editorInlayHint-foreground", + "--vscode-editorInlayHint-parameterBackground", + "--vscode-editorInlayHint-parameterForeground", + "--vscode-editorInlayHint-typeBackground", + "--vscode-editorInlayHint-typeForeground", + "--vscode-editorLightBulb-foreground", + "--vscode-editorLightBulbAutoFix-foreground", + "--vscode-editorLineNumber-activeForeground", + "--vscode-editorLineNumber-dimmedForeground", + "--vscode-editorLineNumber-foreground", + "--vscode-editorLink-activeForeground", + "--vscode-editorMarkerNavigation-background", + "--vscode-editorMarkerNavigationError-background", + "--vscode-editorMarkerNavigationError-headerBackground", + "--vscode-editorMarkerNavigationInfo-background", + "--vscode-editorMarkerNavigationInfo-headerBackground", + "--vscode-editorMarkerNavigationWarning-background", + "--vscode-editorMarkerNavigationWarning-headerBackground", + "--vscode-editorOverviewRuler-addedForeground", + "--vscode-editorOverviewRuler-background", + "--vscode-editorOverviewRuler-border", + "--vscode-editorOverviewRuler-bracketMatchForeground", + "--vscode-editorOverviewRuler-commentForeground", + "--vscode-editorOverviewRuler-commentUnresolvedForeground", + "--vscode-editorOverviewRuler-commonContentForeground", + "--vscode-editorOverviewRuler-currentContentForeground", + "--vscode-editorOverviewRuler-deletedForeground", + "--vscode-editorOverviewRuler-errorForeground", + "--vscode-editorOverviewRuler-findMatchForeground", + "--vscode-editorOverviewRuler-incomingContentForeground", + "--vscode-editorOverviewRuler-infoForeground", + "--vscode-editorOverviewRuler-modifiedForeground", + "--vscode-editorOverviewRuler-rangeHighlightForeground", + "--vscode-editorOverviewRuler-selectionHighlightForeground", + "--vscode-editorOverviewRuler-warningForeground", + "--vscode-editorOverviewRuler-wordHighlightForeground", + "--vscode-editorOverviewRuler-wordHighlightStrongForeground", + "--vscode-editorOverviewRuler-wordHighlightTextForeground", + "--vscode-editorPane-background", + "--vscode-editorRuler-foreground", + "--vscode-editorStickyScroll-background", + "--vscode-editorStickyScrollHover-background", + "--vscode-editorSuggestWidget-background", + "--vscode-editorSuggestWidget-border", + "--vscode-editorSuggestWidget-focusHighlightForeground", + "--vscode-editorSuggestWidget-foreground", + "--vscode-editorSuggestWidget-highlightForeground", + "--vscode-editorSuggestWidget-selectedBackground", + "--vscode-editorSuggestWidget-selectedForeground", + "--vscode-editorSuggestWidget-selectedIconForeground", + "--vscode-editorSuggestWidgetStatus-foreground", + "--vscode-editorUnicodeHighlight-background", + "--vscode-editorUnicodeHighlight-border", + "--vscode-editorUnnecessaryCode-border", + "--vscode-editorUnnecessaryCode-opacity", + "--vscode-editorWarning-background", + "--vscode-editorWarning-border", + "--vscode-editorWarning-foreground", + "--vscode-editorWhitespace-foreground", + "--vscode-editorWidget-background", + "--vscode-editorWidget-border", + "--vscode-editorWidget-foreground", + "--vscode-editorWidget-resizeBorder", + "--vscode-errorForeground", + "--vscode-extensionBadge-remoteBackground", + "--vscode-extensionBadge-remoteForeground", + "--vscode-extensionButton-background", + "--vscode-extensionButton-foreground", + "--vscode-extensionButton-hoverBackground", + "--vscode-extensionButton-prominentBackground", + "--vscode-extensionButton-prominentForeground", + "--vscode-extensionButton-prominentHoverBackground", + "--vscode-extensionButton-separator", + "--vscode-extensionIcon-preReleaseForeground", + "--vscode-extensionIcon-sponsorForeground", + "--vscode-extensionIcon-starForeground", + "--vscode-extensionIcon-verifiedForeground", + "--vscode-focusBorder", + "--vscode-foreground", + "--vscode-icon-foreground", + "--vscode-input-background", + "--vscode-input-border", + "--vscode-input-foreground", + "--vscode-input-placeholderForeground", + "--vscode-inputOption-activeBackground", + "--vscode-inputOption-activeBorder", + "--vscode-inputOption-activeForeground", + "--vscode-inputOption-hoverBackground", + "--vscode-inputValidation-errorBackground", + "--vscode-inputValidation-errorBorder", + "--vscode-inputValidation-errorForeground", + "--vscode-inputValidation-infoBackground", + "--vscode-inputValidation-infoBorder", + "--vscode-inputValidation-infoForeground", + "--vscode-inputValidation-warningBackground", + "--vscode-inputValidation-warningBorder", + "--vscode-inputValidation-warningForeground", + "--vscode-interactiveEditor-border", + "--vscode-interactiveEditor-regionHighlight", + "--vscode-interactiveEditor-shadow", + "--vscode-interactiveEditorDiff-inserted", + "--vscode-interactiveEditorDiff-removed", + "--vscode-interactiveEditorInput-background", + "--vscode-interactiveEditorInput-border", + "--vscode-interactiveEditorInput-focusBorder", + "--vscode-interactiveEditorInput-placeholderForeground", + "--vscode-keybindingLabel-background", + "--vscode-keybindingLabel-border", + "--vscode-keybindingLabel-bottomBorder", + "--vscode-keybindingLabel-foreground", + "--vscode-keybindingTable-headerBackground", + "--vscode-keybindingTable-rowsBackground", + "--vscode-list-activeSelectionBackground", + "--vscode-list-activeSelectionForeground", + "--vscode-list-activeSelectionIconForeground", + "--vscode-list-deemphasizedForeground", + "--vscode-list-dropBackground", + "--vscode-list-errorForeground", + "--vscode-list-filterMatchBackground", + "--vscode-list-filterMatchBorder", + "--vscode-list-focusAndSelectionOutline", + "--vscode-list-focusBackground", + "--vscode-list-focusForeground", + "--vscode-list-focusHighlightForeground", + "--vscode-list-focusOutline", + "--vscode-list-highlightForeground", + "--vscode-list-hoverBackground", + "--vscode-list-hoverForeground", + "--vscode-list-inactiveFocusBackground", + "--vscode-list-inactiveFocusOutline", + "--vscode-list-inactiveSelectionBackground", + "--vscode-list-inactiveSelectionForeground", + "--vscode-list-inactiveSelectionIconForeground", + "--vscode-list-invalidItemForeground", + "--vscode-list-warningForeground", + "--vscode-listFilterWidget-background", + "--vscode-listFilterWidget-noMatchesOutline", + "--vscode-listFilterWidget-outline", + "--vscode-listFilterWidget-shadow", + "--vscode-menu-background", + "--vscode-menu-border", + "--vscode-menu-foreground", + "--vscode-menu-selectionBackground", + "--vscode-menu-selectionBorder", + "--vscode-menu-selectionForeground", + "--vscode-menu-separatorBackground", + "--vscode-menubar-selectionBackground", + "--vscode-menubar-selectionBorder", + "--vscode-menubar-selectionForeground", + "--vscode-merge-border", + "--vscode-merge-commonContentBackground", + "--vscode-merge-commonHeaderBackground", + "--vscode-merge-currentContentBackground", + "--vscode-merge-currentHeaderBackground", + "--vscode-merge-incomingContentBackground", + "--vscode-merge-incomingHeaderBackground", + "--vscode-mergeEditor-change-background", + "--vscode-mergeEditor-change-word-background", + "--vscode-mergeEditor-changeBase-background", + "--vscode-mergeEditor-changeBase-word-background", + "--vscode-mergeEditor-conflict-handled-minimapOverViewRuler", + "--vscode-mergeEditor-conflict-handledFocused-border", + "--vscode-mergeEditor-conflict-handledUnfocused-border", + "--vscode-mergeEditor-conflict-input1-background", + "--vscode-mergeEditor-conflict-input2-background", + "--vscode-mergeEditor-conflict-unhandled-minimapOverViewRuler", + "--vscode-mergeEditor-conflict-unhandledFocused-border", + "--vscode-mergeEditor-conflict-unhandledUnfocused-border", + "--vscode-mergeEditor-conflictingLines-background", + "--vscode-minimap-background", + "--vscode-minimap-errorHighlight", + "--vscode-minimap-findMatchHighlight", + "--vscode-minimap-foregroundOpacity", + "--vscode-minimap-selectionHighlight", + "--vscode-minimap-selectionOccurrenceHighlight", + "--vscode-minimap-warningHighlight", + "--vscode-minimapGutter-addedBackground", + "--vscode-minimapGutter-deletedBackground", + "--vscode-minimapGutter-modifiedBackground", + "--vscode-minimapSlider-activeBackground", + "--vscode-minimapSlider-background", + "--vscode-minimapSlider-hoverBackground", + "--vscode-notebook-cellBorderColor", + "--vscode-notebook-cellEditorBackground", + "--vscode-notebook-cellHoverBackground", + "--vscode-notebook-cellInsertionIndicator", + "--vscode-notebook-cellStatusBarItemHoverBackground", + "--vscode-notebook-cellToolbarSeparator", + "--vscode-notebook-editorBackground", + "--vscode-notebook-focusedCellBackground", + "--vscode-notebook-focusedCellBorder", + "--vscode-notebook-focusedEditorBorder", + "--vscode-notebook-inactiveFocusedCellBorder", + "--vscode-notebook-inactiveSelectedCellBorder", + "--vscode-notebook-outputContainerBackgroundColor", + "--vscode-notebook-outputContainerBorderColor", + "--vscode-notebook-selectedCellBackground", + "--vscode-notebook-selectedCellBorder", + "--vscode-notebook-symbolHighlightBackground", + "--vscode-notebookEditorOverviewRuler-runningCellForeground", + "--vscode-notebookScrollbarSlider-activeBackground", + "--vscode-notebookScrollbarSlider-background", + "--vscode-notebookScrollbarSlider-hoverBackground", + "--vscode-notebookStatusErrorIcon-foreground", + "--vscode-notebookStatusRunningIcon-foreground", + "--vscode-notebookStatusSuccessIcon-foreground", + "--vscode-notificationCenter-border", + "--vscode-notificationCenterHeader-background", + "--vscode-notificationCenterHeader-foreground", + "--vscode-notificationLink-foreground", + "--vscode-notificationToast-border", + "--vscode-notifications-background", + "--vscode-notifications-border", + "--vscode-notifications-foreground", + "--vscode-notificationsErrorIcon-foreground", + "--vscode-notificationsInfoIcon-foreground", + "--vscode-notificationsWarningIcon-foreground", + "--vscode-panel-background", + "--vscode-panel-border", + "--vscode-panel-dropBorder", + "--vscode-panelInput-border", + "--vscode-panelSection-border", + "--vscode-panelSection-dropBackground", + "--vscode-panelSectionHeader-background", + "--vscode-panelSectionHeader-border", + "--vscode-panelSectionHeader-foreground", + "--vscode-panelTitle-activeBorder", + "--vscode-panelTitle-activeForeground", + "--vscode-panelTitle-inactiveForeground", + "--vscode-peekView-border", + "--vscode-peekViewEditor-background", + "--vscode-peekViewEditor-matchHighlightBackground", + "--vscode-peekViewEditor-matchHighlightBorder", + "--vscode-peekViewEditorGutter-background", + "--vscode-peekViewEditorStickyScroll-background", + "--vscode-peekViewResult-background", + "--vscode-peekViewResult-fileForeground", + "--vscode-peekViewResult-lineForeground", + "--vscode-peekViewResult-matchHighlightBackground", + "--vscode-peekViewResult-selectionBackground", + "--vscode-peekViewResult-selectionForeground", + "--vscode-peekViewTitle-background", + "--vscode-peekViewTitleDescription-foreground", + "--vscode-peekViewTitleLabel-foreground", + "--vscode-pickerGroup-border", + "--vscode-pickerGroup-foreground", + "--vscode-ports-iconRunningProcessForeground", + "--vscode-problemsErrorIcon-foreground", + "--vscode-problemsInfoIcon-foreground", + "--vscode-problemsWarningIcon-foreground", + "--vscode-profileBadge-background", + "--vscode-profileBadge-foreground", + "--vscode-progressBar-background", + "--vscode-quickInput-background", + "--vscode-quickInput-foreground", + "--vscode-quickInput-list-focusBackground", + "--vscode-quickInputList-focusBackground", + "--vscode-quickInputList-focusForeground", + "--vscode-quickInputList-focusIconForeground", + "--vscode-quickInputTitle-background", + "--vscode-sash-hoverBorder", + "--vscode-scm-providerBorder", + "--vscode-scrollbar-shadow", + "--vscode-scrollbarSlider-activeBackground", + "--vscode-scrollbarSlider-background", + "--vscode-scrollbarSlider-hoverBackground", + "--vscode-search-resultsInfoForeground", + "--vscode-searchEditor-findMatchBackground", + "--vscode-searchEditor-findMatchBorder", + "--vscode-searchEditor-textInputBorder", + "--vscode-selection-background", + "--vscode-settings-checkboxBackground", + "--vscode-settings-checkboxBorder", + "--vscode-settings-checkboxForeground", + "--vscode-settings-dropdownBackground", + "--vscode-settings-dropdownBorder", + "--vscode-settings-dropdownForeground", + "--vscode-settings-dropdownListBorder", + "--vscode-settings-focusedRowBackground", + "--vscode-settings-focusedRowBorder", + "--vscode-settings-headerBorder", + "--vscode-settings-headerForeground", + "--vscode-settings-modifiedItemIndicator", + "--vscode-settings-numberInputBackground", + "--vscode-settings-numberInputBorder", + "--vscode-settings-numberInputForeground", + "--vscode-settings-rowHoverBackground", + "--vscode-settings-sashBorder", + "--vscode-settings-settingsHeaderHoverForeground", + "--vscode-settings-textInputBackground", + "--vscode-settings-textInputBorder", + "--vscode-settings-textInputForeground", + "--vscode-sideBar-background", + "--vscode-sideBar-border", + "--vscode-sideBar-dropBackground", + "--vscode-sideBar-foreground", + "--vscode-sideBarSectionHeader-background", + "--vscode-sideBarSectionHeader-border", + "--vscode-sideBarSectionHeader-foreground", + "--vscode-sideBarTitle-foreground", + "--vscode-sideBySideEditor-horizontalBorder", + "--vscode-sideBySideEditor-verticalBorder", + "--vscode-statusBar-background", + "--vscode-statusBar-border", + "--vscode-statusBar-debuggingBackground", + "--vscode-statusBar-debuggingBorder", + "--vscode-statusBar-debuggingForeground", + "--vscode-statusBar-focusBorder", + "--vscode-statusBar-foreground", + "--vscode-statusBar-noFolderBackground", + "--vscode-statusBar-noFolderBorder", + "--vscode-statusBar-noFolderForeground", + "--vscode-statusBar-offlineBackground", + "--vscode-statusBar-offlineForeground", + "--vscode-statusBarItem-activeBackground", + "--vscode-statusBarItem-compactHoverBackground", + "--vscode-statusBarItem-errorBackground", + "--vscode-statusBarItem-errorForeground", + "--vscode-statusBarItem-focusBorder", + "--vscode-statusBarItem-hoverBackground", + "--vscode-statusBarItem-prominentBackground", + "--vscode-statusBarItem-prominentForeground", + "--vscode-statusBarItem-prominentHoverBackground", + "--vscode-statusBarItem-remoteBackground", + "--vscode-statusBarItem-remoteForeground", + "--vscode-statusBarItem-warningBackground", + "--vscode-statusBarItem-warningForeground", + "--vscode-symbolIcon-arrayForeground", + "--vscode-symbolIcon-booleanForeground", + "--vscode-symbolIcon-classForeground", + "--vscode-symbolIcon-colorForeground", + "--vscode-symbolIcon-constantForeground", + "--vscode-symbolIcon-constructorForeground", + "--vscode-symbolIcon-enumeratorForeground", + "--vscode-symbolIcon-enumeratorMemberForeground", + "--vscode-symbolIcon-eventForeground", + "--vscode-symbolIcon-fieldForeground", + "--vscode-symbolIcon-fileForeground", + "--vscode-symbolIcon-folderForeground", + "--vscode-symbolIcon-functionForeground", + "--vscode-symbolIcon-interfaceForeground", + "--vscode-symbolIcon-keyForeground", + "--vscode-symbolIcon-keywordForeground", + "--vscode-symbolIcon-methodForeground", + "--vscode-symbolIcon-moduleForeground", + "--vscode-symbolIcon-namespaceForeground", + "--vscode-symbolIcon-nullForeground", + "--vscode-symbolIcon-numberForeground", + "--vscode-symbolIcon-objectForeground", + "--vscode-symbolIcon-operatorForeground", + "--vscode-symbolIcon-packageForeground", + "--vscode-symbolIcon-propertyForeground", + "--vscode-symbolIcon-referenceForeground", + "--vscode-symbolIcon-snippetForeground", + "--vscode-symbolIcon-stringForeground", + "--vscode-symbolIcon-structForeground", + "--vscode-symbolIcon-textForeground", + "--vscode-symbolIcon-typeParameterForeground", + "--vscode-symbolIcon-unitForeground", + "--vscode-symbolIcon-variableForeground", + "--vscode-tab-activeBackground", + "--vscode-tab-activeBorder", + "--vscode-tab-activeBorderTop", + "--vscode-tab-activeForeground", + "--vscode-tab-activeModifiedBorder", + "--vscode-tab-border", + "--vscode-tab-hoverBackground", + "--vscode-tab-hoverBorder", + "--vscode-tab-hoverForeground", + "--vscode-tab-inactiveBackground", + "--vscode-tab-inactiveForeground", + "--vscode-tab-inactiveModifiedBorder", + "--vscode-tab-lastPinnedBorder", + "--vscode-tab-unfocusedActiveBackground", + "--vscode-tab-unfocusedActiveBorder", + "--vscode-tab-unfocusedActiveBorderTop", + "--vscode-tab-unfocusedActiveForeground", + "--vscode-tab-unfocusedActiveModifiedBorder", + "--vscode-tab-unfocusedHoverBackground", + "--vscode-tab-unfocusedHoverBorder", + "--vscode-tab-unfocusedHoverForeground", + "--vscode-tab-unfocusedInactiveBackground", + "--vscode-tab-unfocusedInactiveForeground", + "--vscode-tab-unfocusedInactiveModifiedBorder", + "--vscode-terminal-ansiBlack", + "--vscode-terminal-ansiBlue", + "--vscode-terminal-ansiBrightBlack", + "--vscode-terminal-ansiBrightBlue", + "--vscode-terminal-ansiBrightCyan", + "--vscode-terminal-ansiBrightGreen", + "--vscode-terminal-ansiBrightMagenta", + "--vscode-terminal-ansiBrightRed", + "--vscode-terminal-ansiBrightWhite", + "--vscode-terminal-ansiBrightYellow", + "--vscode-terminal-ansiCyan", + "--vscode-terminal-ansiGreen", + "--vscode-terminal-ansiMagenta", + "--vscode-terminal-ansiRed", + "--vscode-terminal-ansiWhite", + "--vscode-terminal-ansiYellow", + "--vscode-terminal-background", + "--vscode-terminal-border", + "--vscode-terminal-dropBackground", + "--vscode-terminal-findMatchBackground", + "--vscode-terminal-findMatchBorder", + "--vscode-terminal-findMatchHighlightBackground", + "--vscode-terminal-findMatchHighlightBorder", + "--vscode-terminal-foreground", + "--vscode-terminal-hoverHighlightBackground", + "--vscode-terminal-inactiveSelectionBackground", + "--vscode-terminal-selectionBackground", + "--vscode-terminal-selectionForeground", + "--vscode-terminal-tab-activeBorder", + "--vscode-terminalCommandDecoration-defaultBackground", + "--vscode-terminalCommandDecoration-errorBackground", + "--vscode-terminalCommandDecoration-successBackground", + "--vscode-terminalCursor-background", + "--vscode-terminalCursor-foreground", + "--vscode-terminalOverviewRuler-cursorForeground", + "--vscode-terminalOverviewRuler-findMatchForeground", + "--vscode-testing-iconErrored", + "--vscode-testing-iconFailed", + "--vscode-testing-iconPassed", + "--vscode-testing-iconQueued", + "--vscode-testing-iconSkipped", + "--vscode-testing-iconUnset", + "--vscode-testing-message-error-decorationForeground", + "--vscode-testing-message-error-lineBackground", + "--vscode-testing-message-info-decorationForeground", + "--vscode-testing-message-info-lineBackground", + "--vscode-testing-peekBorder", + "--vscode-testing-peekHeaderBackground", + "--vscode-testing-runAction", + "--vscode-textBlockQuote-background", + "--vscode-textBlockQuote-border", + "--vscode-textCodeBlock-background", + "--vscode-textLink-activeForeground", + "--vscode-textLink-foreground", + "--vscode-textPreformat-foreground", + "--vscode-textSeparator-foreground", + "--vscode-titleBar-activeBackground", + "--vscode-titleBar-activeForeground", + "--vscode-titleBar-border", + "--vscode-titleBar-inactiveBackground", + "--vscode-titleBar-inactiveForeground", + "--vscode-toolbar-activeBackground", + "--vscode-toolbar-hoverBackground", + "--vscode-toolbar-hoverOutline", + "--vscode-tree-inactiveIndentGuidesStroke", + "--vscode-tree-indentGuidesStroke", + "--vscode-tree-tableColumnsBorder", + "--vscode-tree-tableOddRowsBackground", + "--vscode-walkThrough-embeddedEditorBackground", + "--vscode-walkthrough-stepTitle-foreground", + "--vscode-welcomePage-background", + "--vscode-welcomePage-progress-background", + "--vscode-welcomePage-progress-foreground", + "--vscode-welcomePage-tileBackground", + "--vscode-welcomePage-tileBorder", + "--vscode-welcomePage-tileHoverBackground", + "--vscode-widget-border", + "--vscode-widget-shadow", + "--vscode-window-activeBorder", + "--vscode-window-inactiveBorder" + ], + "others": [ + "--background-dark", + "--background-light", + "--dropdown-padding-bottom", + "--dropdown-padding-top", + "--insert-border-color", + "--last-tab-margin-right", + "--monaco-monospace-font", + "--monaco-monospace-font", + "--notebook-cell-input-preview-font-family", + "--notebook-cell-input-preview-font-size", + "--notebook-cell-output-font-size", + "--notebook-diff-view-viewport-slider", + "--notebook-find-horizontal-padding", + "--notebook-find-width", + "--outline-element-color", + "--separator-border", + "--status-border-top-color", + "--tab-border-bottom-color", + "--tab-border-top-color", + "--tab-dirty-border-top-color", + "--tabs-border-bottom-color", + "--tab-sizing-current-width", + "--tab-sizing-fixed-max-width", + "--testMessageDecorationFontFamily", + "--testMessageDecorationFontSize", + "--title-border-bottom-color", + "--vscode-editorCodeLens-fontFamily", + "--vscode-editorCodeLens-fontFamilyDefault", + "--vscode-editorCodeLens-fontFeatureSettings", + "--vscode-editorCodeLens-fontSize", + "--vscode-editorCodeLens-lineHeight", + "--vscode-explorer-align-offset-margin-left", + "--vscode-interactive-session-foreground", + "--vscode-interactive-result-editor-background-color", + "--vscode-repl-font-family", + "--vscode-repl-font-size-for-twistie", + "--vscode-repl-font-size", + "--vscode-repl-line-height", + "--vscode-sash-hover-size", + "--vscode-sash-size", + "--window-border-color", + "--workspace-trust-check-color", + "--workspace-trust-selected-color", + "--workspace-trust-unselected-color", + "--workspace-trust-x-color", + "--z-index-notebook-cell-bottom-toolbar-container", + "--z-index-notebook-cell-editor-outline", + "--z-index-notebook-cell-expand-part-button", + "--z-index-notebook-cell-output-toolbar", + "--z-index-notebook-cell-status", + "--z-index-notebook-cell-toolbar-dropdown-active", + "--z-index-notebook-cell-toolbar", + "--z-index-notebook-folding-indicator", + "--z-index-notebook-input-collapse-condicon", + "--z-index-notebook-list-insertion-indicator", + "--z-index-notebook-output", + "--z-index-notebook-progress-bar", + "--z-index-notebook-scrollbar", + "--z-index-run-button-container", + "--zoom-factor" + ] +} \ No newline at end of file diff --git a/build/lib/task.js b/build/lib/task.js index d844aeb273..627766d6e2 100644 --- a/build/lib/task.js +++ b/build/lib/task.js @@ -95,3 +95,4 @@ function define(name, task) { return task; } exports.define = define; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGFzay5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRhc2sudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsc0NBQXNDO0FBQ3RDLDBDQUEwQztBQW1CMUMsU0FBUyxVQUFVLENBQUMsQ0FBeUM7SUFDNUQsSUFBSSxPQUFhLENBQUUsQ0FBQyxJQUFJLEtBQUssVUFBVSxFQUFFO1FBQ3hDLE9BQU8sSUFBSSxDQUFDO0tBQ1o7SUFDRCxPQUFPLEtBQUssQ0FBQztBQUNkLENBQUM7QUFFRCxTQUFTLFdBQVcsQ0FBQyxJQUFZO0lBQ2hDLE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUM7QUFDakMsQ0FBQztBQUVELEtBQUssVUFBVSxRQUFRLENBQUMsSUFBVTtJQUNqQyxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxXQUFXLElBQUksYUFBYSxDQUFDO0lBQ2hFLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFO1FBQ2pCLFFBQVEsQ0FBQyxVQUFVLEVBQUUsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztLQUNuRDtJQUNELE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQztJQUNuQyxNQUFNLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN2QixNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQzdDLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEdBQUcsR0FBRyxHQUFHLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQ2pFLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFO1FBQ2pCLFFBQVEsQ0FBQyxVQUFVLEVBQUUsVUFBVSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFB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 \ No newline at end of file diff --git a/build/lib/test/i18n.test.js b/build/lib/test/i18n.test.js index 9d109637c6..1acb449cd2 100644 --- a/build/lib/test/i18n.test.js +++ b/build/lib/test/i18n.test.js @@ -9,20 +9,20 @@ const i18n = require("../i18n"); suite('XLF Parser Tests', () => { const sampleXlf = 'Key #1Key #2 &'; const sampleTranslatedXlf = 'Key #1Кнопка #1Key #2 &Кнопка #2 &'; - const originalFilePath = 'vs/base/common/keybinding'; + const name = 'vs/base/common/keybinding'; const keys = ['key1', 'key2']; const messages = ['Key #1', 'Key #2 &']; const translatedMessages = { key1: 'Кнопка #1', key2: 'Кнопка #2 &' }; test('Keys & messages to XLF conversion', () => { const xlf = new i18n.XLF('vscode-workbench'); - xlf.addFile(originalFilePath, keys, messages); + xlf.addFile(name, keys, messages); const xlfString = xlf.toString(); assert.strictEqual(xlfString.replace(/\s{2,}/g, ''), sampleXlf); }); test('XLF to keys & messages conversion', () => { i18n.XLF.parse(sampleTranslatedXlf).then(function (resolvedFiles) { assert.deepStrictEqual(resolvedFiles[0].messages, translatedMessages); - assert.strictEqual(resolvedFiles[0].originalFilePath, originalFilePath); + assert.strictEqual(resolvedFiles[0].name, name); }); }); test('JSON file source path to Transifex resource match', () => { @@ -38,3 +38,4 @@ suite('XLF Parser Tests', () => { assert.deepStrictEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench); }); }); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaTE4bi50ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiaTE4bi50ZXN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7QUFFaEcsaUNBQWtDO0FBQ2xDLGdDQUFpQztBQUVqQyxLQUFLLENBQUMsa0JBQWtCLEVBQUUsR0FBRyxFQUFFO0lBQzlCLE1BQU0sU0FBUyxHQUFHLGtYQUFrWCxDQUFDO0lBQ3JZLE1BQU0sbUJBQW1CLEdBQUcsaWNBQWljLENBQUM7SUFDOWQsTUFBTSxJQUFJLEdBQUcsMkJBQTJCLENBQUM7SUFDekMsTUFBTSxJQUFJLEdBQUcsQ0FBQyxNQUFNLEVBQUUsTUFBTSxDQUFDLENBQUM7SUFDOUIsTUFBTSxRQUFRLEdBQUcsQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDeEMsTUFBTSxrQkFBa0IsR0FBRyxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsSUFBSSxFQUFFLGFBQWEsRUFBRSxDQUFDO0lBRXRFLElBQUksQ0FBQyxtQ0FBbUMsRUFBRSxHQUFHLEVBQUU7UUFDOUMsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLENBQUMsR0FBRyxDQUFDLGtCQUFrQixDQUFDLENBQUM7UUFDN0MsR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ2xDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUVqQyxNQUFNLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLEVBQUUsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQ2pFLENBQUMsQ0FBQyxDQUFDO0lBRUgsSUFBSSxDQUFDLG1DQUFtQyxFQUFFLEdBQUcsRUFBRTtRQUM5QyxJQUFJLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDLElBQUksQ0FBQyxVQUFVLGFBQWE7WUFDL0QsTUFBTSxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsUUFBUSxFQUFFLGtCQUFrQixDQUFDLENBQUM7WUFDdEUsTUFBTSxDQUFDLFdBQVcsQ0FBQyxhQUFhLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ2pELENBQUMsQ0FBQyxDQUFDO0lBQ0osQ0FBQyxDQUFDLENBQUM7SUFFSCxJQUFJLENBQUMsbURBQW1ELEVBQUUsR0FBRyxFQUFFO1FBQzlELE1BQU0sYUFBYSxHQUFXLGVBQWUsRUFDNUMsZ0JBQWdCLEdBQVcsa0JBQWtCLENBQUM7UUFFL0MsTUFBTSxRQUFRLEdBQWtCLEVBQUUsSUFBSSxFQUFFLGFBQWEsRUFBRSxPQUFPLEVBQUUsYUFBYSxFQUFFLEVBQzlFLGFBQWEsR0FBRyxFQUFFLElBQUksRUFBRSxtQkFBbUIsRUFBRSxPQUFPLEVBQUUsYUFBYSxFQUFFLEVBQ3JFLE1BQU0sR0FBRyxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsT0FBTyxFQUFFLGFBQWEsRUFBRSxFQUN0RCxJQUFJLEdBQUcsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxhQUFhLEVBQUUsRUFDbEQsSUFBSSxHQUFHLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsRUFDckQsY0FBYyxHQUFHLEVBQUUsSUFBSSxFQUFFLDJCQUEyQixFQUFFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxFQUNqRixpQkFBaUIsR0FBRyxFQUFFLElBQUksRUFBRSxnQ0FBZ0MsRUFBRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsRUFDekYsU0FBUyxHQUFHLEVBQUUsSUFBSSxFQUFFLGNBQWMsRUFBRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsQ0FBQztRQUVqRSxNQUFNLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsaURBQWlELENBQUMsRUFBRSxRQUFRLENBQUMsQ0FBQztRQUN0RyxNQUFNLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsK0NBQStDLENBQUMsRUFBRSxhQUFhLENBQUMsQ0FBQztRQUN6RyxNQUFNLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsc0NBQXNDLENBQUMsRUFBRSxNQUFNLENBQUMsQ0FBQztRQUN6RixNQUFNLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsNkJBQTZCLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUM5RSxNQUFNLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsOEJBQThCLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUMvRSxNQUFNLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsMkNBQTJDLENBQUMsRUFBRSxjQUFjLENBQUMsQ0FBQztRQUN0RyxNQUFNLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMscURBQXFELENBQUMsRUFBRSxpQkFBaUIsQ0FBQyxDQUFDO1FBQ25ILE1BQU0sQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQywrQ0FBK0MsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQ3RHLENBQUMsQ0FBQyxDQUFDO0FBQ0osQ0FBQyxDQUFDLENBQUMifQ== \ No newline at end of file diff --git a/build/lib/test/i18n.test.ts b/build/lib/test/i18n.test.ts index e9b84e700b..01b1d713ce 100644 --- a/build/lib/test/i18n.test.ts +++ b/build/lib/test/i18n.test.ts @@ -9,14 +9,14 @@ import i18n = require('../i18n'); suite('XLF Parser Tests', () => { const sampleXlf = 'Key #1Key #2 &'; const sampleTranslatedXlf = 'Key #1Кнопка #1Key #2 &Кнопка #2 &'; - const originalFilePath = 'vs/base/common/keybinding'; + const name = 'vs/base/common/keybinding'; const keys = ['key1', 'key2']; const messages = ['Key #1', 'Key #2 &']; const translatedMessages = { key1: 'Кнопка #1', key2: 'Кнопка #2 &' }; test('Keys & messages to XLF conversion', () => { const xlf = new i18n.XLF('vscode-workbench'); - xlf.addFile(originalFilePath, keys, messages); + xlf.addFile(name, keys, messages); const xlfString = xlf.toString(); assert.strictEqual(xlfString.replace(/\s{2,}/g, ''), sampleXlf); @@ -25,7 +25,7 @@ suite('XLF Parser Tests', () => { test('XLF to keys & messages conversion', () => { i18n.XLF.parse(sampleTranslatedXlf).then(function (resolvedFiles) { assert.deepStrictEqual(resolvedFiles[0].messages, translatedMessages); - assert.strictEqual(resolvedFiles[0].originalFilePath, originalFilePath); + assert.strictEqual(resolvedFiles[0].name, name); }); }); diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js index 65b9837339..351d0b073d 100644 --- a/build/lib/treeshaking.js +++ b/build/lib/treeshaking.js @@ -13,7 +13,7 @@ var ShakeLevel; ShakeLevel[ShakeLevel["Files"] = 0] = "Files"; ShakeLevel[ShakeLevel["InnerFile"] = 1] = "InnerFile"; ShakeLevel[ShakeLevel["ClassMembers"] = 2] = "ClassMembers"; -})(ShakeLevel = exports.ShakeLevel || (exports.ShakeLevel = {})); +})(ShakeLevel || (exports.ShakeLevel = ShakeLevel = {})); function toStringShakeLevel(shakeLevel) { switch (shakeLevel) { case 0 /* ShakeLevel.Files */: @@ -163,6 +163,10 @@ function processLibFiles(ts, options) { * A TypeScript language service host */ class TypeScriptLanguageServiceHost { + _ts; + _libs; + _files; + _compilerOptions; constructor(ts, libs, files, compilerOptions) { this._ts = ts; this._libs = libs; @@ -490,54 +494,56 @@ function markNodes(ts, languageService, options) { } const nodeSourceFile = node.getSourceFile(); const loop = (node) => { - const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node); - if (symbolImportNode) { - setColor(symbolImportNode, 2 /* NodeColor.Black */); - const importDeclarationNode = findParentImportDeclaration(symbolImportNode); - if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { - enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + const symbols = getRealNodeSymbol(ts, checker, node); + for (const { symbol, symbolImportNode } of symbols) { + if (symbolImportNode) { + setColor(symbolImportNode, 2 /* NodeColor.Black */); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + } } - } - if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { - for (let i = 0, len = symbol.declarations.length; i < len; i++) { // {{SQL CARBON EDIT}} Compile fixes - const declaration = symbol.declarations[i]; // {{SQL CARBON EDIT}} Compile fixes - if (ts.isSourceFile(declaration)) { - // Do not enqueue full source files - // (they can be the declaration of a module import) - continue; - } - if (options.shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { - enqueue_black(declaration.name); - for (let j = 0; j < declaration.members.length; j++) { - const member = declaration.members[j]; - const memberName = member.name ? member.name.getText() : null; - if (ts.isConstructorDeclaration(member) - || ts.isConstructSignatureDeclaration(member) - || ts.isIndexSignatureDeclaration(member) - || ts.isCallSignatureDeclaration(member) - || memberName === '[Symbol.iterator]' - || memberName === '[Symbol.toStringTag]' - || memberName === 'toJSON' - || memberName === 'toString' - || memberName === 'dispose' // TODO: keeping all `dispose` methods - || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... - ) { - enqueue_black(member); + if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { + for (let i = 0, len = symbol.declarations.length; i < len; i++) { // {{SQL CARBON EDIT}} Compile fixes + const declaration = symbol.declarations[i]; // {{SQL CARBON EDIT}} Compile fixes + if (ts.isSourceFile(declaration)) { + // Do not enqueue full source files + // (they can be the declaration of a module import) + continue; + } + if (options.shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { + enqueue_black(declaration.name); + for (let j = 0; j < declaration.members.length; j++) { + const member = declaration.members[j]; + const memberName = member.name ? member.name.getText() : null; + if (ts.isConstructorDeclaration(member) + || ts.isConstructSignatureDeclaration(member) + || ts.isIndexSignatureDeclaration(member) + || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' + || memberName === '[Symbol.toStringTag]' + || memberName === 'toJSON' + || memberName === 'toString' + || memberName === 'dispose' // TODO: keeping all `dispose` methods + || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... + ) { + enqueue_black(member); + } + if (isStaticMemberWithSideEffects(ts, member)) { + enqueue_black(member); + } } - if (isStaticMemberWithSideEffects(ts, member)) { - enqueue_black(member); + // queue the heritage clauses + if (declaration.heritageClauses) { + for (const heritageClause of declaration.heritageClauses) { + enqueue_black(heritageClause); + } } } - // queue the heritage clauses - if (declaration.heritageClauses) { - for (const heritageClause of declaration.heritageClauses) { - enqueue_black(heritageClause); - } + else { + enqueue_black(declaration); } } - else { - enqueue_black(declaration); - } } } node.forEachChild(loop); @@ -736,13 +742,22 @@ function findSymbolFromHeritageType(ts, checker, type) { return findSymbolFromHeritageType(ts, checker, type.expression); } if (ts.isIdentifier(type)) { - return getRealNodeSymbol(ts, checker, type)[0]; + const tmp = getRealNodeSymbol(ts, checker, type); + return (tmp.length > 0 ? tmp[0].symbol : null); } if (ts.isPropertyAccessExpression(type)) { return findSymbolFromHeritageType(ts, checker, type.name); } return null; } +class SymbolImportTuple { + symbol; + symbolImportNode; + constructor(symbol, symbolImportNode) { + this.symbol = symbol; + this.symbolImportNode = symbolImportNode; + } +} /** * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ @@ -774,7 +789,7 @@ function getRealNodeSymbol(ts, checker, node) { } if (!ts.isShorthandPropertyAssignment(node)) { if (node.getChildCount() !== 0) { - return [null, null]; + return []; } } const { parent } = node; @@ -820,10 +835,7 @@ function getRealNodeSymbol(ts, checker, node) { const type = checker.getTypeAtLocation(parent.parent); if (name && type) { if (type.isUnion()) { - const prop = type.types[0].getProperty(name); - if (prop) { - symbol = prop; - } + return generateMultipleSymbols(type, name, importNode); } else { const prop = type.getProperty(name); @@ -854,9 +866,19 @@ function getRealNodeSymbol(ts, checker, node) { } } if (symbol && symbol.declarations) { - return [symbol, importNode]; + return [new SymbolImportTuple(symbol, importNode)]; + } + return []; + function generateMultipleSymbols(type, name, importNode) { + const result = []; + for (const t of type.types) { + const prop = t.getProperty(name); + if (prop && prop.declarations) { + result.push(new SymbolImportTuple(prop, importNode)); + } + } + return result; } - return [null, null]; } /** Get the token whose text contains the position */ function getTokenAtPosition(ts, sourceFile, position, allowPositionInLeadingTrivia, includeEndPosition) { @@ -879,3 +901,4 @@ function getTokenAtPosition(ts, sourceFile, position, allowPositionInLeadingTriv } } //#endregion +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJlZXNoYWtpbmcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0cmVlc2hha2luZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyx5QkFBeUI7QUFDekIsNkJBQTZCO0FBRzdCLE1BQU0scUJBQXFCLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLHlCQUF5QixDQUFDLENBQUMsQ0FBQztBQUV2RixJQUFrQixVQUlqQjtBQUpELFdBQWtCLFVBQVU7SUFDM0IsNkNBQVMsQ0FBQTtJQUNULHFEQUFhLENBQUE7SUFDYiwyREFBZ0IsQ0FBQTtBQUNqQixDQUFDLEVBSmlCLFVBQVUsMEJBQVYsVUFBVSxRQUkzQjtBQUVELFNBQWdCLGtCQUFrQixDQUFDLFVBQXNCO0lBQ3hELFFBQVEsVUFBVSxFQUFFO1FBQ25CO1lBQ0MsT0FBTyxXQUFXLENBQUM7UUFDcEI7WUFDQyxPQUFPLGVBQWUsQ0FBQztRQUN4QjtZQUNDLE9BQU8sa0JBQWtCLENBQUM7S0FDM0I7QUFDRixDQUFDO0FBVEQsZ0RBU0M7QUF3Q0QsU0FBUyxnQkFBZ0IsQ0FBQyxPQUE0QixFQUFFLFdBQXlDO0lBQ2hHLEtBQUssTUFBTSxJQUFJLElBQUksV0FBVyxFQUFFO1FBQy9CLElBQUksTUFBTSxHQUFHLEVBQUUsQ0FBQztRQUNoQixJQUFJLElBQUksQ0FBQyxJQUFJLEVBQUU7WUFDZCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxXQUFXLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1NBQ2xFO1FBQ0QsSUFBSSxJQUFJLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUU7WUFDNUIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyw2QkFBNkIsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDckUsTUFBTSxJQUFJ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 \ No newline at end of file diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index 84f8aebbda..e33d25321e 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -609,58 +609,60 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language const nodeSourceFile = node.getSourceFile(); const loop = (node: ts.Node) => { - const [symbol, symbolImportNode] = getRealNodeSymbol(ts, checker, node); - if (symbolImportNode) { - setColor(symbolImportNode, NodeColor.Black); - const importDeclarationNode = findParentImportDeclaration(symbolImportNode); - if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { - enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + const symbols = getRealNodeSymbol(ts, checker, node); + for (const { symbol, symbolImportNode } of symbols) { + if (symbolImportNode) { + setColor(symbolImportNode, NodeColor.Black); + const importDeclarationNode = findParentImportDeclaration(symbolImportNode); + if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { + enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); + } } - } - if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { + if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { for (let i = 0, len = symbol.declarations!.length; i < len; i++) { // {{SQL CARBON EDIT}} Compile fixes const declaration = symbol.declarations![i]; // {{SQL CARBON EDIT}} Compile fixes - if (ts.isSourceFile(declaration)) { - // Do not enqueue full source files - // (they can be the declaration of a module import) - continue; - } - - if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { - enqueue_black(declaration.name!); - - for (let j = 0; j < declaration.members.length; j++) { - const member = declaration.members[j]; - const memberName = member.name ? member.name.getText() : null; - if ( - ts.isConstructorDeclaration(member) - || ts.isConstructSignatureDeclaration(member) - || ts.isIndexSignatureDeclaration(member) - || ts.isCallSignatureDeclaration(member) - || memberName === '[Symbol.iterator]' - || memberName === '[Symbol.toStringTag]' - || memberName === 'toJSON' - || memberName === 'toString' - || memberName === 'dispose'// TODO: keeping all `dispose` methods - || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... - ) { - enqueue_black(member); - } - - if (isStaticMemberWithSideEffects(ts, member)) { - enqueue_black(member); - } + if (ts.isSourceFile(declaration)) { + // Do not enqueue full source files + // (they can be the declaration of a module import) + continue; } - // queue the heritage clauses - if (declaration.heritageClauses) { - for (const heritageClause of declaration.heritageClauses) { - enqueue_black(heritageClause); + if (options.shakeLevel === ShakeLevel.ClassMembers && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { + enqueue_black(declaration.name!); + + for (let j = 0; j < declaration.members.length; j++) { + const member = declaration.members[j]; + const memberName = member.name ? member.name.getText() : null; + if ( + ts.isConstructorDeclaration(member) + || ts.isConstructSignatureDeclaration(member) + || ts.isIndexSignatureDeclaration(member) + || ts.isCallSignatureDeclaration(member) + || memberName === '[Symbol.iterator]' + || memberName === '[Symbol.toStringTag]' + || memberName === 'toJSON' + || memberName === 'toString' + || memberName === 'dispose'// TODO: keeping all `dispose` methods + || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... + ) { + enqueue_black(member); + } + + if (isStaticMemberWithSideEffects(ts, member)) { + enqueue_black(member); + } } + + // queue the heritage clauses + if (declaration.heritageClauses) { + for (const heritageClause of declaration.heritageClauses) { + enqueue_black(heritageClause); + } + } + } else { + enqueue_black(declaration); } - } else { - enqueue_black(declaration); } } } @@ -879,7 +881,8 @@ function findSymbolFromHeritageType(ts: typeof import('typescript'), checker: ts return findSymbolFromHeritageType(ts, checker, type.expression); } if (ts.isIdentifier(type)) { - return getRealNodeSymbol(ts, checker, type)[0]; + const tmp = getRealNodeSymbol(ts, checker, type); + return (tmp.length > 0 ? tmp[0].symbol : null); } if (ts.isPropertyAccessExpression(type)) { return findSymbolFromHeritageType(ts, checker, type.name); @@ -887,10 +890,17 @@ function findSymbolFromHeritageType(ts: typeof import('typescript'), checker: ts return null; } +class SymbolImportTuple { + constructor( + public readonly symbol: ts.Symbol | null, + public readonly symbolImportNode: ts.Declaration | null + ) { } +} + /** * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) */ -function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChecker, node: ts.Node): [ts.Symbol | null, ts.Declaration | null] { +function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChecker, node: ts.Node): SymbolImportTuple[] { // Use some TypeScript internals to avoid code duplication type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes }; @@ -923,7 +933,7 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec if (!ts.isShorthandPropertyAssignment(node)) { if (node.getChildCount() !== 0) { - return [null, null]; + return []; } } @@ -976,10 +986,7 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec const type = checker.getTypeAtLocation(parent.parent); if (name && type) { if (type.isUnion()) { - const prop = type.types[0].getProperty(name); - if (prop) { - symbol = prop; - } + return generateMultipleSymbols(type, name, importNode); } else { const prop = type.getProperty(name); if (prop) { @@ -1011,10 +1018,21 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec } if (symbol && symbol.declarations) { - return [symbol, importNode]; + return [new SymbolImportTuple(symbol, importNode)]; } - return [null, null]; + return []; + + function generateMultipleSymbols(type: ts.UnionType, name: string, importNode: ts.Declaration | null): SymbolImportTuple[] { + const result: SymbolImportTuple[] = []; + for (const t of type.types) { + const prop = t.getProperty(name); + if (prop && prop.declarations) { + result.push(new SymbolImportTuple(prop, importNode)); + } + } + return result; + } } /** Get the token whose text contains the position */ diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js index 6cf57578c9..7a16f74aad 100644 --- a/build/lib/tsb/builder.js +++ b/build/lib/tsb/builder.js @@ -5,19 +5,20 @@ *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.createTypeScriptBuilder = exports.CancellationToken = void 0; -const fs_1 = require("fs"); +const fs = require("fs"); const path = require("path"); const crypto = require("crypto"); const utils = require("./utils"); const colors = require("ansi-colors"); const ts = require("typescript"); const Vinyl = require("vinyl"); +const source_map_1 = require("source-map"); var CancellationToken; (function (CancellationToken) { CancellationToken.None = { isCancellationRequested() { return false; } }; -})(CancellationToken = exports.CancellationToken || (exports.CancellationToken = {})); +})(CancellationToken || (exports.CancellationToken = CancellationToken = {})); function normalize(path) { return path.replace(/\\/g, '/'); } @@ -125,8 +126,74 @@ function createTypeScriptBuilder(config, projectFile, cmd) { const basename = path.basename(vinyl.relative, extname); const dirname = path.dirname(vinyl.relative); const tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts'; - const sourceMap = JSON.parse(sourcemapFile.text); + let sourceMap = JSON.parse(sourcemapFile.text); sourceMap.sources[0] = tsname.replace(/\\/g, '/'); + // check for an "input source" map and combine them + // in step 1 we extract all line edit from the input source map, and + // in step 2 we apply the line edits to the typescript source map + const snapshot = host.getScriptSnapshot(fileName); + if (snapshot instanceof VinylScriptSnapshot && snapshot.sourceMap) { + const inputSMC = new source_map_1.SourceMapConsumer(snapshot.sourceMap); + const tsSMC = new source_map_1.SourceMapConsumer(sourceMap); + let didChange = false; + const smg = new source_map_1.SourceMapGenerator({ + file: sourceMap.file, + sourceRoot: sourceMap.sourceRoot + }); + // step 1 + const lineEdits = new Map(); + inputSMC.eachMapping(m => { + if (m.originalLine === m.generatedLine) { + // same line mapping + let array = lineEdits.get(m.originalLine); + if (!array) { + array = []; + lineEdits.set(m.originalLine, array); + } + array.push([m.originalColumn, m.generatedColumn]); + } + else { + // NOT SUPPORTED + } + }); + // step 2 + tsSMC.eachMapping(m => { + didChange = true; + const edits = lineEdits.get(m.originalLine); + let originalColumnDelta = 0; + if (edits) { + for (const [from, to] of edits) { + if (to >= m.originalColumn) { + break; + } + originalColumnDelta = from - to; + } + } + smg.addMapping({ + source: m.source, + name: m.name, + generated: { line: m.generatedLine, column: m.generatedColumn }, + original: { line: m.originalLine, column: m.originalColumn + originalColumnDelta } + }); + }); + if (didChange) { + [tsSMC, inputSMC].forEach((consumer) => { + consumer.sources.forEach((sourceFile) => { + smg._sources.add(sourceFile); + const sourceContent = consumer.sourceContentFor(sourceFile); + if (sourceContent !== null) { + smg.setSourceContent(sourceFile, sourceContent); + } + }); + }); + sourceMap = JSON.parse(smg.toString()); + // const filename = '/Users/jrieken/Code/vscode/src2/' + vinyl.relative + '.map'; + // fs.promises.mkdir(path.dirname(filename), { recursive: true }).then(async () => { + // await fs.promises.writeFile(filename, smg.toString()); + // await fs.promises.writeFile('/Users/jrieken/Code/vscode/src2/' + vinyl.relative, vinyl.contents); + // }); + } + } vinyl.sourceMap = sourceMap; } } @@ -299,6 +366,8 @@ function createTypeScriptBuilder(config, projectFile, cmd) { } exports.createTypeScriptBuilder = createTypeScriptBuilder; class ScriptSnapshot { + _text; + _mtime; constructor(text, mtime) { this._text = text; this._mtime = mtime; @@ -317,24 +386,32 @@ class ScriptSnapshot { } } class VinylScriptSnapshot extends ScriptSnapshot { + _base; + sourceMap; constructor(file) { super(file.contents.toString(), file.stat.mtime); this._base = file.base; + this.sourceMap = file.sourceMap; } getBase() { return this._base; } } class LanguageServiceHost { + _cmdLine; + _projectPath; + _log; + _snapshots; + _filesInProject; + _filesAdded; + _dependencies; + _dependenciesRecomputeList; + _fileNameToDeclaredModule; + _projectVersion; constructor(_cmdLine, _projectPath, _log) { this._cmdLine = _cmdLine; this._projectPath = _projectPath; this._log = _log; - this.directoryExists = ts.sys.directoryExists; - this.getDirectories = ts.sys.getDirectories; - this.fileExists = ts.sys.fileExists; - this.readFile = ts.sys.readFile; - this.readDirectory = ts.sys.readDirectory; this._snapshots = Object.create(null); this._filesInProject = new Set(_cmdLine.fileNames); this._filesAdded = new Set(); @@ -377,9 +454,9 @@ class LanguageServiceHost { try { result = new VinylScriptSnapshot(new Vinyl({ path: filename, - contents: (0, fs_1.readFileSync)(filename), + contents: fs.readFileSync(filename), base: this.getCompilationSettings().outDir, - stat: (0, fs_1.statSync)(filename) + stat: fs.statSync(filename) })); this.addScriptSnapshot(filename, result); } @@ -389,6 +466,7 @@ class LanguageServiceHost { } return result; } + static _declareModule = /declare\s+module\s+('|")(.+)\1/g; addScriptSnapshot(filename, snapshot) { this._projectVersion++; filename = normalize(filename); @@ -432,6 +510,11 @@ class LanguageServiceHost { getDefaultLibFileName(options) { return ts.getDefaultLibFilePath(options); } + directoryExists = ts.sys.directoryExists; + getDirectories = ts.sys.getDirectories; + fileExists = ts.sys.fileExists; + readFile = ts.sys.readFile; + readDirectory = ts.sys.readDirectory; // ---- dependency management collectDependents(filename, target) { while (this._dependenciesRecomputeList.length) { @@ -488,4 +571,4 @@ class LanguageServiceHost { }); } } -LanguageServiceHost._declareModule = /declare\s+module\s+('|")(.+)\1/g; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVpbGRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImJ1aWxkZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixpQ0FBaUM7QUFDakMsaUNBQWlDO0FBQ2pDLHNDQUFzQztBQUN0QyxpQ0FBaUM7QUFDakMsK0JBQStCO0FBQy9CLDJDQUFpRjtBQVdqRixJQUFpQixpQkFBaUIsQ0FJakM7QUFKRCxXQUFpQixpQkFBaUI7SUFDcEIsc0JBQUksR0FBc0I7UUFDdEMsdUJBQXVCLEtBQUssT0FBTyxLQUFLLENBQUMsQ0FBQyxDQUFDO0tBQzNDLENBQUM7QUFDSCxDQUFDLEVBSmdCLGlCQUFpQixpQ0FBakIsaUJBQWlCLFFBSWpDO0FBUUQsU0FBUyxTQUFTLENBQUMsSUFBWTtJQUM5QixPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0FBQ2pDLENBQUM7QUFFRCxTQUFnQix1QkFBdUIsQ0FBQyxNQUFzQixFQUFFLFdBQW1CLEVBQUUsR0FBeUI7SUFFN0csTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLEtBQUssQ0FBQztJQUUxQixNQUFNLElBQUksR0FBRyxJQUFJLG1CQUFtQixDQUFDLEdBQUcsRUFBRSxXQUFXLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDN0QsTUFBTSxPQUFPLEdBQUcsRUFBRSxDQUFDLHFCQUFxQixDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsc0JBQXNCLEVBQUUsQ0FBQyxDQUFDO0lBQzVFLE1BQU0sZ0JBQWdCLEdBQStCLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDekUsTUFBTSxXQUFXLEdBQStCLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDcEUsTUFBTSxxQkFBcUIsR0FBRyxHQUFHLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztJQUN0RCxJQUFJLFNB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 \ No newline at end of file diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index f36fd1195e..b6bb099326 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -3,13 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { statSync, readFileSync } from 'fs'; +import * as fs from 'fs'; import * as path from 'path'; import * as crypto from 'crypto'; import * as utils from './utils'; import * as colors from 'ansi-colors'; import * as ts from 'typescript'; import * as Vinyl from 'vinyl'; +import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'; export interface IConfiguration { logFn: (topic: string, message: string) => void; @@ -158,8 +159,81 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str const dirname = path.dirname(vinyl.relative); const tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts'; - const sourceMap = JSON.parse(sourcemapFile.text); + let sourceMap = JSON.parse(sourcemapFile.text); sourceMap.sources[0] = tsname.replace(/\\/g, '/'); + + // check for an "input source" map and combine them + // in step 1 we extract all line edit from the input source map, and + // in step 2 we apply the line edits to the typescript source map + const snapshot = host.getScriptSnapshot(fileName); + if (snapshot instanceof VinylScriptSnapshot && snapshot.sourceMap) { + const inputSMC = new SourceMapConsumer(snapshot.sourceMap); + const tsSMC = new SourceMapConsumer(sourceMap); + let didChange = false; + const smg = new SourceMapGenerator({ + file: sourceMap.file, + sourceRoot: sourceMap.sourceRoot + }); + + // step 1 + const lineEdits = new Map(); + inputSMC.eachMapping(m => { + if (m.originalLine === m.generatedLine) { + // same line mapping + let array = lineEdits.get(m.originalLine); + if (!array) { + array = []; + lineEdits.set(m.originalLine, array); + } + array.push([m.originalColumn, m.generatedColumn]); + } else { + // NOT SUPPORTED + } + }); + + // step 2 + tsSMC.eachMapping(m => { + didChange = true; + const edits = lineEdits.get(m.originalLine); + let originalColumnDelta = 0; + if (edits) { + for (const [from, to] of edits) { + if (to >= m.originalColumn) { + break; + } + originalColumnDelta = from - to; + } + } + smg.addMapping({ + source: m.source, + name: m.name, + generated: { line: m.generatedLine, column: m.generatedColumn }, + original: { line: m.originalLine, column: m.originalColumn + originalColumnDelta } + }); + }); + + if (didChange) { + + [tsSMC, inputSMC].forEach((consumer) => { + (consumer).sources.forEach((sourceFile: any) => { + (smg)._sources.add(sourceFile); + const sourceContent = consumer.sourceContentFor(sourceFile); + if (sourceContent !== null) { + smg.setSourceContent(sourceFile, sourceContent); + } + }); + }); + + sourceMap = JSON.parse(smg.toString()); + + // const filename = '/Users/jrieken/Code/vscode/src2/' + vinyl.relative + '.map'; + // fs.promises.mkdir(path.dirname(filename), { recursive: true }).then(async () => { + // await fs.promises.writeFile(filename, smg.toString()); + // await fs.promises.writeFile('/Users/jrieken/Code/vscode/src2/' + vinyl.relative, vinyl.contents); + // }); + } + } + (vinyl).sourceMap = sourceMap; } } @@ -397,9 +471,12 @@ class VinylScriptSnapshot extends ScriptSnapshot { private readonly _base: string; - constructor(file: Vinyl) { + readonly sourceMap?: RawSourceMap; + + constructor(file: Vinyl & { sourceMap?: RawSourceMap }) { super(file.contents!.toString(), file.stat!.mtime); this._base = file.base; + this.sourceMap = file.sourceMap; } getBase(): string { @@ -474,9 +551,9 @@ class LanguageServiceHost implements ts.LanguageServiceHost { try { result = new VinylScriptSnapshot(new Vinyl({ path: filename, - contents: readFileSync(filename), + contents: fs.readFileSync(filename), base: this.getCompilationSettings().outDir, - stat: statSync(filename) + stat: fs.statSync(filename) })); this.addScriptSnapshot(filename, result); } catch (e) { diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js index 207d8cf402..a3686dce0c 100644 --- a/build/lib/tsb/index.js +++ b/build/lib/tsb/index.js @@ -28,7 +28,10 @@ function createNullCompiler() { const _defaultOnError = (err) => console.log(JSON.stringify(err, null, 4)); function create(projectPath, existingOptions, config, onError = _defaultOnError) { function printDiagnostic(diag) { - if (!diag.file || !diag.start) { + if (diag instanceof Error) { + onError(diag.message); + } + else if (!diag.file || !diag.start) { onError(ts.flattenDiagnosticMessageText(diag.messageText, '\n')); } else { @@ -92,7 +95,9 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) } let result; if (config.transpileOnly) { - const transpiler = new transpiler_1.Transpiler(logFn, printDiagnostic, projectPath, cmdLine); + const transpiler = !config.transpileWithSwc + ? new transpiler_1.TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) + : new transpiler_1.SwcTranspiler(logFn, printDiagnostic, projectPath, cmdLine); result = (() => createTranspileStream(transpiler)); } else { @@ -128,3 +133,4 @@ function create(projectPath, existingOptions, config, onError = _defaultOnError) return result; } exports.create = create; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRywrQkFBK0I7QUFDL0IsbUNBQW1DO0FBQ25DLHFDQUFxQztBQUNyQyxpQ0FBaUM7QUFDakMsbUNBQW9EO0FBQ3BELCtCQUErQjtBQUMvQixtQ0FBa0M7QUFDbEMsMkJBQTRDO0FBQzVDLGlDQUFpQztBQUNqQyxzQ0FBdUM7QUFDdkMsNkNBQXlFO0FBT3pFLE1BQU0sV0FBWSxTQUFRLGVBQU07SUFDL0IsTUFBTSxDQUFDLE1BQVcsRUFBRSxTQUFpQixFQUFFLFFBQStCLElBQVUsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQzdGLEtBQUssS0FBSyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztDQUM1QjtBQUVELFNBQVMsa0JBQWtCO0lBQzFCLE1BQU0sTUFBTSxHQUF3QixjQUFjLE9BQU8sSUFBSSxXQUFXLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUM5RSxNQUFNLENBQUMsR0FBRyxHQUFHLEdBQUcsRUFBRSxDQUFDLElBQUksV0FBVyxFQUFFLENBQUM7SUFDckMsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDO0FBRUQsTUFBTSxlQUFlLEdBQUcsQ0FBQyxHQUFXLEVBQUUsRUFBRSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFFbkYsU0FBZ0IsTUFBTSxDQUNyQixXQUFtQixFQUNuQixlQUE0QyxFQUM1QyxNQUFzSCxFQUN0SCxVQUFxQyxlQUFlO0lBR3BELFNBQVMsZUFBZSxDQUFDLElBQTJCO1FBRW5ELElBQUksSUFBSSxZQUFZLEtBQUssRUFBRTtZQUMxQixPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1NBQ3RCO2FBQU0s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 \ No newline at end of file diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts index 5c9175d8ab..f7d5368658 100644 --- a/build/lib/tsb/index.ts +++ b/build/lib/tsb/index.ts @@ -13,7 +13,7 @@ import { strings } from './utils'; import { readFileSync, statSync } from 'fs'; import * as log from 'fancy-log'; import colors = require('ansi-colors'); -import { Transpiler } from './transpiler'; +import { ITranspiler, SwcTranspiler, TscTranspiler } from './transpiler'; export interface IncrementalCompiler { (token?: any): Readable & Writable; @@ -36,13 +36,15 @@ const _defaultOnError = (err: string) => console.log(JSON.stringify(err, null, 4 export function create( projectPath: string, existingOptions: Partial, - config: { verbose?: boolean; transpileOnly?: boolean; transpileOnlyIncludesDts?: boolean }, + config: { verbose?: boolean; transpileOnly?: boolean; transpileOnlyIncludesDts?: boolean; transpileWithSwc?: boolean }, onError: (message: string) => void = _defaultOnError ): IncrementalCompiler { - function printDiagnostic(diag: ts.Diagnostic): void { + function printDiagnostic(diag: ts.Diagnostic | Error): void { - if (!diag.file || !diag.start) { + if (diag instanceof Error) { + onError(diag.message); + } else if (!diag.file || !diag.start) { onError(ts.flattenDiagnosticMessageText(diag.messageText, '\n')); } else { const lineAndCh = diag.file.getLineAndCharacterOfPosition(diag.start); @@ -95,7 +97,7 @@ export function create( } // TRANSPILE ONLY stream doing just TS to JS conversion - function createTranspileStream(transpiler: Transpiler): Readable & Writable { + function createTranspileStream(transpiler: ITranspiler): Readable & Writable { return through(function (this: through.ThroughStream & { queue(a: any): void }, file: Vinyl) { // give the file to the compiler if (file.isStream()) { @@ -126,7 +128,9 @@ export function create( let result: IncrementalCompiler; if (config.transpileOnly) { - const transpiler = new Transpiler(logFn, printDiagnostic, projectPath, cmdLine); + const transpiler = !config.transpileWithSwc + ? new TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) + : new SwcTranspiler(logFn, printDiagnostic, projectPath, cmdLine); result = (() => createTranspileStream(transpiler)); } else { const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js index 52b9ab2c6e..70ed0c479d 100644 --- a/build/lib/tsb/transpiler.js +++ b/build/lib/tsb/transpiler.js @@ -4,7 +4,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.Transpiler = void 0; +exports.SwcTranspiler = exports.TscTranspiler = void 0; +const swc = require("@swc/core"); const ts = require("typescript"); const threads = require("node:worker_threads"); const Vinyl = require("vinyl"); @@ -36,11 +37,43 @@ if (!threads.isMainThread) { threads.parentPort.postMessage(res); }); } +class OutputFileNameOracle { + getOutputFileName; + constructor(cmdLine, configFilePath) { + this.getOutputFileName = (file) => { + try { + // windows: path-sep normalizing + file = ts.normalizePath(file); + if (!cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + cmdLine.fileNames.push(file); + } + const outfile = ts.getOutputFileNames(cmdLine, file, true)[0]; + if (isDts) { + cmdLine.fileNames.pop(); + } + return outfile; + } + catch (err) { + console.error(file, cmdLine.fileNames); + console.error(err); + throw new err; + } + }; + } +} class TranspileWorker { + static pool = 1; + id = TranspileWorker.pool++; + _worker = new threads.Worker(__filename); + _pending; + _durations = []; constructor(outFileFn) { - this.id = TranspileWorker.pool++; - this._worker = new threads.Worker(__filename); - this._durations = []; this._worker.addListener('message', (res) => { if (!this._pending) { console.error('RECEIVING data WITHOUT request'); @@ -111,40 +144,20 @@ class TranspileWorker { }); } } -TranspileWorker.pool = 1; -class Transpiler { +class TscTranspiler { + _onError; + _cmdLine; + static P = Math.floor((0, node_os_1.cpus)().length * .5); + _outputFileNames; + onOutfile; + _workerPool = []; + _queue = []; + _allJobs = []; constructor(logFn, _onError, configFilePath, _cmdLine) { this._onError = _onError; this._cmdLine = _cmdLine; - this._workerPool = []; - this._queue = []; - this._allJobs = []; - logFn('Transpile', `will use ${Transpiler.P} transpile worker`); - this._getOutputFileName = (file) => { - try { - // windows: path-sep normalizing - file = ts.normalizePath(file); - if (!_cmdLine.options.configFilePath) { - // this is needed for the INTERNAL getOutputFileNames-call below... - _cmdLine.options.configFilePath = configFilePath; - } - const isDts = file.endsWith('.d.ts'); - if (isDts) { - file = file.slice(0, -5) + '.ts'; - _cmdLine.fileNames.push(file); - } - const outfile = ts.getOutputFileNames(_cmdLine, file, true)[0]; - if (isDts) { - _cmdLine.fileNames.pop(); - } - return outfile; - } - catch (err) { - console.error(file, _cmdLine.fileNames); - console.error(err); - throw new err; - } - }; + logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); } async join() { // wait for all penindg jobs @@ -161,7 +174,7 @@ class Transpiler { return; } const newLen = this._queue.push(file); - if (newLen > Transpiler.P ** 2) { + if (newLen > TscTranspiler.P ** 2) { this._consumeQueue(); } } @@ -172,8 +185,8 @@ class Transpiler { } // kinda LAZYily create workers if (this._workerPool.length === 0) { - for (let i = 0; i < Transpiler.P; i++) { - this._workerPool.push(new TranspileWorker(file => this._getOutputFileName(file))); + for (let i = 0; i < TscTranspiler.P; i++) { + this._workerPool.push(new TranspileWorker(file => this._outputFileNames.getOutputFileName(file))); } } const freeWorker = this._workerPool.filter(w => !w.isBusy); @@ -187,7 +200,7 @@ class Transpiler { } const job = new Promise(resolve => { const consume = () => { - const files = this._queue.splice(0, Transpiler.P); + const files = this._queue.splice(0, TscTranspiler.P); if (files.length === 0) { // DONE resolve(undefined); @@ -210,11 +223,102 @@ class Transpiler { } } } -exports.Transpiler = Transpiler; -Transpiler.P = Math.floor((0, node_os_1.cpus)().length * .5); +exports.TscTranspiler = TscTranspiler; function _isDefaultEmpty(src) { return src .replace('"use strict";', '') .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') .trim().length === 0; } +class SwcTranspiler { + _logFn; + _onError; + _cmdLine; + onOutfile; + _outputFileNames; + _jobs = []; + constructor(_logFn, _onError, configFilePath, _cmdLine) { + this._logFn = _logFn; + this._onError = _onError; + this._cmdLine = _cmdLine; + _logFn('Transpile', `will use SWC to transpile source files`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); + } + async join() { + const jobs = this._jobs.slice(); + this._jobs.length = 0; + await Promise.allSettled(jobs); + } + transpile(file) { + if (this._cmdLine.options.noEmit) { + // not doing ANYTHING here + return; + } + const tsSrc = String(file.contents); + const t1 = Date.now(); + let options = SwcTranspiler._swcrcEsm; + if (this._cmdLine.options.module === ts.ModuleKind.AMD) { + const isAmd = /\n(import|export)/m.test(tsSrc); + if (isAmd) { + options = SwcTranspiler._swcrcAmd; + } + } + else if (this._cmdLine.options.module === ts.ModuleKind.CommonJS) { + options = SwcTranspiler._swcrcCommonJS; + } + this._jobs.push(swc.transform(tsSrc, options).then(output => { + // check if output of a DTS-files isn't just "empty" and iff so + // skip this file + if (file.path.endsWith('.d.ts') && _isDefaultEmpty(output.code)) { + return; + } + const outBase = this._cmdLine.options.outDir ?? file.base; + const outPath = this._outputFileNames.getOutputFileName(file.path); + this.onOutfile(new Vinyl({ + path: outPath, + base: outBase, + contents: Buffer.from(output.code), + })); + this._logFn('Transpile', `swc took ${Date.now() - t1}ms for ${file.path}`); + }).catch(err => { + this._onError(err); + })); + } + // --- .swcrc + static _swcrcAmd = { + exclude: '\.js$', + jsc: { + parser: { + syntax: 'typescript', + tsx: false, + decorators: true + }, + target: 'es2020', + loose: false, + minify: { + compress: false, + mangle: false + } + }, + module: { + type: 'amd', + noInterop: true + }, + minify: false, + }; + static _swcrcCommonJS = { + ...this._swcrcAmd, + module: { + type: 'commonjs', + importInterop: 'none' + } + }; + static _swcrcEsm = { + ...this._swcrcAmd, + module: { + type: 'es6' + } + }; +} +exports.SwcTranspiler = SwcTranspiler; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhbnNwaWxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRyYW5zcGlsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsaUNBQWlDO0FBQ2pDLGlDQUFpQztBQUNqQywrQ0FBK0M7QUFDL0MsK0JBQStCO0FBQy9CLHFDQUErQjtBQVkvQixTQUFTLFNBQVMsQ0FBQyxLQUFhLEVBQUUsT0FBNEI7SUFFN0QsTUFBTSxLQUFLLEdBQUcsb0JBQW9CLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQy9DLElBQUksQ0FBQyxLQUFLLElBQUksT0FBTyxDQUFDLGVBQWUsRUFBRSxNQUFNLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyxHQUFHLEVBQUU7UUFDcEUsK0NBQStDO1FBQy9DLE9BQU8sR0FBRyxFQUFFLEdBQUcsT0FBTyxFQUFFLEdBQUcsRUFBRSxlQUFlLEVBQUUsRUFBRSxHQUFHLE9BQU8sQ0FBQyxlQUFlLEVBQUUsTUFBTSxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxFQUFFLEVBQUUsRUFBRSxDQUFDO0tBQzdHO0lBQ0QsTUFBTSxHQUFHLEdBQUcsRUFBRSxDQUFDLGVBQWUsQ0FBQyxLQUFLLEVBQUUsT0FBTyxDQUFDLENBQUM7SUFDL0MsT0FBTztRQUNOLEtBQUssRUFBRSxHQUFHLENBQUMsVUFBVTtRQUNyQixJQUFJLEVBQUUsR0FBRyxDQUFDLFdBQVcsSUFBSSxFQUFFO0tBQzNCLENBQUM7QUFDSCxDQUFDO0FBRUQsSUFBSSxDQUFDLE9BQU8sQ0FBQyxZQUFZLEVBQUU7SUFDMUIsU0FBUztJQUNULE9BQU8sQ0FBQyxVQUFVLEVBQUUsV0FBVyxDQUFDLFNBQVMsRUFBRSxDQUFDLEdBQWlCLEVBQUUsRUFBRTtRQUNoRSxNQUFNLEdBQUcsR0FBaUI7WUFDekIsTUFBTSxFQUFFLEVBQUU7WUFDVixXQUFXLEVBQUUsRUFBRTtTQUNmLENBQUM7UUFDRixLQUFLLE1BQU0sS0FBSyxJ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 \ No newline at end of file diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index 267fef97ae..ceb4c75ce8 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as swc from '@swc/core'; import * as ts from 'typescript'; import * as threads from 'node:worker_threads'; import * as Vinyl from 'vinyl'; @@ -48,6 +49,47 @@ if (!threads.isMainThread) { }); } +class OutputFileNameOracle { + + readonly getOutputFileName: (name: string) => string; + + constructor(cmdLine: ts.ParsedCommandLine, configFilePath: string) { + // very complicated logic to re-use TS internal functions to know the output path + // given a TS input path and its config + type InternalTsApi = typeof ts & { + normalizePath(path: string): string; + getOutputFileNames(commandLine: ts.ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[]; + }; + this.getOutputFileName = (file) => { + try { + + // windows: path-sep normalizing + file = (ts).normalizePath(file); + + if (!cmdLine.options.configFilePath) { + // this is needed for the INTERNAL getOutputFileNames-call below... + cmdLine.options.configFilePath = configFilePath; + } + const isDts = file.endsWith('.d.ts'); + if (isDts) { + file = file.slice(0, -5) + '.ts'; + cmdLine.fileNames.push(file); + } + const outfile = (ts).getOutputFileNames(cmdLine, file, true)[0]; + if (isDts) { + cmdLine.fileNames.pop(); + } + return outfile; + + } catch (err) { + console.error(file, cmdLine.fileNames); + console.error(err); + throw new err; + } + }; + } +} + class TranspileWorker { private static pool = 1; @@ -141,12 +183,18 @@ class TranspileWorker { } } +export interface ITranspiler { + onOutfile?: (file: Vinyl) => void; + join(): Promise; + transpile(file: Vinyl): void; +} -export class Transpiler { +export class TscTranspiler implements ITranspiler { static P = Math.floor(cpus().length * .5); - private readonly _getOutputFileName: (name: string) => string; + private readonly _outputFileNames: OutputFileNameOracle; + public onOutfile?: (file: Vinyl) => void; @@ -160,42 +208,8 @@ export class Transpiler { configFilePath: string, private readonly _cmdLine: ts.ParsedCommandLine ) { - logFn('Transpile', `will use ${Transpiler.P} transpile worker`); - - - // very complicated logic to re-use TS internal functions to know the output path - // given a TS input path and its config - type InternalTsApi = typeof ts & { - normalizePath(path: string): string; - getOutputFileNames(commandLine: ts.ParsedCommandLine, inputFileName: string, ignoreCase: boolean): readonly string[]; - }; - this._getOutputFileName = (file) => { - try { - - // windows: path-sep normalizing - file = (ts).normalizePath(file); - - if (!_cmdLine.options.configFilePath) { - // this is needed for the INTERNAL getOutputFileNames-call below... - _cmdLine.options.configFilePath = configFilePath; - } - const isDts = file.endsWith('.d.ts'); - if (isDts) { - file = file.slice(0, -5) + '.ts'; - _cmdLine.fileNames.push(file); - } - const outfile = (ts).getOutputFileNames(_cmdLine, file, true)[0]; - if (isDts) { - _cmdLine.fileNames.pop(); - } - return outfile; - - } catch (err) { - console.error(file, _cmdLine.fileNames); - console.error(err); - throw new err; - } - }; + logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); } async join() { @@ -218,7 +232,7 @@ export class Transpiler { } const newLen = this._queue.push(file); - if (newLen > Transpiler.P ** 2) { + if (newLen > TscTranspiler.P ** 2) { this._consumeQueue(); } } @@ -232,8 +246,8 @@ export class Transpiler { // kinda LAZYily create workers if (this._workerPool.length === 0) { - for (let i = 0; i < Transpiler.P; i++) { - this._workerPool.push(new TranspileWorker(file => this._getOutputFileName(file))); + for (let i = 0; i < TscTranspiler.P; i++) { + this._workerPool.push(new TranspileWorker(file => this._outputFileNames.getOutputFileName(file))); } } @@ -251,7 +265,7 @@ export class Transpiler { const job = new Promise(resolve => { const consume = () => { - const files = this._queue.splice(0, Transpiler.P); + const files = this._queue.splice(0, TscTranspiler.P); if (files.length === 0) { // DONE resolve(undefined); @@ -283,3 +297,111 @@ function _isDefaultEmpty(src: string): boolean { .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') .trim().length === 0; } + + +export class SwcTranspiler implements ITranspiler { + + onOutfile?: ((file: Vinyl) => void) | undefined; + + private readonly _outputFileNames: OutputFileNameOracle; + private _jobs: Promise[] = []; + + constructor( + private readonly _logFn: (topic: string, message: string) => void, + private readonly _onError: (err: any) => void, + configFilePath: string, + private readonly _cmdLine: ts.ParsedCommandLine + ) { + _logFn('Transpile', `will use SWC to transpile source files`); + this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); + } + + async join(): Promise { + const jobs = this._jobs.slice(); + this._jobs.length = 0; + await Promise.allSettled(jobs); + } + + transpile(file: Vinyl): void { + if (this._cmdLine.options.noEmit) { + // not doing ANYTHING here + return; + } + + const tsSrc = String(file.contents); + const t1 = Date.now(); + + let options: swc.Options = SwcTranspiler._swcrcEsm; + if (this._cmdLine.options.module === ts.ModuleKind.AMD) { + const isAmd = /\n(import|export)/m.test(tsSrc); + if (isAmd) { + options = SwcTranspiler._swcrcAmd; + } + } else if (this._cmdLine.options.module === ts.ModuleKind.CommonJS) { + options = SwcTranspiler._swcrcCommonJS; + } + + this._jobs.push(swc.transform(tsSrc, options).then(output => { + + // check if output of a DTS-files isn't just "empty" and iff so + // skip this file + if (file.path.endsWith('.d.ts') && _isDefaultEmpty(output.code)) { + return; + } + + const outBase = this._cmdLine.options.outDir ?? file.base; + const outPath = this._outputFileNames.getOutputFileName(file.path); + + this.onOutfile!(new Vinyl({ + path: outPath, + base: outBase, + contents: Buffer.from(output.code), + })); + + this._logFn('Transpile', `swc took ${Date.now() - t1}ms for ${file.path}`); + + }).catch(err => { + this._onError(err); + })); + } + + // --- .swcrc + + + private static readonly _swcrcAmd: swc.Options = { + exclude: '\.js$', + jsc: { + parser: { + syntax: 'typescript', + tsx: false, + decorators: true + }, + target: 'es2020', + loose: false, + minify: { + compress: false, + mangle: false + } + }, + module: { + type: 'amd', + noInterop: true + }, + minify: false, + }; + + private static readonly _swcrcCommonJS: swc.Options = { + ...this._swcrcAmd, + module: { + type: 'commonjs', + importInterop: 'none' + } + }; + + private static readonly _swcrcEsm: swc.Options = { + ...this._swcrcAmd, + module: { + type: 'es6' + } + }; +} diff --git a/build/lib/tsb/utils.js b/build/lib/tsb/utils.js index 805b450030..38841a0cd7 100644 --- a/build/lib/tsb/utils.js +++ b/build/lib/tsb/utils.js @@ -44,7 +44,7 @@ var collections; return hasOwnProperty.call(collection, key); } collections.contains = contains; -})(collections = exports.collections || (exports.collections = {})); +})(collections || (exports.collections = collections = {})); var strings; (function (strings) { /** @@ -59,7 +59,7 @@ var strings; }); } strings.format = format; -})(strings = exports.strings || (exports.strings = {})); +})(strings || (exports.strings = strings = {})); var graph; (function (graph) { function newNode(data) { @@ -71,9 +71,10 @@ var graph; } graph.newNode = newNode; class Graph { + _hashFn; + _nodes = {}; constructor(_hashFn) { this._hashFn = _hashFn; - this._nodes = {}; // empty } traverse(start, inwards, callback) { @@ -121,4 +122,5 @@ var graph; } } graph.Graph = Graph; -})(graph = exports.graph || (exports.graph = {})); +})(graph || (exports.graph = graph = {})); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ1dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxJQUFjLFdBQVcsQ0FzQ3hCO0FBdENELFdBQWMsV0FBVztJQUVyQixNQUFNLGNBQWMsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQztJQUV2RCxTQUFnQixNQUFNLENBQUksVUFBaUMsRUFBRSxHQUFXO1FBQ3BFLElBQUksY0FBYyxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsR0FBRyxDQUFDLEVBQUU7WUFDdEMsT0FBTyxVQUFVLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDMUI7UUFDRCxPQUFPLElBQUksQ0FBQztJQUNoQixDQUFDO0lBTGUsa0JBQU0sU0FLckIsQ0FBQTtJQUVELFNBQWdCLE1BQU0sQ0FBSSxVQUFpQyxFQUFFLEdBQVcsRUFBRSxLQUFRO1FBQzlFLFVBQVUsQ0FBQyxHQUFHLENBQUMsR0FBRyxLQUFLLENBQUM7SUFDNUIsQ0FBQztJQUZlLGtCQUFNLFNBRXJCLENBQUE7SUFFRCxTQUFnQixjQUFjLENBQUksVUFBaUMsRUFBRSxHQUFXLEVBQUUsS0FBUTtRQUN0RixJQUFJLGNBQWMsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLEdBQUcsQ0FBQyxFQUFFO1lBQ3RDLE9BQU8sVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1NBQzFCO2FBQU07WUFDSCxVQUFVLENBQUMsR0FBRyxDQUFDLEdBQUcsS0FBSyxDQUFDO1lBQ3hCLE9BQU8sS0FBSyxDQUFDO1NBQ2hCO0lBQ0wsQ0FBQztJQVBlLDBCQUFjLGlCQU83QixDQUFBO0lBRUQsU0FBZ0IsT0FBTyxDQUFJLFVBQWlDLEVBQUUsUUFBb0Q7UUFDOUcsS0FBSyxNQUFNLEdBQUcsSUFBSSxVQUFVLEVBQUU7WUFDMUIsSUFBSSxjQUFjLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxHQUFHLENBQUMsRUFBRTtnQkFDdEMsUUFBUSxDQUFDO29CQUNMLEdBQUcsRUFBRSxHQUFHO29CQUNSLEtBQUssRUFBRSxVQUFVLENBQUMsR0FBRyxDQUFDO2lCQUN6QixDQUFDLENBQUM7YUFDTjtTQUNKO0lBQ0wsQ0FBQztJQVRlLG1CQUFPLFVBU3RCLENBQUE7SUFFRCxTQUFnQixRQUFRLENBQUMsVUFBbUMsRUFBRSxHQUFXO1FBQ3JFLE9BQU8sY0FBYyxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsR0FBRyxDQUFDLENBQUM7SUFDaEQsQ0FBQztJQUZlLG9CQUFRLFdBRXZCLENBQUE7QUFDTCxDQUFDLEVBdENhLFdBQVcsMkJBQVgsV0FBVyxRQXNDeEI7QUFFRCxJQUFjLE9BQU8sQ0FlcEI7QUFmRCxXQUFjLE9BQU87SUFFakI7O09BRUc7SUFDVSxhQUFLLEdBQUcsRUFBRSxDQUFDO0lBRVgsZUFBTyxHQUFHLE1BQU0sQ0FBQztJQUU5QixTQUFnQixNQUFNLENBQUMsS0FBYSxFQUFFLEdBQUcsSUFBVztRQUNoRCxPQUFPLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBVSxFQUFFLFVBQVUsS0FBSztZQUM1QyxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQzNELE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLEtBQUssQ0FBQztRQUN4QyxDQUFDLENBQUMsQ0FBQztJQUNQLENBQUM7SUFMZSxjQUFNLFNBS3JCLENBQUE7QUFDTCxDQUFDLEVBZmEsT0FBTyx1QkFBUCxPQUFPLFFBZXBCO0FBRUQsSUFBYyxLQUFLLENBNkVsQjtBQTdFRCxXQUFjLEtBQUs7SUFRZixTQUFnQixPQUFPLENBQUksSUFBTztRQUM5QixPQUFPO1lBQ0gsSUFBSSxFQUFFLElBQUk7WUFDVixRQUFRLEVBQUUsRUFBRTtZQUNaLFFBQVEsRUFBRSxFQUFFO1NBQ2YsQ0FBQztJQUNOLENBQUM7SUFOZSxhQUFPLFVBTXRCLENBQUE7SUFFRCxNQUFhLEtBQUs7UUFJTTtRQUZaLE1BQU0sR0FBK0IsRUFBRSxDQUFDO1FBRWhELFlBQW9CLE9BQStCO1lBQS9CLFlBQU8sR0FBUCxPQUFPLENBQXdCO1lBQy9DLFFBQVE7UUFDWixDQUFDO1FBRUQsUUFBUSxDQUFDLEtBQVEsRUFBRSxPQUFnQixFQUFFLFFBQTJCO1lBQzVELE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDckMsSUFBSSxDQUFDLFNBQVMsRUFBRTtnQkFDWixPQUFPO2FBQ1Y7WUFDRCxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxPQUFPLEVBQUUsRUFBRSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQ3JELENBQUM7UUFFTyxTQUFTLENBQUMsSUFBYSxFQUFFLE9BQWdCLEVBQUUsSUFBZ0MsRUFBRSxRQUEyQjtZQUM1RyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUNwQyxJQUFJLFdBQVcsQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLEdBQUcsQ0FBQyxFQUFFO2dCQUNqQyxPQUFPO2FBQ1Y7WUFDRCxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDO1lBQ2pCLFFBQVEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDcEIsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDO1lBQ3RELFdBQVcsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDO1FBQ2hHLENBQUM7UUFFRCxTQUFTLENBQUMsSUFBTyxFQUFFLEVBQUs7WUFDcEIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixDQUFDLElBQUksQ0FBQyxDQUFDO1lBQy9DLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUUzQyxRQUFRLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLENBQUMsR0FBRyxNQUFNLENBQUM7WUFDN0MsTUFBTSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsUUFBUSxDQUFDO1FBQ25ELENBQUM7UUFFRCxVQUFVLENBQUMsSUFBTztZQUNkLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDL0IsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3hCLFdBQVcsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFO2dCQUN2QyxPQUFPLEtBQUssQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO2dCQUNqQyxPQUFPLEtBQUssQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3JDLENBQUMsQ0FBQyxDQUFDO1FBQ1AsQ0FBQztRQUVELGtCQUFrQixDQUFDLElBQU87WUFDdEIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUMvQixJQUFJLElBQUksR0FBRyxXQUFXLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxDQUFDLENBQUM7WUFFaEQsSUFBSSxDQUFDLElBQUksRUFBRTtnQkFDUCxJQUFJLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO2dCQUNyQixJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQzthQUMzQjtZQUVELE9BQU8sSUFBSSxDQUFDO1FBQ2hCLENBQUM7UUFFRCxNQUFNLENBQUMsSUFBTztZQUNWLE9BQU8sV0FBVyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUMvRCxDQUFDO0tBQ0o7SUEzRFksV0FBSyxRQTJEakIsQ0FBQTtBQUVMLENBQUMsRUE3RWEsS0FBSyxxQkFBTCxLQUFLLFFBNkVsQiJ9 \ No newline at end of file diff --git a/build/lib/typings/gulp-buffer.d.ts b/build/lib/typings/gulp-buffer.d.ts new file mode 100644 index 0000000000..cc4afcfd9a --- /dev/null +++ b/build/lib/typings/gulp-buffer.d.ts @@ -0,0 +1,12 @@ + +declare module "gulp-buffer" { + function f(): NodeJS.ReadWriteStream; + + /** + * This is required as per: + * https://github.com/microsoft/TypeScript/issues/5073 + */ + namespace f {} + + export = f; +} diff --git a/build/lib/typings/gulp-remote-src.d.ts b/build/lib/typings/gulp-remote-src.d.ts deleted file mode 100644 index ff9026b79b..0000000000 --- a/build/lib/typings/gulp-remote-src.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -declare module 'gulp-remote-retry-src' { - - import stream = require("stream"); - - function remote(url: string, options: remote.IOptions): stream.Stream; - - module remote { - export interface IRequestOptions { - body?: any; - json?: boolean; - method?: string; - headers?: any; - } - - export interface IOptions { - base?: string; - buffer?: boolean; - requestOptions?: IRequestOptions; - } - } - - export = remote; -} diff --git a/build/lib/typings/ternary-stream.d.ts b/build/lib/typings/ternary-stream.d.ts new file mode 100644 index 0000000000..d16dbe43fc --- /dev/null +++ b/build/lib/typings/ternary-stream.d.ts @@ -0,0 +1,12 @@ +declare module 'ternary-stream' { + import File = require('vinyl'); + function f(check: (f: File) => boolean, onTrue: NodeJS.ReadWriteStream, opnFalse?: NodeJS.ReadWriteStream): NodeJS.ReadWriteStream; + + /** + * This is required as per: + * https://github.com/microsoft/TypeScript/issues/5073 + */ + namespace f {} + + export = f; +} diff --git a/build/lib/util.js b/build/lib/util.js index a65835a5df..cf68f31cad 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -4,7 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.buildWebNodePaths = exports.createExternalLoaderConfig = exports.acquireWebNodePaths = exports.getElectronVersion = exports.streamToPromise = exports.versionStringToNumber = exports.filter = exports.rebase = exports.getVersion = exports.ensureDir = exports.rreddir = exports.rimraf = exports.rewriteSourceMappingURL = exports.stripSourceMappingURL = exports.loadSourcemaps = exports.cleanNodeModules = exports.skipDirectories = exports.toFileUri = exports.setExecutableBit = exports.fixWin32DirectoryPermissions = exports.debounce = exports.incremental = void 0; +exports.buildWebNodePaths = exports.createExternalLoaderConfig = exports.acquireWebNodePaths = exports.getElectronVersion = exports.streamToPromise = exports.versionStringToNumber = exports.filter = exports.rebase = exports.getVersion = exports.ensureDir = exports.rreddir = exports.rimraf = exports.rewriteSourceMappingURL = exports.appendOwnPathSourceURL = exports.$if = exports.stripSourceMappingURL = exports.loadSourcemaps = exports.cleanNodeModules = exports.skipDirectories = exports.toFileUri = exports.setExecutableBit = exports.fixWin32DirectoryPermissions = exports.debounce = exports.incremental = void 0; const es = require("event-stream"); const _debounce = require("debounce"); const _filter = require("gulp-filter"); @@ -13,7 +13,9 @@ const path = require("path"); const fs = require("fs"); const _rimraf = require("rimraf"); const VinylFile = require("vinyl"); -const git = require("./git"); +const url_1 = require("url"); +const ternaryStream = require("ternary-stream"); +const git = require("./git"); // {{SQL CARBON EDIT}} - add for get version const root = path.dirname(path.dirname(__dirname)); const NoCancellationToken = { isCancellationRequested: () => false }; function incremental(streamProvider, initial, supportsCancellation) { @@ -196,6 +198,28 @@ function stripSourceMappingURL() { return es.duplex(input, output); } exports.stripSourceMappingURL = stripSourceMappingURL; +/** Splits items in the stream based on the predicate, sending them to onTrue if true, or onFalse otherwise */ +function $if(test, onTrue, onFalse = es.through()) { + if (typeof test === 'boolean') { + return test ? onTrue : onFalse; + } + return ternaryStream(test, onTrue, onFalse); +} +exports.$if = $if; +/** Operator that appends the js files' original path a sourceURL, so debug locations map */ +function appendOwnPathSourceURL() { + const input = es.through(); + const output = input + .pipe(es.mapSync(f => { + if (!(f.contents instanceof Buffer)) { + throw new Error(`contents of ${f.path} are not a buffer`); + } + f.contents = Buffer.concat([f.contents, Buffer.from(`\n//# sourceURL=${(0, url_1.pathToFileURL)(f.path)}`)]); + return f; + })); + return es.duplex(input, output); +} +exports.appendOwnPathSourceURL = appendOwnPathSourceURL; function rewriteSourceMappingURL(sourceMappingURLBase) { const input = es.through(); const output = input @@ -307,11 +331,17 @@ function acquireWebNodePaths() { const root = path.join(__dirname, '..', '..'); const webPackageJSON = path.join(root, '/remote/web', 'package.json'); const webPackages = JSON.parse(fs.readFileSync(webPackageJSON, 'utf8')).dependencies; + const distroWebPackageJson = path.join(root, '.build/distro/npm/remote/web/package.json'); + if (fs.existsSync(distroWebPackageJson)) { + const distroWebPackages = JSON.parse(fs.readFileSync(distroWebPackageJson, 'utf8')).dependencies; + Object.assign(webPackages, distroWebPackages); + } const nodePaths = {}; for (const key of Object.keys(webPackages)) { const packageJSON = path.join(root, 'node_modules', key, 'package.json'); const packageData = JSON.parse(fs.readFileSync(packageJSON, 'utf8')); - let entryPoint = typeof packageData.browser === 'string' ? packageData.browser : packageData.main ?? packageData.main; // {{SQL CARBON EDIT}} Some packages (like Turndown) have objects in this field instead of the entry point, fall back to main in that case + // Only cases where the browser is a string are handled + let entryPoint = typeof packageData.browser === 'string' ? packageData.browser : packageData.main; // On rare cases a package doesn't have an entrypoint so we assume it has a dist folder with a min.js if (!entryPoint) { // TODO @lramos15 remove this when jschardet adds an entrypoint so we can warn on all packages w/out entrypoint @@ -353,7 +383,7 @@ function createExternalLoaderConfig(webEndpoint, commit, quality) { webEndpoint = webEndpoint + `/${quality}/${commit}`; const nodePaths = acquireWebNodePaths(); Object.keys(nodePaths).map(function (key, _) { - nodePaths[key] = `${webEndpoint}/node_modules/${key}/${nodePaths[key]}`; + nodePaths[key] = `../node_modules/${key}/${nodePaths[key]}`; }); const externalLoaderConfig = { baseUrl: `${webEndpoint}/out`, @@ -384,3 +414,4 @@ function buildWebNodePaths(outDir) { return result; } exports.buildWebNodePaths = buildWebNodePaths; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInV0aWwudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOzs7QUFFaEcsbUNBQW1DO0FBQ25DLHNDQUF1QztBQUN2Qyx1Q0FBdUM7QUFDdkMsc0NBQXNDO0FBQ3RDLDZCQUE2QjtBQUM3Qix5QkFBeUI7QUFDekIsa0NBQWtDO0FBQ2xDLG1DQUFtQztBQUduQyw2QkFBb0M7QUFDcEMsZ0RBQWdEO0FBQ2hELDZCQUE2QixDQUFDLDRDQUE0QztBQUUxRSxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQU1uRCxNQUFNLG1CQUFtQixHQUF1QixFQUFFLHVCQUF1QixFQUFFLEdBQUcsRUFBRSxDQUFDLEtBQUssRUFBRSxDQUFDO0FBTXpGLFNBQWdCLFdBQVcsQ0FBQyxjQUErQixFQUFFLE9BQStCLEVBQUUsb0JBQThCO0lBQzNILE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUMzQixNQUFNLE1BQU0sR0FBRyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDNUIsSUFBSSxLQUFLLEdBQUcsTUFBTSxDQUFDO0lBQ25CLElBQUksTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFFakMsTUFBTSxLQUFLLEdBQW1DLENBQUMsb0JBQW9CLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsRUFBRSx1QkFBdUIsRUFBRSxHQUFHLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztJQUVwSixNQUFNLEdBQUcsR0FBRyxDQUFDLEtBQTZCLEVBQUUsYUFBc0IsRUFBRSxFQUFFO1FBQ3JFLEtBQUssR0FBRyxTQUFTLENBQUM7UUFFbEIsTUFBTSxNQUFN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 \ No newline at end of file diff --git a/build/lib/util.ts b/build/lib/util.ts index f0e9e0c634..5784b0338c 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -7,14 +7,15 @@ import * as es from 'event-stream'; import _debounce = require('debounce'); import * as _filter from 'gulp-filter'; import * as rename from 'gulp-rename'; -import * as _ from 'underscore'; import * as path from 'path'; import * as fs from 'fs'; import * as _rimraf from 'rimraf'; import * as VinylFile from 'vinyl'; import { ThroughStream } from 'through'; import * as sm from 'source-map'; -import * as git from './git'; +import { pathToFileURL } from 'url'; +import * as ternaryStream from 'ternary-stream'; +import * as git from './git'; // {{SQL CARBON EDIT}} - add for get version const root = path.dirname(path.dirname(__dirname)); @@ -207,8 +208,8 @@ export function loadSourcemaps(): NodeJS.ReadWriteStream { const contents = (f.contents).toString('utf8'); const reg = /\/\/# sourceMappingURL=(.*)$/g; - let lastMatch: RegExpMatchArray | null = null; - let match: RegExpMatchArray | null = null; + let lastMatch: RegExpExecArray | null = null; + let match: RegExpExecArray | null = null; while (match = reg.exec(contents)) { lastMatch = match; @@ -253,6 +254,32 @@ export function stripSourceMappingURL(): NodeJS.ReadWriteStream { return es.duplex(input, output); } +/** Splits items in the stream based on the predicate, sending them to onTrue if true, or onFalse otherwise */ +export function $if(test: boolean | ((f: VinylFile) => boolean), onTrue: NodeJS.ReadWriteStream, onFalse: NodeJS.ReadWriteStream = es.through()) { + if (typeof test === 'boolean') { + return test ? onTrue : onFalse; + } + + return ternaryStream(test, onTrue, onFalse); +} + +/** Operator that appends the js files' original path a sourceURL, so debug locations map */ +export function appendOwnPathSourceURL(): NodeJS.ReadWriteStream { + const input = es.through(); + + const output = input + .pipe(es.mapSync(f => { + if (!(f.contents instanceof Buffer)) { + throw new Error(`contents of ${f.path} are not a buffer`); + } + + f.contents = Buffer.concat([f.contents, Buffer.from(`\n//# sourceURL=${pathToFileURL(f.path)}`)]); + return f; + })); + + return es.duplex(input, output); +} + export function rewriteSourceMappingURL(sourceMappingURLBase: string): NodeJS.ReadWriteStream { const input = es.through(); @@ -378,11 +405,20 @@ export function acquireWebNodePaths() { const root = path.join(__dirname, '..', '..'); const webPackageJSON = path.join(root, '/remote/web', 'package.json'); const webPackages = JSON.parse(fs.readFileSync(webPackageJSON, 'utf8')).dependencies; + + const distroWebPackageJson = path.join(root, '.build/distro/npm/remote/web/package.json'); + if (fs.existsSync(distroWebPackageJson)) { + const distroWebPackages = JSON.parse(fs.readFileSync(distroWebPackageJson, 'utf8')).dependencies; + Object.assign(webPackages, distroWebPackages); + } + const nodePaths: { [key: string]: string } = {}; for (const key of Object.keys(webPackages)) { const packageJSON = path.join(root, 'node_modules', key, 'package.json'); const packageData = JSON.parse(fs.readFileSync(packageJSON, 'utf8')); - let entryPoint: string = typeof packageData.browser === 'string' ? packageData.browser : packageData.main ?? packageData.main; // {{SQL CARBON EDIT}} Some packages (like Turndown) have objects in this field instead of the entry point, fall back to main in that case + // Only cases where the browser is a string are handled + let entryPoint: string = typeof packageData.browser === 'string' ? packageData.browser : packageData.main; + // On rare cases a package doesn't have an entrypoint so we assume it has a dist folder with a min.js if (!entryPoint) { // TODO @lramos15 remove this when jschardet adds an entrypoint so we can warn on all packages w/out entrypoint @@ -422,16 +458,22 @@ export function acquireWebNodePaths() { return nodePaths; } -export function createExternalLoaderConfig(webEndpoint?: string, commit?: string, quality?: string) { +export interface IExternalLoaderInfo { + baseUrl: string; + paths: { [moduleId: string]: string }; + [key: string]: any; +} + +export function createExternalLoaderConfig(webEndpoint?: string, commit?: string, quality?: string): IExternalLoaderInfo | undefined { if (!webEndpoint || !commit || !quality) { return undefined; } webEndpoint = webEndpoint + `/${quality}/${commit}`; const nodePaths = acquireWebNodePaths(); Object.keys(nodePaths).map(function (key, _) { - nodePaths[key] = `${webEndpoint}/node_modules/${key}/${nodePaths[key]}`; + nodePaths[key] = `../node_modules/${key}/${nodePaths[key]}`; }); - const externalLoaderConfig = { + const externalLoaderConfig: IExternalLoaderInfo = { baseUrl: `${webEndpoint}/out`, recordStats: true, paths: nodePaths @@ -459,4 +501,3 @@ export function buildWebNodePaths(outDir: string) { result.taskName = 'build-web-node-paths'; return result; } - diff --git a/build/lib/watch/index.js b/build/lib/watch/index.js index 6d4456a74e..3c302ad686 100644 --- a/build/lib/watch/index.js +++ b/build/lib/watch/index.js @@ -7,3 +7,4 @@ const watch = process.platform === 'win32' ? require('./watch-win32') : require( module.exports = function () { return watch.apply(null, arguments); }; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7QUFFaEcsTUFBTSxLQUFLLEdBQUcsT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLENBQUM7QUFFckcsTUFBTSxDQUFDLE9BQU8sR0FBRztJQUNoQixPQUFPLEtBQUssQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0FBQ3JDLENBQUMsQ0FBQyJ9 \ No newline at end of file diff --git a/build/lib/watch/watch-win32.js b/build/lib/watch/watch-win32.js index f63e48dd91..6fa161b810 100644 --- a/build/lib/watch/watch-win32.js +++ b/build/lib/watch/watch-win32.js @@ -98,3 +98,4 @@ module.exports = function (pattern, options) { })) .pipe(rebase); }; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2F0Y2gtd2luMzIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ3YXRjaC13aW4zMi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7O0FBRWhHLDZCQUE2QjtBQUM3QixvQ0FBb0M7QUFDcEMseUJBQXlCO0FBQ3pCLDhCQUE4QjtBQUM5QixtQ0FBbUM7QUFDbkMsc0NBQXNDO0FBR3RDLE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLGFBQWEsQ0FBQyxDQUFDO0FBRXhELFNBQVMsWUFBWSxDQUFDLElBQXFCO0lBQzFDLFFBQVEsSUFBSSxFQUFFO1FBQ2IsS0FBSyxHQUFHLENBQUMsQ0FBQyxPQUFPLFFBQVEsQ0FBQztRQUMxQixLQUFLLEdBQUcsQ0FBQyxDQUFDLE9BQU8sS0FBSyxDQUFDO1FBQ3ZCLE9BQU8sQ0FBQyxDQUFDLE9BQU8sUUFBUSxDQUFDO0tBQ3pCO0FBQ0YsQ0FBQztBQUVELFNBQVMsS0FBSyxDQUFDLElBQVk7SUFDMUIsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQzVCLElBQUksS0FBSyxHQUEyQixFQUFFLENBQUMsS0FBSyxDQUFDLFdBQVcsRUFBRSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7SUFFbEUsS0FBSyxDQUFDLE1BQU8sQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLFVBQVUsSUFBSTtRQUN0QyxNQUFNLEtBQUssR0FBYSxJQUFJLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMxRCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUN0QyxNQUFNLElBQUksR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDN0IsSUFBSSxJQUFJLENBQUMsTUFBTSxL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 \ No newline at end of file diff --git a/build/linux/debian/calculate-deps.js b/build/linux/debian/calculate-deps.js new file mode 100644 index 0000000000..d9839581a8 --- /dev/null +++ b/build/linux/debian/calculate-deps.js @@ -0,0 +1,80 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generatePackageDeps = void 0; +const child_process_1 = require("child_process"); +const fs_1 = require("fs"); +const os_1 = require("os"); +const path = require("path"); +const manifests = require("../../../cgmanifest.json"); +const dep_lists_1 = require("./dep-lists"); +function generatePackageDeps(files, arch, sysroot) { + const dependencies = files.map(file => calculatePackageDeps(file, arch, sysroot)); + const additionalDepsSet = new Set(dep_lists_1.additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; +} +exports.generatePackageDeps = generatePackageDeps; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. +function calculatePackageDeps(binaryPath, arch, sysroot) { + try { + if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } + catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + // Get the Chromium dpkg-shlibdeps file. + const chromiumManifest = manifests.registrations.filter(registration => { + return registration.component.type === 'git' && registration.component.git.name === 'chromium'; + }); + const dpkgShlibdepsUrl = `https://raw.githubusercontent.com/chromium/chromium/${chromiumManifest[0].version}/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl`; + const dpkgShlibdepsScriptLocation = `${(0, os_1.tmpdir)()}/dpkg-shlibdeps.pl`; + const result = (0, child_process_1.spawnSync)('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); + } + const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; + switch (arch) { + case 'amd64': + cmd.push(`-l${sysroot}/usr/lib/x86_64-linux-gnu`, `-l${sysroot}/lib/x86_64-linux-gnu`); + break; + case 'armhf': + cmd.push(`-l${sysroot}/usr/lib/arm-linux-gnueabihf`, `-l${sysroot}/lib/arm-linux-gnueabihf`); + break; + case 'arm64': + cmd.push(`-l${sysroot}/usr/lib/aarch64-linux-gnu`, `-l${sysroot}/lib/aarch64-linux-gnu`); + break; + } + cmd.push(`-l${sysroot}/usr/lib`); + cmd.push('-O', '-e', path.resolve(binaryPath)); + const dpkgShlibdepsResult = (0, child_process_1.spawnSync)('perl', cmd, { cwd: sysroot }); + if (dpkgShlibdepsResult.status !== 0) { + throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); + } + const shlibsDependsPrefix = 'shlibs:Depends='; + const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); + let depsStr = ''; + for (const line of requiresList) { + if (line.startsWith(shlibsDependsPrefix)) { + depsStr = line.substring(shlibsDependsPrefix.length); + } + } + // Refs https://chromium-review.googlesource.com/c/chromium/src/+/3572926 + // Chromium depends on libgcc_s, is from the package libgcc1. However, in + // Bullseye, the package was renamed to libgcc-s1. To avoid adding a dep + // on the newer package, this hack skips the dep. This is safe because + // libgcc-s1 is a dependency of libc6. This hack can be removed once + // support for Debian Buster and Ubuntu Bionic are dropped. + const filteredDeps = depsStr.split(', ').filter(dependency => { + return !dependency.startsWith('libgcc-s1'); + }).sort(); + const requires = new Set(filteredDeps); + return requires; +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FsY3VsYXRlLWRlcHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjYWxjdWxhdGUtZGVwcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxpREFBMEM7QUFDMUMsMkJBQXlDO0FBQ3pDLDJCQUE0QjtBQUM1Qiw2QkFBOEI7QUFDOUIsc0RBQXNEO0FBQ3RELDJDQUE2QztBQUc3QyxTQUFnQixtQkFBbUIsQ0FBQyxLQUFlLEVBQUUsSUFBc0IsRUFBRSxPQUFlO0lBQzNGLE1BQU0sWUFBWSxHQUFrQixLQUFLLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsb0JBQW9CLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDO0lBQ2pHLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxHQUFHLENBQUMsMEJBQWMsQ0FBQyxDQUFDO0lBQ2xELFlBQVksQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQztJQUNyQyxPQUFPLFlBQVksQ0FBQztBQUNyQixDQUFDO0FBTEQsa0RBS0M7QUFFRCw2SEFBNkg7QUFDN0gsU0FBUyxvQkFBb0IsQ0FBQyxVQUFrQixFQUFFLElBQXNCLEVBQUUsT0FBZTtJQUN4RixJQUFJO1FBQ0gsSUFBSSxDQUFDLENBQUMsSUFBQSxhQUFRLEVBQUMsVUFBVSxDQUFDLENBQUMsSUFBSSxHQUFHLGNBQVMsQ0FBQyxPQUFPLENBQUMsRUFBRTtZQUNyRCxNQUFNLElBQUksS0FBSyxDQUFDLFVBQVUsVUFBVSx1Q0FBdUMsQ0FBQyxDQUFDO1NBQzdFO0tBQ0Q7SUFBQyxPQUFPLENBQUMsRUFBRTtRQUNYLDhEQUE4RDtRQUM5RCxPQUFPLENBQUMsS0FBSyxDQUFDLGdCQUFnQixHQUFHLFVBQVUsR0FBRyxjQUFjLENBQUMsQ0FBQztLQUM5RDtJQUVELHdDQUF3QztJQUN4QyxNQUFNLGdCQUFnQixHQUFHLFNBQVMsQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLFlB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 \ No newline at end of file diff --git a/build/linux/debian/calculate-deps.ts b/build/linux/debian/calculate-deps.ts new file mode 100644 index 0000000000..38b6fa1c37 --- /dev/null +++ b/build/linux/debian/calculate-deps.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawnSync } from 'child_process'; +import { constants, statSync } from 'fs'; +import { tmpdir } from 'os'; +import path = require('path'); +import * as manifests from '../../../cgmanifest.json'; +import { additionalDeps } from './dep-lists'; +import { DebianArchString } from './types'; + +export function generatePackageDeps(files: string[], arch: DebianArchString, sysroot: string): Set[] { + const dependencies: Set[] = files.map(file => calculatePackageDeps(file, arch, sysroot)); + const additionalDepsSet = new Set(additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; +} + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. +function calculatePackageDeps(binaryPath: string, arch: DebianArchString, sysroot: string): Set { + try { + if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + + // Get the Chromium dpkg-shlibdeps file. + const chromiumManifest = manifests.registrations.filter(registration => { + return registration.component.type === 'git' && registration.component.git!.name === 'chromium'; + }); + const dpkgShlibdepsUrl = `https://raw.githubusercontent.com/chromium/chromium/${chromiumManifest[0].version}/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl`; + const dpkgShlibdepsScriptLocation = `${tmpdir()}/dpkg-shlibdeps.pl`; + const result = spawnSync('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); + } + const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; + switch (arch) { + case 'amd64': + cmd.push(`-l${sysroot}/usr/lib/x86_64-linux-gnu`, + `-l${sysroot}/lib/x86_64-linux-gnu`); + break; + case 'armhf': + cmd.push(`-l${sysroot}/usr/lib/arm-linux-gnueabihf`, + `-l${sysroot}/lib/arm-linux-gnueabihf`); + break; + case 'arm64': + cmd.push(`-l${sysroot}/usr/lib/aarch64-linux-gnu`, + `-l${sysroot}/lib/aarch64-linux-gnu`); + break; + } + cmd.push(`-l${sysroot}/usr/lib`); + cmd.push('-O', '-e', path.resolve(binaryPath)); + + const dpkgShlibdepsResult = spawnSync('perl', cmd, { cwd: sysroot }); + if (dpkgShlibdepsResult.status !== 0) { + throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); + } + + const shlibsDependsPrefix = 'shlibs:Depends='; + const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); + let depsStr = ''; + for (const line of requiresList) { + if (line.startsWith(shlibsDependsPrefix)) { + depsStr = line.substring(shlibsDependsPrefix.length); + } + } + // Refs https://chromium-review.googlesource.com/c/chromium/src/+/3572926 + // Chromium depends on libgcc_s, is from the package libgcc1. However, in + // Bullseye, the package was renamed to libgcc-s1. To avoid adding a dep + // on the newer package, this hack skips the dep. This is safe because + // libgcc-s1 is a dependency of libc6. This hack can be removed once + // support for Debian Buster and Ubuntu Bionic are dropped. + const filteredDeps = depsStr.split(', ').filter(dependency => { + return !dependency.startsWith('libgcc-s1'); + }).sort(); + const requires = new Set(filteredDeps); + return requires; +} diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js index 24349a9b3b..21fb708cc1 100644 --- a/build/linux/debian/dep-lists.js +++ b/build/linux/debian/dep-lists.js @@ -4,7 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.bundledDeps = exports.recommendedDeps = exports.additionalDeps = void 0; +exports.referenceGeneratedDepsByArch = exports.recommendedDeps = exports.additionalDeps = void 0; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps // Additional dependencies not in the dpkg-shlibdeps output. exports.additionalDeps = [ @@ -20,22 +20,10 @@ exports.additionalDeps = [ exports.recommendedDeps = [ 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. ]; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 -// and the Linux Archive build -// Shared library dependencies that we already bundle. -exports.bundledDeps = [ - 'libEGL.so', - 'libGLESv2.so', - 'libvulkan.so.1', - 'swiftshader_libEGL.so', - 'swiftshader_libGLESv2.so', - 'libvk_swiftshader.so', - 'libffmpeg.so' -]; exports.referenceGeneratedDepsByArch = { 'amd64': [ 'ca-certificates', - 'libasound2 (>= 1.0.16)', + 'libasound2 (>= 1.0.17)', 'libatk-bridge2.0-0 (>= 2.5.3)', 'libatk1.0-0 (>= 2.2.0)', 'libatspi2.0-0 (>= 2.9.90)', @@ -45,10 +33,9 @@ exports.referenceGeneratedDepsByArch = { 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', 'libdbus-1-3 (>= 1.5.12)', - 'libdrm2 (>= 2.4.38)', + 'libdrm2 (>= 2.4.60)', 'libexpat1 (>= 2.0.1)', - 'libgbm1 (>= 8.1~0)', - 'libgcc1 (>= 1:3.0)', + 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.16.0)', 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', @@ -72,21 +59,20 @@ exports.referenceGeneratedDepsByArch = { ], 'armhf': [ 'ca-certificates', - 'libasound2 (>= 1.0.16)', + 'libasound2 (>= 1.0.17)', 'libatk-bridge2.0-0 (>= 2.5.3)', 'libatk1.0-0 (>= 2.2.0)', 'libatspi2.0-0 (>= 2.9.90)', 'libc6 (>= 2.17)', + 'libc6 (>= 2.28)', 'libc6 (>= 2.4)', 'libc6 (>= 2.9)', 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', 'libdbus-1-3 (>= 1.5.12)', - 'libdrm2 (>= 2.4.38)', + 'libdrm2 (>= 2.4.60)', 'libexpat1 (>= 2.0.1)', - 'libgbm1 (>= 8.1~0)', - 'libgcc1 (>= 1:3.0)', - 'libgcc1 (>= 1:3.5)', + 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.16.0)', 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', @@ -114,20 +100,18 @@ exports.referenceGeneratedDepsByArch = { ], 'arm64': [ 'ca-certificates', - 'libasound2 (>= 1.0.16)', + 'libasound2 (>= 1.0.17)', 'libatk-bridge2.0-0 (>= 2.5.3)', 'libatk1.0-0 (>= 2.2.0)', 'libatspi2.0-0 (>= 2.9.90)', 'libc6 (>= 2.17)', + 'libc6 (>= 2.28)', 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', 'libdbus-1-3 (>= 1.0.2)', - 'libdrm2 (>= 2.4.38)', + 'libdrm2 (>= 2.4.60)', 'libexpat1 (>= 2.0.1)', - 'libgbm1 (>= 8.1~0)', - 'libgcc1 (>= 1:3.0)', - 'libgcc1 (>= 1:4.2)', - 'libgcc1 (>= 1:4.5)', + 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.16.0)', 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', @@ -154,3 +138,4 @@ exports.referenceGeneratedDepsByArch = { 'xdg-utils (>= 1.0.2)' ] }; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGtIQUFrSDtBQUNsSCw0REFBNEQ7QUFDL0MsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHFDQUFxQztJQUNyQyxtQkFBbUI7SUFDbkIsc0RBQXNEO0lBQ3RELHNCQUFzQixDQUFDLGlCQUFpQjtDQUN4QyxDQUFDO0FBRUYsb0hBQW9IO0FBQ3BILDBDQUEwQztBQUMxQyw4REFBOEQ7QUFDakQsUUFBQSxlQUFlLEdBQUc7SUFDOUIsWUFBWSxDQUFDLHlFQUF5RTtDQUN0RixDQUFDO0FBRVcsUUFBQSw0QkFBNEIsR0FBRztJQUMzQyxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsa0JBQWtCO1FBQ2xCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQiwwQkFBMEI7UUFDMUIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyx3QkFBd0I7UUFDeEIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQiw0QkFBNEI7UUFDNUIseUJBQXlCO1FBQ3pCLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsZ0JBQWdCO1FBQ2hCLGdCQUFnQjtRQUNoQixzQkFBc0I7UUFDdEIsc0RBQXNEO1FBQ3RELHlCQUF5QjtRQUN6QixxQkFBcUI7UUFDckIsc0JBQXNCO1FBQ3RCLHlCQUF5QjtRQUN6QiwwQkFBMEI7UUFDMUIsMEJBQTBCO1FBQzFCLHdCQUF3QjtRQUN4QixxQ0FBcUM7UUFDckMsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLHlCQUF5QjtRQUN6Qix1QkFBdUI7UUFDdkIsbUJBQW1CO1FBQ25CLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsVUFBVTtRQUNWLDBCQUEwQjtRQUMxQixvQkFBb0I7UUFDcEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QixVQUFVO1FBQ1YsWUFBWTtRQUNaLDBCQUEwQjtRQUMxQixhQUFhO1FBQ2IsWUFBWTtRQUNaLHNCQUFzQjtLQUN0QjtJQUNELE9BQU8sRUFBRTtRQUNSLGlCQUFpQjtRQUNqQix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QiwyQkFBMkI7UUFDM0IsaUJBQWlCO1FBQ2pCLGlCQUFpQjtRQUNqQixzQkFBc0I7UUFDdEIsc0RBQXNEO1FBQ3RELHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsc0JBQXNCO1FBQ3RCLHlCQUF5QjtRQUN6QiwwQkFBMEI7UUFDMUIsMEJBQTBCO1FBQzFCLHdCQUF3QjtRQUN4QixxQ0FBcUM7UUFDckMsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsNEJBQTRCO1FBQzVCLHlCQUF5QjtRQUN6Qix1QkFBdUI7UUFDdkIsbUJBQW1CO1FBQ25CLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsVUFBVTtRQUNWLDBCQUEwQjtRQUMxQixvQkFBb0I7UUFDcEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QixVQUFVO1FBQ1YsWUFBWTtRQUNaLDBCQUEwQjtRQUMxQixhQUFhO1FBQ2IsWUFBWTtRQUNaLHNCQUFzQjtLQUN0QjtDQUNELENBQUMifQ== \ No newline at end of file diff --git a/build/linux/debian/dep-lists.ts b/build/linux/debian/dep-lists.ts index b86b5609e2..e04077c41f 100644 --- a/build/linux/debian/dep-lists.ts +++ b/build/linux/debian/dep-lists.ts @@ -20,23 +20,10 @@ export const recommendedDeps = [ 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. ]; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 -// and the Linux Archive build -// Shared library dependencies that we already bundle. -export const bundledDeps = [ - 'libEGL.so', - 'libGLESv2.so', - 'libvulkan.so.1', - 'swiftshader_libEGL.so', - 'swiftshader_libGLESv2.so', - 'libvk_swiftshader.so', - 'libffmpeg.so' -]; - export const referenceGeneratedDepsByArch = { 'amd64': [ 'ca-certificates', - 'libasound2 (>= 1.0.16)', + 'libasound2 (>= 1.0.17)', 'libatk-bridge2.0-0 (>= 2.5.3)', 'libatk1.0-0 (>= 2.2.0)', 'libatspi2.0-0 (>= 2.9.90)', @@ -46,10 +33,9 @@ export const referenceGeneratedDepsByArch = { 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', 'libdbus-1-3 (>= 1.5.12)', - 'libdrm2 (>= 2.4.38)', + 'libdrm2 (>= 2.4.60)', 'libexpat1 (>= 2.0.1)', - 'libgbm1 (>= 8.1~0)', - 'libgcc1 (>= 1:3.0)', + 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.16.0)', 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', @@ -73,21 +59,20 @@ export const referenceGeneratedDepsByArch = { ], 'armhf': [ 'ca-certificates', - 'libasound2 (>= 1.0.16)', + 'libasound2 (>= 1.0.17)', 'libatk-bridge2.0-0 (>= 2.5.3)', 'libatk1.0-0 (>= 2.2.0)', 'libatspi2.0-0 (>= 2.9.90)', 'libc6 (>= 2.17)', + 'libc6 (>= 2.28)', 'libc6 (>= 2.4)', 'libc6 (>= 2.9)', 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', 'libdbus-1-3 (>= 1.5.12)', - 'libdrm2 (>= 2.4.38)', + 'libdrm2 (>= 2.4.60)', 'libexpat1 (>= 2.0.1)', - 'libgbm1 (>= 8.1~0)', - 'libgcc1 (>= 1:3.0)', - 'libgcc1 (>= 1:3.5)', + 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.16.0)', 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', @@ -115,20 +100,18 @@ export const referenceGeneratedDepsByArch = { ], 'arm64': [ 'ca-certificates', - 'libasound2 (>= 1.0.16)', + 'libasound2 (>= 1.0.17)', 'libatk-bridge2.0-0 (>= 2.5.3)', 'libatk1.0-0 (>= 2.2.0)', 'libatspi2.0-0 (>= 2.9.90)', 'libc6 (>= 2.17)', + 'libc6 (>= 2.28)', 'libcairo2 (>= 1.6.0)', 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', 'libdbus-1-3 (>= 1.0.2)', - 'libdrm2 (>= 2.4.38)', + 'libdrm2 (>= 2.4.60)', 'libexpat1 (>= 2.0.1)', - 'libgbm1 (>= 8.1~0)', - 'libgcc1 (>= 1:3.0)', - 'libgcc1 (>= 1:4.2)', - 'libgcc1 (>= 1:4.5)', + 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.16.0)', 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', diff --git a/build/linux/debian/dependencies-generator.js b/build/linux/debian/dependencies-generator.js deleted file mode 100644 index dac26a4f05..0000000000 --- a/build/linux/debian/dependencies-generator.js +++ /dev/null @@ -1,130 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getDependencies = void 0; -const child_process_1 = require("child_process"); -const fs_1 = require("fs"); -const os_1 = require("os"); -const path = require("path"); -const dep_lists_1 = require("./dep-lists"); // {{SQL CARBON EDIT}} Not needed -// A flag that can easily be toggled. -// Make sure to compile the build directory after toggling the value. -// If false, we warn about new dependencies if they show up -// while running the Debian prepare package task for a release. -// If true, we fail the build if there are new dependencies found during that task. -// The reference dependencies, which one has to update when the new dependencies -// are valid, are in dep-lists.ts -// const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; {{ SQL CARBON EDIT}} Not needed -function getDependencies(buildDir, applicationName, arch, sysroot) { - // Get the files for which we want to find dependencies. - const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); - const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']); - if (findResult.status) { - console.error('Error finding files:'); - console.error(findResult.stderr.toString()); - return []; - } - const files = findResult.stdout.toString().trimEnd().split('\n'); - const appPath = path.join(buildDir, applicationName); - files.push(appPath); - // Add chrome sandbox and crashpad handler. - files.push(path.join(buildDir, 'chrome-sandbox')); - files.push(path.join(buildDir, 'chrome_crashpad_handler')); - // Generate the dependencies. - const dependencies = files.map((file) => calculatePackageDeps(file, arch, sysroot)); - // Add additional dependencies. - const additionalDepsSet = new Set(dep_lists_1.additionalDeps); - dependencies.push(additionalDepsSet); - // Merge all the dependencies. - const mergedDependencies = mergePackageDeps(dependencies); - let sortedDependencies = []; - for (const dependency of mergedDependencies) { - sortedDependencies.push(dependency); - } - sortedDependencies.sort(); - // Exclude bundled dependencies - sortedDependencies = sortedDependencies.filter(dependency => { - return !dep_lists_1.bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); - }); - /* {{SQL CARBON EDIT}} Not needed - const referenceGeneratedDeps = referenceGeneratedDepsByArch[arch]; - if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { - const failMessage = 'The dependencies list has changed.' - + '\nOld:\n' + referenceGeneratedDeps.join('\n') - + '\nNew:\n' + sortedDependencies.join('\n'); - if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { - throw new Error(failMessage); - } else { - console.warn(failMessage); - } - } - */ - return sortedDependencies; -} -exports.getDependencies = getDependencies; -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. -function calculatePackageDeps(binaryPath, arch, sysroot) { - try { - if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } - catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - // Get the Chromium dpkg-shlibdeps file. - const dpkgShlibdepsUrl = 'https://raw.githubusercontent.com/chromium/chromium/100.0.4896.160/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl'; - const dpkgShlibdepsScriptLocation = `${(0, os_1.tmpdir)()}/dpkg-shlibdeps.pl`; - const result = (0, child_process_1.spawnSync)('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); - if (result.status !== 0) { - throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); - } - const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; - switch (arch) { - case 'amd64': - cmd.push(`-l${sysroot}/usr/lib/x86_64-linux-gnu`, `-l${sysroot}/lib/x86_64-linux-gnu`); - break; - case 'armhf': - cmd.push(`-l${sysroot}/usr/lib/arm-linux-gnueabihf`, `-l${sysroot}/lib/arm-linux-gnueabihf`); - break; - case 'arm64': - cmd.push(`-l${sysroot}/usr/lib/aarch64-linux-gnu`, `-l${sysroot}/lib/aarch64-linux-gnu`); - break; - default: - throw new Error('Unsupported architecture ' + arch); - } - cmd.push(`-l${sysroot}/usr/lib`); - cmd.push('-O', '-e', path.resolve(binaryPath)); - const dpkgShlibdepsResult = (0, child_process_1.spawnSync)('perl', cmd, { cwd: sysroot }); - if (dpkgShlibdepsResult.status !== 0) { - throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); - } - const shlibsDependsPrefix = 'shlibs:Depends='; - const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); - let depsStr = ''; - for (const line of requiresList) { - if (line.startsWith(shlibsDependsPrefix)) { - depsStr = line.substring(shlibsDependsPrefix.length); - } - } - const requires = new Set(depsStr.split(', ').sort()); - return requires; -} -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. -function mergePackageDeps(inputDeps) { - // For now, see if directly appending the dependencies helps. - const requires = new Set(); - for (const depSet of inputDeps) { - for (const dep of depSet) { - const trimmedDependency = dep.trim(); - if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { - requires.add(trimmedDependency); - } - } - } - return requires; -} diff --git a/build/linux/debian/dependencies-generator.ts b/build/linux/debian/dependencies-generator.ts deleted file mode 100644 index cc29607a4a..0000000000 --- a/build/linux/debian/dependencies-generator.ts +++ /dev/null @@ -1,147 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import { spawnSync } from 'child_process'; -import { constants, statSync } from 'fs'; -import { tmpdir } from 'os'; -import path = require('path'); -import { additionalDeps, bundledDeps/*, referenceGeneratedDepsByArch*/ } from './dep-lists'; // {{SQL CARBON EDIT}} Not needed -import { ArchString } from './types'; - -// A flag that can easily be toggled. -// Make sure to compile the build directory after toggling the value. -// If false, we warn about new dependencies if they show up -// while running the Debian prepare package task for a release. -// If true, we fail the build if there are new dependencies found during that task. -// The reference dependencies, which one has to update when the new dependencies -// are valid, are in dep-lists.ts -// const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; {{ SQL CARBON EDIT}} Not needed - -export function getDependencies(buildDir: string, applicationName: string, arch: ArchString, sysroot: string): string[] { - // Get the files for which we want to find dependencies. - const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); - const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); - if (findResult.status) { - console.error('Error finding files:'); - console.error(findResult.stderr.toString()); - return []; - } - - const files = findResult.stdout.toString().trimEnd().split('\n'); - - const appPath = path.join(buildDir, applicationName); - files.push(appPath); - - // Add chrome sandbox and crashpad handler. - files.push(path.join(buildDir, 'chrome-sandbox')); - files.push(path.join(buildDir, 'chrome_crashpad_handler')); - - // Generate the dependencies. - const dependencies: Set[] = files.map((file) => calculatePackageDeps(file, arch, sysroot)); - // Add additional dependencies. - const additionalDepsSet = new Set(additionalDeps); - dependencies.push(additionalDepsSet); - - // Merge all the dependencies. - const mergedDependencies = mergePackageDeps(dependencies); - let sortedDependencies: string[] = []; - for (const dependency of mergedDependencies) { - sortedDependencies.push(dependency); - } - sortedDependencies.sort(); - - // Exclude bundled dependencies - sortedDependencies = sortedDependencies.filter(dependency => { - return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); - }); - - /* {{SQL CARBON EDIT}} Not needed - const referenceGeneratedDeps = referenceGeneratedDepsByArch[arch]; - if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { - const failMessage = 'The dependencies list has changed.' - + '\nOld:\n' + referenceGeneratedDeps.join('\n') - + '\nNew:\n' + sortedDependencies.join('\n'); - if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { - throw new Error(failMessage); - } else { - console.warn(failMessage); - } - } - */ - - return sortedDependencies; -} - -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. -function calculatePackageDeps(binaryPath: string, arch: ArchString, sysroot: string): Set { - try { - if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - - // Get the Chromium dpkg-shlibdeps file. - const dpkgShlibdepsUrl = 'https://raw.githubusercontent.com/chromium/chromium/100.0.4896.160/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl'; - const dpkgShlibdepsScriptLocation = `${tmpdir()}/dpkg-shlibdeps.pl`; - const result = spawnSync('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); - if (result.status !== 0) { - throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); - } - const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; - switch (arch) { - case 'amd64': - cmd.push(`-l${sysroot}/usr/lib/x86_64-linux-gnu`, - `-l${sysroot}/lib/x86_64-linux-gnu`); - break; - case 'armhf': - cmd.push(`-l${sysroot}/usr/lib/arm-linux-gnueabihf`, - `-l${sysroot}/lib/arm-linux-gnueabihf`); - break; - case 'arm64': - cmd.push(`-l${sysroot}/usr/lib/aarch64-linux-gnu`, - `-l${sysroot}/lib/aarch64-linux-gnu`); - break; - default: - throw new Error('Unsupported architecture ' + arch); - } - cmd.push(`-l${sysroot}/usr/lib`); - cmd.push('-O', '-e', path.resolve(binaryPath)); - - const dpkgShlibdepsResult = spawnSync('perl', cmd, { cwd: sysroot }); - if (dpkgShlibdepsResult.status !== 0) { - throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); - } - - const shlibsDependsPrefix = 'shlibs:Depends='; - const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); - let depsStr = ''; - for (const line of requiresList) { - if (line.startsWith(shlibsDependsPrefix)) { - depsStr = line.substring(shlibsDependsPrefix.length); - } - } - const requires = new Set(depsStr.split(', ').sort()); - return requires; -} - -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. -function mergePackageDeps(inputDeps: Set[]): Set { - // For now, see if directly appending the dependencies helps. - const requires = new Set(); - for (const depSet of inputDeps) { - for (const dep of depSet) { - const trimmedDependency = dep.trim(); - if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { - requires.add(trimmedDependency); - } - } - } - return requires; -} diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js index f848ee070d..badf449e30 100644 --- a/build/linux/debian/install-sysroot.js +++ b/build/linux/debian/install-sysroot.js @@ -11,7 +11,7 @@ const os_1 = require("os"); const fs = require("fs"); const https = require("https"); const path = require("path"); -const sysroots_1 = require("./sysroots"); +const util = require("../../lib/util"); // Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. const URL_PREFIX = 'https://msftelectron.blob.core.windows.net'; const URL_PATH = 'sysroots/toolchain'; @@ -30,7 +30,15 @@ function getSha(filename) { return hash.digest('hex'); } async function getSysroot(arch) { - const sysrootDict = sysroots_1.sysrootInfo[arch]; + const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${util.getElectronVersion()}/script/sysroots.json`; + const sysrootDictLocation = `${(0, os_1.tmpdir)()}/sysroots.json`; + const result = (0, child_process_1.spawnSync)('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr); + } + const sysrootInfo = require(sysrootDictLocation); + const sysrootArch = arch === 'armhf' ? 'bullseye_arm' : `bullseye_${arch}`; + const sysrootDict = sysrootInfo[sysrootArch]; const tarballFilename = sysrootDict['Tarball']; const tarballSha = sysrootDict['Sha1Sum']; const sysroot = path.join((0, os_1.tmpdir)(), sysrootDict['SysrootDir']); @@ -46,14 +54,13 @@ async function getSysroot(arch) { console.log(`Downloading ${url}`); let downloadSuccess = false; for (let i = 0; i < 3 && !downloadSuccess; i++) { + fs.writeFileSync(tarball, ''); await new Promise((c) => { https.get(url, (res) => { - const chunks = []; res.on('data', (chunk) => { - chunks.push(chunk); + fs.appendFileSync(tarball, chunk); }); res.on('end', () => { - fs.writeFileSync(tarball, Buffer.concat(chunks)); downloadSuccess = true; c(); }); @@ -64,6 +71,7 @@ async function getSysroot(arch) { }); } if (!downloadSuccess) { + fs.rmSync(tarball); throw new Error('Failed to download ' + url); } const sha = getSha(tarball); @@ -79,3 +87,4 @@ async function getSysroot(arch) { return sysroot; } exports.getSysroot = getSysroot; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5zdGFsbC1zeXNyb290LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiaW5zdGFsbC1zeXNyb290LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGlEQUEwQztBQUMxQyxtQ0FBb0M7QUFDcEMsMkJBQTRCO0FBQzVCLHlCQUF5QjtBQUN6QiwrQkFBK0I7QUFDL0IsNkJBQTZCO0FBRTdCLHVDQUF1QztBQUV2QyxvSEFBb0g7QUFDcEgsTUFBTSxVQUFVLEdBQUcsNENBQTRDLENBQUM7QUFDaEUsTUFBTSxRQUFRLEdBQUcsb0JBQW9CLENBQUM7QUFFdEMsU0FBUyxNQUFNLENBQUMsUUFBcUI7SUFDcEMsTUFBTSxJQUFJLEdBQUcsSUFBQSxtQkFBVSxFQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2hDLDJCQUEyQjtJQUMzQixNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUMsUUFBUSxDQUFDLFFBQVEsRUFBRSxHQUFHLENBQUMsQ0FBQztJQUN0QyxNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUMsQ0FBQztJQUN6QyxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFDakIsSUFBSSxTQUFTLEdBQUcsQ0FBQyxDQUFDO0lBQ2xCLE9BQU8sQ0FBQyxTQUFTLEdBQUcsRUFBRSxDQUFDLFFBQVEsQ0FBQyxFQUFFLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxNQUFNLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDLEtBQUssTUFBTSxDQUFDLE1BQU0sRUFBRTtRQUMzRixJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ3BCLFFBQVEsSUFBSSxTQUFTLENBQUM7S0FDdEI7SUFDRCxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUM7SUFDeEMsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFD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 \ No newline at end of file diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index 0f170fc2be..e074128b61 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -9,8 +9,8 @@ import { tmpdir } from 'os'; import * as fs from 'fs'; import * as https from 'https'; import * as path from 'path'; -import { sysrootInfo } from './sysroots'; -import { ArchString } from './types'; +import { DebianArchString } from './types'; +import * as util from '../../lib/util'; // Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. const URL_PREFIX = 'https://msftelectron.blob.core.windows.net'; @@ -37,8 +37,16 @@ type SysrootDictEntry = { Tarball: string; }; -export async function getSysroot(arch: ArchString): Promise { - const sysrootDict: SysrootDictEntry = sysrootInfo[arch]; +export async function getSysroot(arch: DebianArchString): Promise { + const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${util.getElectronVersion()}/script/sysroots.json`; + const sysrootDictLocation = `${tmpdir()}/sysroots.json`; + const result = spawnSync('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); + if (result.status !== 0) { + throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr); + } + const sysrootInfo = require(sysrootDictLocation); + const sysrootArch = arch === 'armhf' ? 'bullseye_arm' : `bullseye_${arch}`; + const sysrootDict: SysrootDictEntry = sysrootInfo[sysrootArch]; const tarballFilename = sysrootDict['Tarball']; const tarballSha = sysrootDict['Sha1Sum']; const sysroot = path.join(tmpdir(), sysrootDict['SysrootDir']); @@ -55,14 +63,13 @@ export async function getSysroot(arch: ArchString): Promise { console.log(`Downloading ${url}`); let downloadSuccess = false; for (let i = 0; i < 3 && !downloadSuccess; i++) { + fs.writeFileSync(tarball, ''); await new Promise((c) => { https.get(url, (res) => { - const chunks: Uint8Array[] = []; res.on('data', (chunk) => { - chunks.push(chunk); + fs.appendFileSync(tarball, chunk); }); res.on('end', () => { - fs.writeFileSync(tarball, Buffer.concat(chunks)); downloadSuccess = true; c(); }); @@ -73,6 +80,7 @@ export async function getSysroot(arch: ArchString): Promise { }); } if (!downloadSuccess) { + fs.rmSync(tarball); throw new Error('Failed to download ' + url); } const sha = getSha(tarball); diff --git a/build/linux/debian/sysroots.js b/build/linux/debian/sysroots.js deleted file mode 100644 index 9a4086d367..0000000000 --- a/build/linux/debian/sysroots.js +++ /dev/null @@ -1,26 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.sysrootInfo = void 0; -// Based on https://github.com/electron/electron/blob/main/script/sysroots.json, -// which itself is based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/sysroots.json. -exports.sysrootInfo = { - 'amd64': { - 'Sha1Sum': '7e008cea9eae822d80d55c67fbb5ef4204678e74', - 'SysrootDir': 'debian_sid_amd64-sysroot', - 'Tarball': 'debian_sid_amd64_sysroot.tar.xz' - }, - 'armhf': { - 'Sha1Sum': 'b6f4bb07817bea91b06514a9c1e3832df5a90dbf', - 'SysrootDir': 'debian_sid_arm-sysroot', - 'Tarball': 'debian_sid_arm_sysroot.tar.xz' - }, - 'arm64': { - 'Sha1Sum': '5a56c1ef714154ea5003bcafb16f21b0f8dde023', - 'SysrootDir': 'debian_sid_arm64-sysroot', - 'Tarball': 'debian_sid_arm64_sysroot.tar.xz' - } -}; diff --git a/build/linux/debian/sysroots.ts b/build/linux/debian/sysroots.ts deleted file mode 100644 index 8b21431f7e..0000000000 --- a/build/linux/debian/sysroots.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// Based on https://github.com/electron/electron/blob/main/script/sysroots.json, -// which itself is based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/sysroots.json. -export const sysrootInfo = { - 'amd64': { - 'Sha1Sum': '7e008cea9eae822d80d55c67fbb5ef4204678e74', - 'SysrootDir': 'debian_sid_amd64-sysroot', - 'Tarball': 'debian_sid_amd64_sysroot.tar.xz' - }, - 'armhf': { - 'Sha1Sum': 'b6f4bb07817bea91b06514a9c1e3832df5a90dbf', - 'SysrootDir': 'debian_sid_arm-sysroot', - 'Tarball': 'debian_sid_arm_sysroot.tar.xz' - }, - 'arm64': { - 'Sha1Sum': '5a56c1ef714154ea5003bcafb16f21b0f8dde023', - 'SysrootDir': 'debian_sid_arm64-sysroot', - 'Tarball': 'debian_sid_arm64_sysroot.tar.xz' - } -}; diff --git a/build/linux/debian/types.js b/build/linux/debian/types.js index bda3e6b964..e44990ff00 100644 --- a/build/linux/debian/types.js +++ b/build/linux/debian/types.js @@ -4,3 +4,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.isDebianArchString = void 0; +function isDebianArchString(s) { + return ['amd64', 'armhf', 'arm64'].includes(s); +} +exports.isDebianArchString = isDebianArchString; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUloRyxTQUFnQixrQkFBa0IsQ0FBQyxDQUFTO0lBQzNDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNoRCxDQUFDO0FBRkQsZ0RBRUMifQ== \ No newline at end of file diff --git a/build/linux/debian/types.ts b/build/linux/debian/types.ts index 6797a56111..fb556f3d2c 100644 --- a/build/linux/debian/types.ts +++ b/build/linux/debian/types.ts @@ -3,4 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export type ArchString = 'amd64' | 'armhf' | 'arm64'; +export type DebianArchString = 'amd64' | 'armhf' | 'arm64'; + +export function isDebianArchString(s: string): s is DebianArchString { + return ['amd64', 'armhf', 'arm64'].includes(s); +} diff --git a/build/linux/dependencies-generator.js b/build/linux/dependencies-generator.js new file mode 100644 index 0000000000..855fbeffaa --- /dev/null +++ b/build/linux/dependencies-generator.js @@ -0,0 +1,99 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getDependencies = void 0; +const child_process_1 = require("child_process"); +const path = require("path"); +const calculate_deps_1 = require("./debian/calculate-deps"); +const calculate_deps_2 = require("./rpm/calculate-deps"); +// import { referenceGeneratedDepsByArch as debianGeneratedDeps } from './debian/dep-lists'; // {{SQL CARBON EDIT}} remove unused import +// import { referenceGeneratedDepsByArch as rpmGeneratedDeps } from './rpm/dep-lists'; +const types_1 = require("./debian/types"); +const types_2 = require("./rpm/types"); // {{SQL CARBON EDIT}} remove unused import +// A flag that can easily be toggled. +// Make sure to compile the build directory after toggling the value. +// If false, we warn about new dependencies if they show up +// while running the prepare package tasks for a release. +// If true, we fail the build if there are new dependencies found during that task. +// The reference dependencies, which one has to update when the new dependencies +// are valid, are in dep-lists.ts +// const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = false; // {{SQL CARBON EDIT}} Not needed +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:chrome/installer/linux/BUILD.gn;l=64-80 +// and the Linux Archive build +// Shared library dependencies that we already bundle. +const bundledDeps = [ + 'libEGL.so', + 'libGLESv2.so', + 'libvulkan.so.1', + 'libvk_swiftshader.so', + 'libffmpeg.so' +]; +function getDependencies(packageType, buildDir, applicationName, arch, sysroot) { + if (packageType === 'deb') { + if (!(0, types_1.isDebianArchString)(arch)) { + throw new Error('Invalid Debian arch string ' + arch); + } + if (!sysroot) { + throw new Error('Missing sysroot parameter'); + } + } + if (packageType === 'rpm' && !(0, types_2.isRpmArchString)(arch)) { + throw new Error('Invalid RPM arch string ' + arch); + } + // Get the files for which we want to find dependencies. + const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); + const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']); + if (findResult.status) { + console.error('Error finding files:'); + console.error(findResult.stderr.toString()); + return []; + } + const files = findResult.stdout.toString().trimEnd().split('\n'); + const appPath = path.join(buildDir, applicationName); + files.push(appPath); + // Add chrome sandbox and crashpad handler. + files.push(path.join(buildDir, 'chrome-sandbox')); + files.push(path.join(buildDir, 'chrome_crashpad_handler')); + // Generate the dependencies. + const dependencies = packageType === 'deb' ? + (0, calculate_deps_1.generatePackageDeps)(files, arch, sysroot) : + (0, calculate_deps_2.generatePackageDeps)(files); + // Merge all the dependencies. + const mergedDependencies = mergePackageDeps(dependencies); + // Exclude bundled dependencies and sort + const sortedDependencies = Array.from(mergedDependencies).filter(dependency => { + return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); + }).sort(); + /* {{SQL CARBON EDIT}} Not needed + const referenceGeneratedDeps = referenceGeneratedDepsByArch[arch]; + if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { + const failMessage = 'The dependencies list has changed. ' + + 'Printing newer dependencies list that one can use to compare against referenceGeneratedDeps:\n' + + sortedDependencies.join('\n'); + if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { + throw new Error(failMessage); + } else { + console.warn(failMessage); + } + } + */ + return sortedDependencies; +} +exports.getDependencies = getDependencies; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. +function mergePackageDeps(inputDeps) { + const requires = new Set(); + for (const depSet of inputDeps) { + for (const dep of depSet) { + const trimmedDependency = dep.trim(); + if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { + requires.add(trimmedDependency); + } + } + } + return requires; +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwZW5kZW5jaWVzLWdlbmVyYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImRlcGVuZGVuY2llcy1nZW5lcmF0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztnR0FHZ0c7QUFFaEcsWUFBWSxDQUFDOzs7QUFDYixpREFBMEM7QUFDMUMsNkJBQThCO0FBQzlCLDREQUEyRjtBQUMzRix5REFBcUY7QUFDckYseUlBQXlJO0FBQ3pJLHNGQUFzRjtBQUN0RiwwQ0FBc0U7QUFDdEUsdUNBQWlFLENBQUMsMkNBQTJDO0FBRTdHLHFDQUFxQztBQUNyQyxxRUFBcUU7QUFDckUsMkRBQTJEO0FBQzNELHlEQUF5RDtBQUN6RCxtRkFBbUY7QUFDbkYsZ0ZBQWdGO0FBQ2hGLGlDQUFpQztBQUNqQyw0RkFBNEY7QUFFNUYsZ0lBQWdJO0FBQ2hJLDhCQUE4QjtBQUM5QixzREFBc0Q7QUFDdEQsTUFBTSxXQUFXLEdBQUc7SUFDbkIsV0FBVztJQUNYLGNBQWM7SUFDZCxnQkFBZ0I7SUFDaEIsc0JBQXNCO0lBQ3RCLGNBQWM7Q0FDZCxDQUFDO0FBRUYsU0FBZ0IsZUFBZSxDQUFDLFdBQTBCLEVBQUUsUUFBZ0IsRUFBRSxlQUF1QixFQUFFLElBQVksRUFBRSxPQUFnQjtJQUNwSSxJQUFJLFdBQVcsS0FBSyxLQUFLLEVBQUU7UUFDMUIsSUFBSSxDQUFDLElBQUEsMEJBQWtCLEVBQUMsSUFBSSxDQUFDLEVBQUU7WUFDOUIsTUFBTSxJQUFJLEtBQUssQ0FBQyw2QkFBNkIsR0FBRyxJQUFJLENBQUMsQ0FBQztTQUN0RDtRQUNELElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDYixNQUFNLElBQUksS0FBSyxDQUFDLDJCQUEyQixDQUFDLENBQUM7U0FDN0M7S0FDRDtJQUNELElBQUksV0FBVyxLQUFLLEtBQUssSUFBSSxDQUFDLElBQUEsdUJBQWUsRUFBQyxJQUFJLENBQUMsRUFBRTtRQUNwRCxNQUFNLElBQUksS0FBSyxDQUFDLDBCQUEwQixHQUFHLElBQUks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 \ No newline at end of file diff --git a/build/linux/rpm/dependencies-generator.ts b/build/linux/dependencies-generator.ts similarity index 62% rename from build/linux/rpm/dependencies-generator.ts rename to build/linux/dependencies-generator.ts index ee9642c1bc..7273f17299 100644 --- a/build/linux/rpm/dependencies-generator.ts +++ b/build/linux/dependencies-generator.ts @@ -3,27 +3,54 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +'use strict'; import { spawnSync } from 'child_process'; -import { constants, statSync } from 'fs'; import path = require('path'); -import { additionalDeps, bundledDeps/*, referenceGeneratedDepsByArch*/ } from './dep-lists'; // {{SQL CARBON EDIT}} Not needed -import { ArchString } from './types'; +import { generatePackageDeps as generatePackageDepsDebian } from './debian/calculate-deps'; +import { generatePackageDeps as generatePackageDepsRpm } from './rpm/calculate-deps'; +// import { referenceGeneratedDepsByArch as debianGeneratedDeps } from './debian/dep-lists'; // {{SQL CARBON EDIT}} remove unused import +// import { referenceGeneratedDepsByArch as rpmGeneratedDeps } from './rpm/dep-lists'; +import { DebianArchString, isDebianArchString } from './debian/types'; +import { isRpmArchString, /*RpmArchString*/ } from './rpm/types'; // {{SQL CARBON EDIT}} remove unused import // A flag that can easily be toggled. // Make sure to compile the build directory after toggling the value. // If false, we warn about new dependencies if they show up -// while running the rpm prepare package task for a release. +// while running the prepare package tasks for a release. // If true, we fail the build if there are new dependencies found during that task. // The reference dependencies, which one has to update when the new dependencies // are valid, are in dep-lists.ts // const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = false; // {{SQL CARBON EDIT}} Not needed -export function getDependencies(buildDir: string, applicationName: string, arch: ArchString): string[] { +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:chrome/installer/linux/BUILD.gn;l=64-80 +// and the Linux Archive build +// Shared library dependencies that we already bundle. +const bundledDeps = [ + 'libEGL.so', + 'libGLESv2.so', + 'libvulkan.so.1', + 'libvk_swiftshader.so', + 'libffmpeg.so' +]; + +export function getDependencies(packageType: 'deb' | 'rpm', buildDir: string, applicationName: string, arch: string, sysroot?: string): string[] { + if (packageType === 'deb') { + if (!isDebianArchString(arch)) { + throw new Error('Invalid Debian arch string ' + arch); + } + if (!sysroot) { + throw new Error('Missing sysroot parameter'); + } + } + if (packageType === 'rpm' && !isRpmArchString(arch)) { + throw new Error('Invalid RPM arch string ' + arch); + } + // Get the files for which we want to find dependencies. const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); const findResult = spawnSync('find', [nativeModulesPath, '-name', '*.node']); if (findResult.status) { - console.error(`Error finding files for ${arch}:`); + console.error('Error finding files:'); console.error(findResult.stderr.toString()); return []; } @@ -38,24 +65,17 @@ export function getDependencies(buildDir: string, applicationName: string, arch: files.push(path.join(buildDir, 'chrome_crashpad_handler')); // Generate the dependencies. - const dependencies: Set[] = files.map((file) => calculatePackageDeps(file)); - - // Add additional dependencies. - const additionalDepsSet = new Set(additionalDeps); - dependencies.push(additionalDepsSet); + const dependencies = packageType === 'deb' ? + generatePackageDepsDebian(files, arch as DebianArchString, sysroot!) : + generatePackageDepsRpm(files); // Merge all the dependencies. const mergedDependencies = mergePackageDeps(dependencies); - let sortedDependencies: string[] = []; - for (const dependency of mergedDependencies) { - sortedDependencies.push(dependency); - } - sortedDependencies.sort(); - // Exclude bundled dependencies - sortedDependencies = sortedDependencies.filter(dependency => { + // Exclude bundled dependencies and sort + const sortedDependencies: string[] = Array.from(mergedDependencies).filter(dependency => { return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); - }); + }).sort(); /* {{SQL CARBON EDIT}} Not needed const referenceGeneratedDeps = referenceGeneratedDepsByArch[arch]; @@ -74,25 +94,6 @@ export function getDependencies(buildDir: string, applicationName: string, arch: return sortedDependencies; } -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. -function calculatePackageDeps(binaryPath: string): Set { - try { - if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - - const findRequiresResult = spawnSync('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); - if (findRequiresResult.status !== 0) { - throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); - } - - const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); - return requires; -} // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. function mergePackageDeps(inputDeps: Set[]): Set { diff --git a/build/linux/libcxx-fetcher.js b/build/linux/libcxx-fetcher.js index 7877f82679..74d19099d7 100644 --- a/build/linux/libcxx-fetcher.js +++ b/build/linux/libcxx-fetcher.js @@ -6,19 +6,19 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.downloadLibcxxObjects = exports.downloadLibcxxHeaders = void 0; // Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. +const fs = require("fs"); +const path = require("path"); const debug = require("debug"); const extract = require("extract-zip"); -const fs = require("fs-extra"); -const path = require("path"); -const packageJSON = require("../../package.json"); const get_1 = require("@electron/get"); +const root = path.dirname(path.dirname(__dirname)); const d = debug('libcxx-fetcher'); async function downloadLibcxxHeaders(outDir, electronVersion, lib_name) { - if (await fs.pathExists(path.resolve(outDir, 'include'))) { + if (await fs.existsSync(path.resolve(outDir, 'include'))) { return; } - if (!await fs.pathExists(outDir)) { - await fs.mkdirp(outDir); + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); } d(`downloading ${lib_name}_headers`); const headers = await (0, get_1.downloadArtifact)({ @@ -31,11 +31,11 @@ async function downloadLibcxxHeaders(outDir, electronVersion, lib_name) { } exports.downloadLibcxxHeaders = downloadLibcxxHeaders; async function downloadLibcxxObjects(outDir, electronVersion, targetArch = 'x64') { - if (await fs.pathExists(path.resolve(outDir, 'libc++.a'))) { + if (await fs.existsSync(path.resolve(outDir, 'libc++.a'))) { return; } - if (!await fs.pathExists(outDir)) { - await fs.mkdirp(outDir); + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); } d(`downloading libcxx-objects-linux-${targetArch}`); const objects = await (0, get_1.downloadArtifact)({ @@ -53,6 +53,7 @@ async function main() { const libcxxHeadersDownloadDir = process.env['VSCODE_LIBCXX_HEADERS_DIR']; const libcxxabiHeadersDownloadDir = process.env['VSCODE_LIBCXXABI_HEADERS_DIR']; const arch = process.env['VSCODE_ARCH']; + const packageJSON = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); const electronVersion = packageJSON.devDependencies.electron; if (!libcxxObjectsDirPath || !libcxxHeadersDownloadDir || !libcxxabiHeadersDownloadDir) { throw new Error('Required build env not set'); @@ -67,3 +68,4 @@ if (require.main === module) { process.exit(1); }); } +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGliY3h4LWZldGNoZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJsaWJjeHgtZmV0Y2hlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRywwRkFBMEY7QUFFMUYseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QiwrQkFBK0I7QUFDL0IsdUNBQXVDO0FBQ3ZDLHVDQUFpRDtBQUVqRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUVuRCxNQUFNLENBQUMsR0FBRyxLQUFLLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUUzQixLQUFLLFVBQVUscUJBQXFCLENBQUMsTUFBYyxFQUFFLGVBQXVCLEVBQUUsUUFBZ0I7SUFDcEcsSUFBSSxNQUFNLEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsU0FBUyxDQUFDLENBQUMsRUFBRTtRQUN6RCxPQUFPO0tBQ1A7SUFDRCxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxFQUFFO1FBQ2pDLE1BQU0sRUFBRSxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztLQUNoRDtJQUVELENBQUMsQ0FBQyxlQUFlLFFBQVEsVUFBVSxDQUFDLENBQUM7SUFDckMsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFBLHNCQUFnQixFQUFDO1FBQ3RDLE9BQU8sRUFBRSxlQUFlO1FBQ3hCLFNBQVMsRUFBRSxJQUFJO1FBQ2YsWUFBWSxFQUFFLEdBQUcsUUFBUSxjQUFjO0tBQ3ZDLENBQUMsQ0FBQztJQUVILENBQUMsQ0FBQyxhQUFhLFFBQVEsaUJBQWlCLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFDbkQsTUFBTSxPQUFPLENBQUMsT0FBTyxFQUFFLEVBQUUsR0FBRyxFQUFFLE1B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 \ No newline at end of file diff --git a/build/linux/libcxx-fetcher.ts b/build/linux/libcxx-fetcher.ts index 82be5a4727..ef918791a8 100644 --- a/build/linux/libcxx-fetcher.ts +++ b/build/linux/libcxx-fetcher.ts @@ -5,21 +5,22 @@ // Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. +import * as fs from 'fs'; +import * as path from 'path'; import * as debug from 'debug'; import * as extract from 'extract-zip'; -import * as fs from 'fs-extra'; -import * as path from 'path'; -import * as packageJSON from '../../package.json'; import { downloadArtifact } from '@electron/get'; +const root = path.dirname(path.dirname(__dirname)); + const d = debug('libcxx-fetcher'); export async function downloadLibcxxHeaders(outDir: string, electronVersion: string, lib_name: string): Promise { - if (await fs.pathExists(path.resolve(outDir, 'include'))) { + if (await fs.existsSync(path.resolve(outDir, 'include'))) { return; } - if (!await fs.pathExists(outDir)) { - await fs.mkdirp(outDir); + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); } d(`downloading ${lib_name}_headers`); @@ -34,11 +35,11 @@ export async function downloadLibcxxHeaders(outDir: string, electronVersion: str } export async function downloadLibcxxObjects(outDir: string, electronVersion: string, targetArch: string = 'x64'): Promise { - if (await fs.pathExists(path.resolve(outDir, 'libc++.a'))) { + if (await fs.existsSync(path.resolve(outDir, 'libc++.a'))) { return; } - if (!await fs.pathExists(outDir)) { - await fs.mkdirp(outDir); + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); } d(`downloading libcxx-objects-linux-${targetArch}`); @@ -58,6 +59,7 @@ async function main(): Promise { const libcxxHeadersDownloadDir = process.env['VSCODE_LIBCXX_HEADERS_DIR']; const libcxxabiHeadersDownloadDir = process.env['VSCODE_LIBCXXABI_HEADERS_DIR']; const arch = process.env['VSCODE_ARCH']; + const packageJSON = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); const electronVersion = packageJSON.devDependencies.electron; if (!libcxxObjectsDirPath || !libcxxHeadersDownloadDir || !libcxxabiHeadersDownloadDir) { diff --git a/build/linux/rpm/calculate-deps.js b/build/linux/rpm/calculate-deps.js new file mode 100644 index 0000000000..d83bb3fa05 --- /dev/null +++ b/build/linux/rpm/calculate-deps.js @@ -0,0 +1,36 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generatePackageDeps = void 0; +const child_process_1 = require("child_process"); +const fs_1 = require("fs"); +const dep_lists_1 = require("./dep-lists"); +function generatePackageDeps(files) { + const dependencies = files.map(file => calculatePackageDeps(file)); + const additionalDepsSet = new Set(dep_lists_1.additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; +} +exports.generatePackageDeps = generatePackageDeps; +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. +function calculatePackageDeps(binaryPath) { + try { + if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } + catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + const findRequiresResult = (0, child_process_1.spawnSync)('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); + if (findRequiresResult.status !== 0) { + throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); + } + const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); + return requires; +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FsY3VsYXRlLWRlcHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJjYWxjdWxhdGUtZGVwcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUVoRyxpREFBMEM7QUFDMUMsMkJBQXlDO0FBQ3pDLDJDQUE2QztBQUU3QyxTQUFnQixtQkFBbUIsQ0FBQyxLQUFlO0lBQ2xELE1BQU0sWUFBWSxHQUFrQixLQUFLLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUNsRixNQUFNLGlCQUFpQixHQUFHLElBQUksR0FBRyxDQUFDLDBCQUFjLENBQUMsQ0FBQztJQUNsRCxZQUFZLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQUM7SUFDckMsT0FBTyxZQUFZLENBQUM7QUFDckIsQ0FBQztBQUxELGtEQUtDO0FBRUQsMEhBQTBIO0FBQzFILFNBQVMsb0JBQW9CLENBQUMsVUFBa0I7SUFDL0MsSUFBSTtRQUNILElBQUksQ0FBQyxDQUFDLElBQUEsYUFBUSxFQUFDLFVBQVUsQ0FBQyxDQUFDLElBQUksR0FBRyxjQUFTLENBQUMsT0FBTyxDQUFDLEVBQUU7WUFDckQsTUFBTSxJQUFJLEtBQUssQ0FBQyxVQUFVLFVBQVUsdUNBQXVDLENBQUMsQ0FBQztTQUM3RTtLQUNEO0lBQUMsT0FBTyxDQUFDLEVBQUU7UUFDWCw4REFBOEQ7UUFDOUQsT0FBTyxDQUFDLEtBQUssQ0FBQyxnQkFBZ0IsR0FBRyxVQUFVLEdBQUcsY0FBYyxDQUFDLENBQUM7S0FDOUQ7SUFFRCxNQUFNLGtCQUFrQixHQUFHLElBQUEseUJBQVMsRUFBQyw0QkFBNEIsRUFBRSxFQUFFLEtBQUssRUFBRSxVQUFVLEdBQUcsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUNqRyxJQUFJLGtCQUFrQixDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUU7UUFDcEMsTUFBTSxJQUFJLEtBQUssQ0FBQyx1Q0FBdUMsa0JBQWtCLENBQUMsTUFBTSxjQUFjLGtCQUFrQixDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7S0FDM0g7SUFFRCxNQUFNLFFBQVEsR0FBRyxJQUFJLEdBQUcsQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQzVGLE9BQU8sUUFBUSxDQUFDO0FBQ2pCLENBQUMifQ== \ No newline at end of file diff --git a/build/linux/rpm/calculate-deps.ts b/build/linux/rpm/calculate-deps.ts new file mode 100644 index 0000000000..9b90831006 --- /dev/null +++ b/build/linux/rpm/calculate-deps.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawnSync } from 'child_process'; +import { constants, statSync } from 'fs'; +import { additionalDeps } from './dep-lists'; + +export function generatePackageDeps(files: string[]): Set[] { + const dependencies: Set[] = files.map(file => calculatePackageDeps(file)); + const additionalDepsSet = new Set(additionalDeps); + dependencies.push(additionalDepsSet); + return dependencies; +} + +// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. +function calculatePackageDeps(binaryPath: string): Set { + try { + if (!(statSync(binaryPath).mode & constants.S_IXUSR)) { + throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); + } + } catch (e) { + // The package might not exist. Don't re-throw the error here. + console.error('Tried to stat ' + binaryPath + ' but failed.'); + } + + const findRequiresResult = spawnSync('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); + if (findRequiresResult.status !== 0) { + throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); + } + + const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); + return requires; +} diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index b912865c2c..d50ff0f883 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -4,7 +4,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.bundledDeps = exports.additionalDeps = void 0; +exports.referenceGeneratedDepsByArch = exports.additionalDeps = void 0; // Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/additional_deps // Additional dependencies not in the rpm find-requires output. exports.additionalDeps = [ @@ -17,18 +17,6 @@ exports.additionalDeps = [ 'libcurl.so.4()(64bit)', 'xdg-utils' // OS integration ]; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 -// and the Linux Archive build -// Shared library dependencies that we already bundle. -exports.bundledDeps = [ - 'libEGL.so', - 'libGLESv2.so', - 'libvulkan.so.1', - 'swiftshader_libEGL.so', - 'swiftshader_libGLESv2.so', - 'libvk_swiftshader.so', - 'libffmpeg.so' -]; exports.referenceGeneratedDepsByArch = { 'x86_64': [ 'ca-certificates', @@ -136,10 +124,11 @@ exports.referenceGeneratedDepsByArch = { 'libc.so.6', 'libc.so.6(GLIBC_2.10)', 'libc.so.6(GLIBC_2.11)', - 'libc.so.6(GLIBC_2.14)', 'libc.so.6(GLIBC_2.15)', 'libc.so.6(GLIBC_2.16)', 'libc.so.6(GLIBC_2.17)', + 'libc.so.6(GLIBC_2.25)', + 'libc.so.6(GLIBC_2.28)', 'libc.so.6(GLIBC_2.4)', 'libc.so.6(GLIBC_2.6)', 'libc.so.6(GLIBC_2.7)', @@ -156,7 +145,6 @@ exports.referenceGeneratedDepsByArch = { 'libgbm.so.1', 'libgcc_s.so.1', 'libgcc_s.so.1(GCC_3.0)', - 'libgcc_s.so.1(GCC_3.4)', 'libgcc_s.so.1(GCC_3.5)', 'libgio-2.0.so.0', 'libglib-2.0.so.0', @@ -234,6 +222,8 @@ exports.referenceGeneratedDepsByArch = { 'libatspi.so.0()(64bit)', 'libc.so.6()(64bit)', 'libc.so.6(GLIBC_2.17)(64bit)', + 'libc.so.6(GLIBC_2.25)(64bit)', + 'libc.so.6(GLIBC_2.28)(64bit)', 'libcairo.so.2()(64bit)', 'libcups.so.2()(64bit)', 'libcurl.so.4()(64bit)', @@ -304,3 +294,4 @@ exports.referenceGeneratedDepsByArch = { 'xdg-utils' ] }; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLCtHQUErRztBQUMvRywrREFBK0Q7QUFDbEQsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHdCQUF3QjtJQUN4Qiw2QkFBNkI7SUFDN0IsNkJBQTZCO0lBQzdCLGdDQUFnQztJQUNoQyx5QkFBeUI7SUFDekIsdUJBQXVCO0lBQ3ZCLFdBQVcsQ0FBQyxpQkFBaUI7Q0FDN0IsQ0FBQztBQUVXLFFBQUEsNEJBQTRCLEdBQUc7SUFDM0MsUUFBUSxFQUFFO1FBQ1QsaUJBQWlCO1FBQ2pCLCtCQUErQjtRQUMvQiwwQ0FBMEM7UUFDMUMsd0NBQXdDO1FBQ3hDLHNCQUFzQjtRQUN0Qiw2QkFBNkI7UUFDN0IsMEJBQTBCO1FBQzFCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIseUJBQXlCO1FBQ3pCLHlCQUF5QjtRQUN6QixpQ0FBaUM7UUFDakMsc0NBQXNDO1FBQ3RDLDBCQUEwQjtRQUMxQixpQ0FBaUM7UUFDakMsd0JBQXdCO1FBQ3hCLG9CQUFvQjtRQUNwQiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5Qiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5QiwrQkFBK0I7UUFDL0IsNkJBQTZCO1FBQzdCLCtCQUErQjtRQUMvQiwrQkFBK0I7UUFDL0IsK0JBQStCO1FBQy9CLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0IsNkJBQTZCO1FBQzdCLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0Isd0JBQXdCO1FBQ3hCLHVCQUF1QjtRQUN2Qix1QkFBdUI7UUFDdkIseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixnQ0FBZ0M7UUFDaEMsc0JBQXNCO1FBQ3RCLHdCQUF3QjtRQUN4QixzQkFBc0I7UUFDdEIsd0JBQXdCO1FBQ3hCLCtCQUErQjtR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 \ No newline at end of file diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 0edd4758c0..fd6de20ad9 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -16,19 +16,6 @@ export const additionalDeps = [ 'xdg-utils' // OS integration ]; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4758.109:chrome/installer/linux/BUILD.gn;l=64-80 -// and the Linux Archive build -// Shared library dependencies that we already bundle. -export const bundledDeps = [ - 'libEGL.so', - 'libGLESv2.so', - 'libvulkan.so.1', - 'swiftshader_libEGL.so', - 'swiftshader_libGLESv2.so', - 'libvk_swiftshader.so', - 'libffmpeg.so' -]; - export const referenceGeneratedDepsByArch = { 'x86_64': [ 'ca-certificates', @@ -136,10 +123,11 @@ export const referenceGeneratedDepsByArch = { 'libc.so.6', 'libc.so.6(GLIBC_2.10)', 'libc.so.6(GLIBC_2.11)', - 'libc.so.6(GLIBC_2.14)', 'libc.so.6(GLIBC_2.15)', 'libc.so.6(GLIBC_2.16)', 'libc.so.6(GLIBC_2.17)', + 'libc.so.6(GLIBC_2.25)', + 'libc.so.6(GLIBC_2.28)', 'libc.so.6(GLIBC_2.4)', 'libc.so.6(GLIBC_2.6)', 'libc.so.6(GLIBC_2.7)', @@ -156,7 +144,6 @@ export const referenceGeneratedDepsByArch = { 'libgbm.so.1', 'libgcc_s.so.1', 'libgcc_s.so.1(GCC_3.0)', - 'libgcc_s.so.1(GCC_3.4)', 'libgcc_s.so.1(GCC_3.5)', 'libgio-2.0.so.0', 'libglib-2.0.so.0', @@ -234,6 +221,8 @@ export const referenceGeneratedDepsByArch = { 'libatspi.so.0()(64bit)', 'libc.so.6()(64bit)', 'libc.so.6(GLIBC_2.17)(64bit)', + 'libc.so.6(GLIBC_2.25)(64bit)', + 'libc.so.6(GLIBC_2.28)(64bit)', 'libcairo.so.2()(64bit)', 'libcups.so.2()(64bit)', 'libcurl.so.4()(64bit)', diff --git a/build/linux/rpm/dependencies-generator.js b/build/linux/rpm/dependencies-generator.js deleted file mode 100644 index dae223ef18..0000000000 --- a/build/linux/rpm/dependencies-generator.js +++ /dev/null @@ -1,97 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getDependencies = void 0; -const child_process_1 = require("child_process"); -const fs_1 = require("fs"); -const path = require("path"); -const dep_lists_1 = require("./dep-lists"); // {{SQL CARBON EDIT}} Not needed -// A flag that can easily be toggled. -// Make sure to compile the build directory after toggling the value. -// If false, we warn about new dependencies if they show up -// while running the rpm prepare package task for a release. -// If true, we fail the build if there are new dependencies found during that task. -// The reference dependencies, which one has to update when the new dependencies -// are valid, are in dep-lists.ts -// const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = false; // {{SQL CARBON EDIT}} Not needed -function getDependencies(buildDir, applicationName, arch) { - // Get the files for which we want to find dependencies. - const nativeModulesPath = path.join(buildDir, 'resources', 'app', 'node_modules.asar.unpacked'); - const findResult = (0, child_process_1.spawnSync)('find', [nativeModulesPath, '-name', '*.node']); - if (findResult.status) { - console.error(`Error finding files for ${arch}:`); - console.error(findResult.stderr.toString()); - return []; - } - const files = findResult.stdout.toString().trimEnd().split('\n'); - const appPath = path.join(buildDir, applicationName); - files.push(appPath); - // Add chrome sandbox and crashpad handler. - files.push(path.join(buildDir, 'chrome-sandbox')); - files.push(path.join(buildDir, 'chrome_crashpad_handler')); - // Generate the dependencies. - const dependencies = files.map((file) => calculatePackageDeps(file)); - // Add additional dependencies. - const additionalDepsSet = new Set(dep_lists_1.additionalDeps); - dependencies.push(additionalDepsSet); - // Merge all the dependencies. - const mergedDependencies = mergePackageDeps(dependencies); - let sortedDependencies = []; - for (const dependency of mergedDependencies) { - sortedDependencies.push(dependency); - } - sortedDependencies.sort(); - // Exclude bundled dependencies - sortedDependencies = sortedDependencies.filter(dependency => { - return !dep_lists_1.bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); - }); - /* {{SQL CARBON EDIT}} Not needed - const referenceGeneratedDeps = referenceGeneratedDepsByArch[arch]; - if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { - const failMessage = 'The dependencies list has changed. ' - + 'Printing newer dependencies list that one can use to compare against referenceGeneratedDeps:\n' - + sortedDependencies.join('\n'); - if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { - throw new Error(failMessage); - } else { - console.warn(failMessage); - } - } - */ - return sortedDependencies; -} -exports.getDependencies = getDependencies; -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. -function calculatePackageDeps(binaryPath) { - try { - if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } - catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - const findRequiresResult = (0, child_process_1.spawnSync)('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); - if (findRequiresResult.status !== 0) { - throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); - } - const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); - return requires; -} -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. -function mergePackageDeps(inputDeps) { - const requires = new Set(); - for (const depSet of inputDeps) { - for (const dep of depSet) { - const trimmedDependency = dep.trim(); - if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { - requires.add(trimmedDependency); - } - } - } - return requires; -} diff --git a/build/linux/rpm/types.js b/build/linux/rpm/types.js index bda3e6b964..ed5a166775 100644 --- a/build/linux/rpm/types.js +++ b/build/linux/rpm/types.js @@ -4,3 +4,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); +exports.isRpmArchString = void 0; +function isRpmArchString(s) { + return ['x86_64', 'armv7hl', 'aarch64'].includes(s); +} +exports.isRpmArchString = isRpmArchString; +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJ0eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztnR0FHZ0c7OztBQUloRyxTQUFnQixlQUFlLENBQUMsQ0FBUztJQUN4QyxPQUFPLENBQUMsUUFBUSxFQUFFLFNBQVMsRUFBRSxTQUFTLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDckQsQ0FBQztBQUZELDBDQUVDIn0= \ No newline at end of file diff --git a/build/linux/rpm/types.ts b/build/linux/rpm/types.ts index dc3eb37397..35919b7926 100644 --- a/build/linux/rpm/types.ts +++ b/build/linux/rpm/types.ts @@ -3,4 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export type ArchString = 'x86_64' | 'armv7hl' | 'aarch64'; +export type RpmArchString = 'x86_64' | 'armv7hl' | 'aarch64'; + +export function isRpmArchString(s: string): s is RpmArchString { + return ['x86_64', 'armv7hl', 'aarch64'].includes(s); +} diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index c2524ec0a2..d182fbbd34 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -14,10 +14,32 @@ declare namespace monaco { export type Thenable = PromiseLike; export interface Environment { + /** + * Define a global `monaco` symbol. + * This is true by default in AMD and false by default in ESM. + */ globalAPI?: boolean; + /** + * The base url where the editor sources are found (which contains the vs folder) + */ baseUrl?: string; + /** + * A web worker factory. + * NOTE: If `getWorker` is defined, `getWorkerUrl` is not invoked. + */ getWorker?(workerId: string, label: string): Promise | Worker; + /** + * Return the location for web worker scripts. + * NOTE: If `getWorker` is defined, `getWorkerUrl` is not invoked. + */ getWorkerUrl?(workerId: string, label: string): string; + /** + * Create a trust types policy (same API as window.trustedTypes.createPolicy) + */ + createTrustedTypesPolicy( + policyName: string, + policyOptions?: Options, + ): undefined | Pick, 'name' | Extract>; } export interface IDisposable { @@ -43,7 +65,7 @@ declare namespace monaco { #include(vs/base/common/uri): URI, UriComponents #include(vs/base/common/keyCodes): KeyCode #include(vs/editor/common/services/editorBaseApi): KeyMod -#include(vs/base/common/htmlContent): IMarkdownString +#include(vs/base/common/htmlContent): IMarkdownString, MarkdownStringTrustedOptions #include(vs/base/browser/keyboardEvent): IKeyboardEvent #include(vs/base/browser/mouseEvent): IMouseEvent #include(vs/editor/common/editorCommon): IScrollEvent @@ -68,11 +90,14 @@ export interface ICommandHandler { #include(vs/platform/markers/common/markers): IMarker, IMarkerData, IRelatedInformation #include(vs/editor/standalone/browser/colorizer): IColorizerOptions, IColorizerElementOptions #include(vs/base/common/scrollable): ScrollbarVisibility -#include(vs/platform/theme/common/themeService): ThemeColor +#include(vs/base/common/themables): ThemeColor #include(vs/editor/common/core/editOperation): ISingleEditOperation #include(vs/editor/common/core/wordHelper): IWordAtPosition #includeAll(vs/editor/common/model): IScrollEvent -#include(vs/editor/common/diff/diffComputer): IChange, ICharChange, ILineChange +#include(vs/editor/common/diff/smartLinesDiffComputer): IChange, ICharChange, ILineChange +#include(vs/editor/common/diff/documentDiffProvider): IDocumentDiffProvider, IDocumentDiffProviderOptions, IDocumentDiff +#include(vs/editor/common/core/lineRange): LineRange +#include(vs/editor/common/diff/linesDiffComputer): LineRangeMapping, RangeMapping #include(vs/editor/common/core/dimension): IDimension #includeAll(vs/editor/common/editorCommon): IScrollEvent #includeAll(vs/editor/common/textModelEvents): @@ -82,6 +107,7 @@ export interface ICommandHandler { #include(vs/editor/browser/config/editorConfiguration): IEditorConstructionOptions #includeAll(vs/editor/browser/editorBrowser;editorCommon.=>): #include(vs/editor/common/config/fontInfo): FontInfo, BareFontInfo +#include(vs/editor/common/config/editorZoom): EditorZoom, IEditorZoom //compatibility: export type IReadOnlyModel = ITextModel; diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe index 3fab91065a..e3c8cdd091 100644 --- a/build/monaco/monaco.usage.recipe +++ b/build/monaco/monaco.usage.recipe @@ -1,6 +1,8 @@ // This file is adding references to various symbols which should not be removed via tree shaking +import { IObservable } from './vs/base/common/observable'; + import { ServiceIdentifier } from './vs/platform/instantiation/common/instantiation'; import { create as create1 } from './vs/base/common/worker/simpleWorker'; import { create as create2 } from './vs/editor/common/services/editorSimpleWorker'; @@ -32,4 +34,7 @@ import * as editorAPI from './vs/editor/editor.api'; a = editorAPI.Token; a = editorAPI.editor; a = editorAPI.languages; + + const o: IObservable = null!; + o.TChange; })(); diff --git a/build/npm/dirs.js b/build/npm/dirs.js index 19af457dbb..b519b8f96c 100644 --- a/build/npm/dirs.js +++ b/build/npm/dirs.js @@ -3,8 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +const fs = require('fs'); + // Complete list of directories where yarn should be executed to install node modules -exports.dirs = [ +const dirs = [ '', 'build', 'extensions', @@ -24,7 +26,6 @@ exports.dirs = [ 'extensions/git-base', 'extensions/github', 'extensions/github-authentication', - 'extensions/image-preview', 'extensions/import', 'extensions/integration-tests', 'extensions/ipynb', @@ -35,13 +36,13 @@ exports.dirs = [ 'extensions/markdown-language-features/server', 'extensions/markdown-language-features', 'extensions/markdown-math', + 'extensions/media-preview', 'extensions/merge-conflict', 'extensions/microsoft-authentication', 'extensions/mssql', 'extensions/notebook', 'extensions/notebook-renderers', 'extensions/profiler', - 'extensions/python', 'extensions/query-history', 'extensions/resource-deployment', 'extensions/schema-compare', @@ -53,7 +54,6 @@ exports.dirs = [ 'extensions/sql-database-projects', 'extensions/sql-migration', 'extensions/vscode-test-resolver', - 'extensions/xml-language-features', // {{SQL CARBON EDIT}} - End 'remote', 'remote/web', @@ -62,3 +62,11 @@ exports.dirs = [ 'test/monaco', 'test/smoke', ]; + +if (fs.existsSync(`${__dirname}/../../.build/distro/npm`)) { + dirs.push('.build/distro/npm'); + dirs.push('.build/distro/npm/remote'); + dirs.push('.build/distro/npm/remote/web'); +} + +exports.dirs = dirs; diff --git a/build/npm/gyp/package.json b/build/npm/gyp/package.json index 208ee3c3b6..e85c1bcccc 100644 --- a/build/npm/gyp/package.json +++ b/build/npm/gyp/package.json @@ -6,6 +6,5 @@ "devDependencies": { "node-gyp": "^9.3.1" }, - "scripts": { - } + "scripts": {} } diff --git a/build/npm/jsconfig.json b/build/npm/jsconfig.json index 41d18dab43..2b8f9ad195 100644 --- a/build/npm/jsconfig.json +++ b/build/npm/jsconfig.json @@ -4,7 +4,7 @@ "lib": [ "ES2020" ], - "module": "node12", + "module": "node16", "checkJs": true, "noEmit": true } diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index 82b77bf5b5..eccdee4a99 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -3,35 +3,61 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +const fs = require('fs'); +const path = require('path'); +const os = require('os'); const cp = require('child_process'); const { dirs } = require('./dirs'); const { setupBuildYarnrc } = require('./setupBuildYarnrc'); const yarn = process.platform === 'win32' ? 'yarn.cmd' : 'yarn'; +const root = path.dirname(path.dirname(__dirname)); + +function run(command, args, opts) { + console.log('$ ' + command + ' ' + args.join(' ')); + + const result = cp.spawnSync(command, args, opts); + + if (result.error) { + console.error(`ERR Failed to spawn process: ${result.error}`); + process.exit(1); + } else if (result.status !== 0) { + console.error(`ERR Process exited with code: ${result.status}`); + process.exit(result.status); + } +} /** - * @param {string} location + * @param {string} dir * @param {*} [opts] */ -function yarnInstall(location, opts) { - opts = opts || { env: process.env }; - opts.cwd = location; - opts.stdio = 'inherit'; +function yarnInstall(dir, opts) { + opts = { + env: { ...process.env }, + ...(opts ?? {}), + cwd: dir, + stdio: 'inherit', + }; const raw = process.env['npm_config_argv'] || '{}'; const argv = JSON.parse(raw); const original = argv.original || []; const args = original.filter(arg => arg === '--ignore-optional' || arg === '--frozen-lockfile' || arg === '--check-files'); + if (opts.ignoreEngines) { args.push('--ignore-engines'); delete opts.ignoreEngines; } - console.log(`Installing dependencies in ${location}...`); - console.log(`$ yarn ${args.join(' ')}`); - const result = cp.spawnSync(yarn, args, opts); + if (process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'] && /^(.build\/distro\/npm\/)?remote$/.test(dir)) { + const userinfo = os.userInfo(); + console.log(`Installing dependencies in ${dir} inside container ${process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME']}...`); - if (result.error || result.status !== 0) { - process.exit(1); + opts.cwd = root; + run('docker', ['run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `/mnt/vss/_work/1/s:/root/vscode`, '-v', `/mnt/vss/_work/1/s/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); + run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${dir}/node_modules`], opts); + } else { + console.log(`Installing dependencies in ${dir}...`); + run(yarn, args, opts); } } @@ -42,7 +68,16 @@ for (let dir of dirs) { continue; } - if (/^remote/.test(dir) && process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64')) { + if (/^.build\/distro\/npm(\/?)/.test(dir)) { + const ossPath = path.relative('.build/distro/npm', dir); + const ossYarnRc = path.join(ossPath, '.yarnrc'); + + if (fs.existsSync(ossYarnRc)) { + fs.cpSync(ossYarnRc, path.join(dir, '.yarnrc')); + } + } + + if (/^(.build\/distro\/npm\/)?remote/.test(dir) && process.platform === 'win32' && (process.arch === 'arm64' || process.env['npm_config_arch'] === 'arm64')) { // windows arm: do not execute `yarn` on remote folder continue; } @@ -55,7 +90,7 @@ for (let dir of dirs) { let opts; - if (dir === 'remote') { + if (/^(.build\/distro\/npm\/)?remote$/.test(dir)) { // node modules used by vscode server const env = { ...process.env }; if (process.env['VSCODE_REMOTE_CC']) { env['CC'] = process.env['VSCODE_REMOTE_CC']; } diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js index 880f02d362..90d0abc021 100644 --- a/build/npm/preinstall.js +++ b/build/npm/preinstall.js @@ -9,8 +9,8 @@ const majorNodeVersion = parseInt(nodeVersion[1]); const minorNodeVersion = parseInt(nodeVersion[2]); const patchNodeVersion = parseInt(nodeVersion[3]); -if (majorNodeVersion < 16 || (majorNodeVersion === 16 && minorNodeVersion < 14)) { - console.error('\033[1;31m*** Please use node.js versions >=16.14.x and <17.\033[0;0m'); +if (majorNodeVersion < 16 || (majorNodeVersion === 16 && minorNodeVersion < 17)) { + console.error('\033[1;31m*** Please use node.js versions >=16.17.x and <17.\033[0;0m'); err = true; } if (majorNodeVersion >= 17) { @@ -21,12 +21,19 @@ const path = require('path'); const fs = require('fs'); const cp = require('child_process'); const yarnVersion = cp.execSync('yarn -v', { encoding: 'utf8' }).trim(); -const parsedYarnVersion = /^(\d+)\.(\d+)\./.exec(yarnVersion); +const parsedYarnVersion = /^(\d+)\.(\d+)\.(\d+)/.exec(yarnVersion); const majorYarnVersion = parseInt(parsedYarnVersion[1]); const minorYarnVersion = parseInt(parsedYarnVersion[2]); +const patchYarnVersion = parseInt(parsedYarnVersion[3]); -if (majorYarnVersion < 1 || minorYarnVersion < 10) { - console.error('\033[1;31m*** Please use yarn >=1.10.1.\033[0;0m'); +if ( + majorYarnVersion < 1 || + majorYarnVersion === 1 && ( + minorYarnVersion < 10 || (minorYarnVersion === 10 && patchYarnVersion < 1) + ) || + majorYarnVersion >= 2 +) { + console.error('\033[1;31m*** Please use yarn >=1.10.1 and <2.\033[0;0m'); err = true; } diff --git a/build/npm/update-all-grammars.mjs b/build/npm/update-all-grammars.mjs index 2206d70a7b..df0aca9c37 100644 --- a/build/npm/update-all-grammars.mjs +++ b/build/npm/update-all-grammars.mjs @@ -6,7 +6,7 @@ import { spawn as _spawn } from 'child_process'; import { readdirSync, readFileSync } from 'fs'; import { join } from 'path'; -import url from 'url' +import url from 'url'; async function spawn(cmd, args, opts) { return new Promise((c, e) => { @@ -20,7 +20,7 @@ async function main() { for (const extension of readdirSync('extensions')) { try { - let packageJSON = JSON.parse(readFileSync(join('extensions', extension, 'package.json')).toString()); + const packageJSON = JSON.parse(readFileSync(join('extensions', extension, 'package.json')).toString()); if (!(packageJSON && packageJSON.scripts && packageJSON.scripts['update-grammar'])) { continue; } diff --git a/build/npm/update-localization-extension.js b/build/npm/update-localization-extension.js index 0b8e698e12..89c733397c 100644 --- a/build/npm/update-localization-extension.js +++ b/build/npm/update-localization-extension.js @@ -23,6 +23,10 @@ function update(options) { if (location !== undefined && !fs.existsSync(location)) { throw new Error(`${location} doesn't exist.`); } + let externalExtensionsLocation = options.externalExtensionsLocation; + if (externalExtensionsLocation !== undefined && !fs.existsSync(externalExtensionsLocation)) { + throw new Error(`${externalExtensionsLocation} doesn't exist.`); + } let locExtFolder = idOrPath; if (/^\w{2,3}(-\w+)?$/.test(idOrPath)) { locExtFolder = path.join('..', 'vscode-loc', 'i18n', `vscode-language-pack-${idOrPath}`); @@ -67,8 +71,11 @@ function update(options) { console.log(`Importing translations for ${languageId} form '${location}' to '${translationDataFolder}' ...`); let translationPaths = []; - gulp.src(path.join(location, '**', languageId, '*.xlf'), { silent: false }) - .pipe(i18n.prepareI18nPackFiles(i18n.externalExtensionsWithTranslations, translationPaths, languageId === 'ps')) + gulp.src([ + path.join(location, '**', languageId, '*.xlf'), + ...i18n.EXTERNAL_EXTENSIONS.map(extensionId => path.join(externalExtensionsLocation, extensionId, languageId, '*-new.xlf')) + ], { silent: false }) + .pipe(i18n.prepareI18nPackFiles(translationPaths)) .on('error', (error) => { console.log(`Error occurred while importing translations:`); translationPaths = undefined; @@ -94,7 +101,7 @@ function update(options) { } if (path.basename(process.argv[1]) === 'update-localization-extension.js') { var options = minimist(process.argv.slice(2), { - string: 'location' + string: ['location', 'externalExtensionsLocation'] }); update(options); } diff --git a/build/package.json b/build/package.json index 7b1b69e718..0e212f3629 100644 --- a/build/package.json +++ b/build/package.json @@ -5,8 +5,8 @@ "devDependencies": { "@actions/core": "1.9.1", "@actions/github": "2.1.1", - "@azure/cosmos": "^3.14.1", - "@azure/identity": "^3.1.4", + "@azure/cosmos": "^3.17.3", + "@azure/identity": "^2.1.0", "@azure/storage-blob": "^12.13.0", "@electron/get": "^1.12.4", "@types/ansi-colors": "^3.2.0", @@ -16,7 +16,6 @@ "@types/debounce": "^1.0.0", "@types/debug": "4.1.5", "@types/documentdb": "^1.10.5", - "@types/eslint": "4.16.1", "@types/eslint-visitor-keys": "^1.0.0", "@types/fancy-log": "^1.3.0", "@types/fs-extra": "^9.0.12", @@ -38,18 +37,15 @@ "@types/p-limit": "^2.2.0", "@types/plist": "^3.0.2", "@types/pump": "^1.0.1", - "@types/request": "^2.47.0", "@types/rimraf": "^2.0.4", "@types/through": "^0.0.29", "@types/through2": "^2.0.36", "@types/tmp": "^0.2.1", "@types/underscore": "^1.8.9", - "@types/webpack": "^4.41.25", "@types/xml2js": "0.4.11", - "@typescript-eslint/experimental-utils": "~5.10.0", - "@typescript-eslint/parser": "^5.10.0", + "@typescript-eslint/experimental-utils": "^5.57.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/vsce": "2.16.0", + "@vscode/vsce": "^2.16.0", "applicationinsights": "1.0.8", "axios": "0.21.4", "byline": "^5.0.0", @@ -58,10 +54,8 @@ "debug": "^4.3.2", "documentdb": "1.13.0", "electron-osx-sign": "^0.4.16", - "esbuild": "^0.14.2", + "esbuild": "0.17.14", "extract-zip": "^2.0.1", - "fs-extra": "^9.1.0", - "got": "11.8.5", "gulp-merge-json": "^2.1.1", "gulp-shell": "^0.8.0", "jsonc-parser": "^2.3.0", @@ -74,6 +68,7 @@ "rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-node-resolve": "^5.2.0", "source-map": "0.6.1", + "ternary-stream": "^3.0.0", "through2": "^4.0.2", "tmp": "^0.2.1", "vscode-universal-bundler": "^0.0.2" diff --git a/build/setup-npm-registry.js b/build/setup-npm-registry.js new file mode 100644 index 0000000000..70b3e32438 --- /dev/null +++ b/build/setup-npm-registry.js @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +const fs = require('fs').promises; +const path = require('path'); + +async function* getYarnLockFiles(dir) { + const files = await fs.readdir(dir); + + for (const file of files) { + const fullPath = path.join(dir, file); + const stat = await fs.stat(fullPath); + + if (stat.isDirectory()) { + yield* getYarnLockFiles(fullPath); + } else if (file === 'yarn.lock') { + yield fullPath; + } + } +} + +async function setup(url, file) { + let contents = await fs.readFile(file, 'utf8'); + contents = contents.replace(/https:\/\/registry\.[^.]+\.com\//g, url); + await fs.writeFile(file, contents); +} + +async function main(url, dir) { + const root = dir ?? process.cwd(); + + for await (const file of getYarnLockFiles(root)) { + console.log(`Enabling custom NPM registry: ${path.relative(root, file)}`); + await setup(url, file); + } +} + +main(process.argv[2], process.argv[3]); diff --git a/build/stylelint.js b/build/stylelint.js new file mode 100644 index 0000000000..1e7aa3d31b --- /dev/null +++ b/build/stylelint.js @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const es = require('event-stream'); +const vfs = require('vinyl-fs'); +const { stylelintFilter } = require('./filters'); +const { getVariableNameValidator } = require('./lib/stylelint/validateVariableNames'); + +module.exports = gulpstylelint; + +/** use regex on lines */ +function gulpstylelint(reporter) { + const variableValidator = getVariableNameValidator(); + let errorCount = 0; + return es.through(function (file) { + const lines = file.__lines || file.contents.toString('utf8').split(/\r\n|\r|\n/); + file.__lines = lines; + + lines.forEach((line, i) => { + variableValidator(line, unknownVariable => { + reporter(file.relative + '(' + (i + 1) + ',1): Unknown variable: ' + unknownVariable, true); + errorCount++; + }); + }); + + this.emit('data', file); + }, function () { + if (errorCount > 0) { + reporter('All valid variable names are in `build/lib/stylelint/vscode-known-variables.json`\nTo update that file, run `./scripts/test-documentation.sh|bat.`', false); + } + this.emit('end'); + } + ); +} + +function stylelint() { + return vfs + .src(stylelintFilter, { base: '.', follow: true, allowEmpty: true }) + .pipe(gulpstylelint((message, isError) => { + if (isError) { + console.error(message); + } else { + console.info(message); + } + })) + .pipe(es.through(function () { /* noop, important for the stream to end */ })); +} + +if (require.main === module) { + stylelint().on('error', (err) => { + console.error(); + console.error(err); + process.exit(1); + }); +} diff --git a/build/tsconfig.build.json b/build/tsconfig.build.json index 0a00f2d0f4..ecb87a6756 100644 --- a/build/tsconfig.build.json +++ b/build/tsconfig.build.json @@ -7,5 +7,8 @@ }, "include": [ "**/*.ts" + ], + "exclude": [ + "lib/eslint-plugin-vscode/**/*" ] -} +} \ No newline at end of file diff --git a/build/tsconfig.json b/build/tsconfig.json index ac3ce923ad..b195f7bae4 100644 --- a/build/tsconfig.json +++ b/build/tsconfig.json @@ -1,14 +1,16 @@ { "compilerOptions": { - "target": "es2020", - "lib": ["ES2020"], + "target": "es2022", + "lib": [ + "ES2020" + ], "module": "commonjs", "alwaysStrict": true, "removeComments": false, "preserveConstEnums": true, "sourceMap": false, + "inlineSourceMap": true, "resolveJsonModule": true, - "experimentalDecorators": true, // enable JavaScript type checking for the language service // use the tsconfig.build.json for compiling which disable JavaScript // type checking so that JavaScript file are not transpiled diff --git a/build/win32/Cargo.toml b/build/win32/Cargo.toml new file mode 100644 index 0000000000..decae65f9e --- /dev/null +++ b/build/win32/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "inno_updater" +version = "0.9.0" +authors = ["Microsoft "] +build = "build.rs" + +[dependencies] +byteorder = "1" +crc = "^1.0.0" +slog = "2.1.1" +slog-async = "2.2.0" +slog-term = "2.3.0" + +[target.'cfg(windows)'.dependencies] +winapi = { version = "^0.3.9", features = ["winuser", "libloaderapi", "commctrl", "processthreadsapi", "tlhelp32", "handleapi", "psapi", "errhandlingapi", "winbase", "shellapi"] } + +[profile.release] +lto = true +panic = 'abort' diff --git a/build/win32/cargo.toml b/build/win32/cargo.toml index 7ce7c60664..decae65f9e 100644 --- a/build/win32/cargo.toml +++ b/build/win32/cargo.toml @@ -1,7 +1,7 @@ [package] name = "inno_updater" version = "0.9.0" -authors = ["Microsoft"] +authors = ["Microsoft "] build = "build.rs" [dependencies] @@ -16,4 +16,4 @@ winapi = { version = "^0.3.9", features = ["winuser", "libloaderapi", "commctrl" [profile.release] lto = true -panic = 'abort' \ No newline at end of file +panic = 'abort' diff --git a/build/win32/code.iss b/build/win32/code.iss index facbc7beb8..afa727a3ca 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -27,7 +27,7 @@ SetupIconFile={#RepoDir}\resources\win32\code.ico UninstallDisplayIcon={app}\{#ExeBasename}.exe ChangesEnvironment=true ChangesAssociations=true -MinVersion=6.1.7600 +MinVersion=6.2 SourceDir={#SourceDir} AppVersion={#Version} VersionInfoVersion={#RawVersion} @@ -63,13 +63,13 @@ Name: "hungarian"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.hu.isl,{#R Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl,{#RepoDir}\build\win32\i18n\messages.tr.isl" {#LocalizedLanguageFile("trk")} [InstallDelete] -Type: filesandordirs; Name: "{app}\resources\app\out"; Check: IsNotUpdate -Type: filesandordirs; Name: "{app}\resources\app\plugins"; Check: IsNotUpdate -Type: filesandordirs; Name: "{app}\resources\app\extensions"; Check: IsNotUpdate -Type: filesandordirs; Name: "{app}\resources\app\node_modules"; Check: IsNotUpdate -Type: filesandordirs; Name: "{app}\resources\app\node_modules.asar.unpacked"; Check: IsNotUpdate -Type: files; Name: "{app}\resources\app\node_modules.asar"; Check: IsNotUpdate -Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html"; Check: IsNotUpdate +Type: filesandordirs; Name: "{app}\resources\app\out"; Check: IsNotBackgroundUpdate +Type: filesandordirs; Name: "{app}\resources\app\plugins"; Check: IsNotBackgroundUpdate +Type: filesandordirs; Name: "{app}\resources\app\extensions"; Check: IsNotBackgroundUpdate +Type: filesandordirs; Name: "{app}\resources\app\node_modules"; Check: IsNotBackgroundUpdate +Type: filesandordirs; Name: "{app}\resources\app\node_modules.asar.unpacked"; Check: IsNotBackgroundUpdate +Type: files; Name: "{app}\resources\app\node_modules.asar"; Check: IsNotBackgroundUpdate +Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html"; Check: IsNotBackgroundUpdate [UninstallDelete] Type: filesandordirs; Name: "{app}\_" @@ -77,6 +77,7 @@ Type: filesandordirs; Name: "{app}\_" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1 +Name: "addcontextmenufolders"; Description: "{cm:AddContextMenuFolders,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameLong}}"; GroupDescription: "{cm:Other}"; Flags: unchecked Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}" Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent @@ -142,6 +143,16 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ipynb Root: {#EnvironmentRootKey}; Subkey: "{#EnvironmentKey}"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin')) [Code] +function IsBackgroundUpdate(): Boolean; +begin + Result := ExpandConstant('{param:update|false}') <> 'false'; +end; + +function IsNotBackgroundUpdate(): Boolean; +begin + Result := not IsBackgroundUpdate(); +end; + // Don't allow installing conflicting architectures function InitializeSetup(): Boolean; var @@ -194,6 +205,13 @@ begin MsgBox('Please uninstall the ' + AltArch + '-bit version of {#NameShort} before installing this ' + ThisArch + '-bit version.', mbInformation, MB_OK); end; end; + + if IsNotBackgroundUpdate() and CheckForMutexes('{#TunnelMutex}') then + begin + MsgBox('{#NameShort} is still running a tunnel. Please stop the tunnel before installing.', mbInformation, MB_OK); + Result := false + end; + end; function WizardNotSilent(): Boolean; @@ -202,14 +220,44 @@ begin end; // Updates -function IsBackgroundUpdate(): Boolean; + +var + ShouldRestartTunnelService: Boolean; + +procedure StopTunnelServiceIfNeeded(); +var + StopServiceResultCode: Integer; + WaitCounter: Integer; begin - Result := ExpandConstant('{param:update|false}') <> 'false'; + ShouldRestartTunnelService := False; + if CheckForMutexes('{#TunnelServiceMutex}') then begin + // stop the tunnel service + Log('Stopping the tunnel service using ' + ExpandConstant('"{app}\bin\{#ApplicationName}.cmd"')); + ShellExec('', ExpandConstant('"{app}\bin\{#ApplicationName}.cmd"'), 'tunnel service uninstall', '', SW_HIDE, ewWaitUntilTerminated, StopServiceResultCode); + + Log('Stopping the tunnel service completed with result code ' + IntToStr(StopServiceResultCode)); + + WaitCounter := 10; + while (WaitCounter > 0) and CheckForMutexes('{#TunnelServiceMutex}') do + begin + Log('Tunnel service is still running, waiting'); + Sleep(500); + WaitCounter := WaitCounter - 1 + end; + if CheckForMutexes('{#TunnelServiceMutex}') then + Log('Unable to stop tunnel service') + else + ShouldRestartTunnelService := True; + end end; -function IsNotUpdate(): Boolean; + +// called before the wizard checks for running application +function PrepareToInstall(var NeedsRestart: Boolean): String; begin - Result := not IsBackgroundUpdate(); + if IsNotBackgroundUpdate() then + StopTunnelServiceIfNeeded(); + Result := '' end; // AzureDataStudio will create a flag file before the update starts (/update=C:\foo\bar) @@ -228,6 +276,11 @@ begin Result := True; end; +function IsWindows11OrLater(): Boolean; +begin + Result := (GetWindowsVersion >= $0A0055F0); +end; + function GetAppMutex(Value: string): string; begin if IsBackgroundUpdate() then @@ -252,21 +305,69 @@ begin Result := 'false'; end; +function QualityIsInsiders(): boolean; +begin + if '{#Quality}' = 'insider' then + Result := True + else + Result := False; +end; + +#ifdef AppxPackageFullname +procedure AddAppxPackage(); +var + AddAppxPackageResultCode: Integer; +begin + if WizardIsTaskSelected('addcontextmenufiles') then begin + ShellExec('', 'powershell.exe', '-Command ' + AddQuotes('Add-AppxPackage -Path ''' + ExpandConstant('{app}\appx\{#AppxPackage}') + ''' -ExternalLocation ''' + ExpandConstant('{app}\appx') + ''''), '', SW_HIDE, ewWaitUntilTerminated, AddAppxPackageResultCode); + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\*\shell\{#RegValueName}'); + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\directory\shell\{#RegValueName}'); + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\directory\background\shell\{#RegValueName}'); + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\Drive\shell\{#RegValueName}'); + end; +end; + +procedure RemoveAppxPackage(); +var + RemoveAppxPackageResultCode: Integer; +begin + ShellExec('', 'powershell.exe', '-Command ' + AddQuotes('Remove-AppxPackage -Package ''{#AppxPackageFullname}'''), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode); + if not WizardIsTaskSelected('addcontextmenufiles') then begin + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\{#RegValueName}ContextMenu'); + end; +end; +#endif + procedure CurStepChanged(CurStep: TSetupStep); var UpdateResultCode: Integer; + StartServiceResultCode: Integer; begin - if IsBackgroundUpdate() and (CurStep = ssPostInstall) then + if CurStep = ssPostInstall then begin - CreateMutex('{#AppMutex}-ready'); - - while (CheckForMutexes('{#AppMutex}')) do + if IsBackgroundUpdate() then begin - Log('Application is still running, waiting'); - Sleep(1000); + CreateMutex('{#AppMutex}-ready'); + + while (CheckForMutexes('{#AppMutex}')) do + begin + Log('Application is still running, waiting'); + Sleep(1000) + end; + + StopTunnelServiceIfNeeded(); + + Exec(ExpandConstant('{app}\tools\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists())), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); end; - Exec(ExpandConstant('{app}\tools\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists())), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); + if ShouldRestartTunnelService then + begin + // start the tunnel service + Log('Restarting the tunnel service...'); + ShellExec('', ExpandConstant('"{app}\bin\{#ApplicationName}.cmd"'), 'tunnel service install', '', SW_HIDE, ewWaitUntilTerminated, StartServiceResultCode); + Log('Starting the tunnel service completed with result code ' + IntToStr(StartServiceResultCode)); + ShouldRestartTunnelService := False + end; end; end; diff --git a/build/win32/explorer-appx-fetcher.js b/build/win32/explorer-appx-fetcher.js new file mode 100644 index 0000000000..79df094c18 --- /dev/null +++ b/build/win32/explorer-appx-fetcher.js @@ -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. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.downloadExplorerAppx = void 0; +const fs = require("fs"); +const debug = require("debug"); +const extract = require("extract-zip"); +const path = require("path"); +const get_1 = require("@electron/get"); +const root = path.dirname(path.dirname(__dirname)); +const d = debug('explorer-appx-fetcher'); +async function downloadExplorerAppx(outDir, quality = 'stable', targetArch = 'x64') { + const fileNamePrefix = quality === 'insider' ? 'code_insiders' : 'code'; + const fileName = `${fileNamePrefix}_explorer_${targetArch}.zip`; + if (await fs.existsSync(path.resolve(outDir, 'resources.pri'))) { + return; + } + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); + } + d(`downloading ${fileName}`); + const artifact = await (0, get_1.downloadArtifact)({ + isGeneric: true, + version: '3.0.4', + artifactName: fileName, + unsafelyDisableChecksums: true, + mirrorOptions: { + mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/', + customDir: '3.0.4', + customFilename: fileName + } + }); + d(`unpacking from ${fileName}`); + await extract(artifact, { dir: fs.realpathSync(outDir) }); +} +exports.downloadExplorerAppx = downloadExplorerAppx; +async function main(outputDir) { + let arch = process.env['VSCODE_ARCH']; + if (!outputDir) { + throw new Error('Required build env not set'); + } + if (arch === 'ia32') { + arch = 'x86'; + } + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); + await downloadExplorerAppx(outputDir, product.quality, arch); +} +if (require.main === module) { + main(process.argv[2]).catch(err => { + console.error(err); + process.exit(1); + }); +} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhwbG9yZXItYXBweC1mZXRjaGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZXhwbG9yZXItYXBweC1mZXRjaGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Z0dBR2dHO0FBRWhHLFlBQVksQ0FBQzs7O0FBRWIseUJBQXlCO0FBQ3pCLCtCQUErQjtBQUMvQix1Q0FBdUM7QUFDdkMsNkJBQTZCO0FBQzdCLHVDQUFpRDtBQUVqRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztBQUVuRCxNQUFNLENBQUMsR0FBRyxLQUFLLENBQUMsdUJBQXVCLENBQUMsQ0FBQztBQUVsQyxLQUFLLFVBQVUsb0JBQW9CLENBQUMsTUFBYyxFQUFFLFVBQWtCLFFBQVEsRUFBRSxhQUFxQixLQUFLO0lBQ2hILE1BQU0sY0FBYyxHQUFHLE9BQU8sS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDO0lBQ3hFLE1BQU0sUUFBUSxHQUFHLEdBQUcsY0FBYyxhQUFhLFVBQVUsTUFBTSxDQUFDO0lBRWhFLElBQUksTUFBTSxFQUFFLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxFQUFFLGVBQWUsQ0FBQyxDQUFDLEVBQUU7UUFDL0QsT0FBTztLQUNQO0lBRUQsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsRUFBRTtRQUNqQyxNQUFNLEVBQUUsQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7S0FDaEQ7SUFFRCxDQUFDLENBQUMsZUFBZSxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQzdCLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBQSxzQkFBZ0IsRUFBQztRQUN2QyxTQUFTLEVBQUUsSUFBSTtRQUNmLE9BQU8sRUFBRSxPQUFPO1FBQ2hCLFlBQVksRUFBRSxRQUFRO1FBQ3RCLHdCQUF3QixFQUFFLElBQUk7UUFDOUIsYUFBYSxFQUFFO1lBQ2QsTUFBTSxFQUFFLHlFQUF5RTtZQUNqRixTQUFTLEVBQUUsT0FBTztZQUNsQixjQUFjLEVBQUUsUUFBUTtTQUN4QjtLQUNELENBQUMsQ0FBQztJQUVILENBQUMsQ0FBQyxrQkFBa0IsUUFBUSxFQUFFLENBQUMsQ0FBQztJQUNoQyxNQUFNLE9BQU8sQ0FBQyxRQUFRLEVBQUUsRUFBRSxHQUFHLEVBQUUsRUFBRSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLENBQUM7QUFDM0QsQ0FBQztBQTNCRCxvREEyQkM7QUFFRCxLQUFLLFVBQVUsSUFBSSxDQUFDLFNBQWtCO0lBQ3JDLElBQUksSUFBSSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7SUFFdEMsSUFBSSxDQUFDLFNBQVMsRUFBRTtRQUNmLE1BQU0sSUFBSSxLQUFLLENBQUMsNEJBQTRCLENBQUMsQ0FBQztLQUM5QztJQUVELElBQUksSUFBSSxLQUFLLE1BQU0sRUFBRTtRQUNwQixJQUFJLEdBQUcsS0FBSyxDQUFDO0tBQ2I7SUFFRCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsY0FBYyxDQUFDLEVBQUUsTUFBTSxDQUFDLENBQUMsQ0FBQztJQUNyRixNQUFNLG9CQUFvQixDQUFDLFNBQVMsRUFBRyxPQUFlLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQ3ZFLENBQUM7QUFFRCxJQUFJLE9BQU8sQ0FBQyxJQUFJLEtBQUssTUFBTSxFQUFFO0lBQzVCLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxFQUFFO1FBQ2pDLE9BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDbkIsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNqQixDQUFDLENBQUMsQ0FBQztDQUNIIn0= \ No newline at end of file diff --git a/build/win32/explorer-appx-fetcher.ts b/build/win32/explorer-appx-fetcher.ts new file mode 100644 index 0000000000..3460c3ed42 --- /dev/null +++ b/build/win32/explorer-appx-fetcher.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fs from 'fs'; +import * as debug from 'debug'; +import * as extract from 'extract-zip'; +import * as path from 'path'; +import { downloadArtifact } from '@electron/get'; + +const root = path.dirname(path.dirname(__dirname)); + +const d = debug('explorer-appx-fetcher'); + +export async function downloadExplorerAppx(outDir: string, quality: string = 'stable', targetArch: string = 'x64'): Promise { + const fileNamePrefix = quality === 'insider' ? 'code_insiders' : 'code'; + const fileName = `${fileNamePrefix}_explorer_${targetArch}.zip`; + + if (await fs.existsSync(path.resolve(outDir, 'resources.pri'))) { + return; + } + + if (!await fs.existsSync(outDir)) { + await fs.mkdirSync(outDir, { recursive: true }); + } + + d(`downloading ${fileName}`); + const artifact = await downloadArtifact({ + isGeneric: true, + version: '3.0.4', + artifactName: fileName, + unsafelyDisableChecksums: true, + mirrorOptions: { + mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/', + customDir: '3.0.4', + customFilename: fileName + } + }); + + d(`unpacking from ${fileName}`); + await extract(artifact, { dir: fs.realpathSync(outDir) }); +} + +async function main(outputDir?: string): Promise { + let arch = process.env['VSCODE_ARCH']; + + if (!outputDir) { + throw new Error('Required build env not set'); + } + + if (arch === 'ia32') { + arch = 'x86'; + } + + const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); + await downloadExplorerAppx(outputDir, (product as any).quality, arch); +} + +if (require.main === module) { + main(process.argv[2]).catch(err => { + console.error(err); + process.exit(1); + }); +} diff --git a/build/win32/i18n/messages.es.isl b/build/win32/i18n/messages.es.isl index e51f099f9a..0ba4d0c44f 100644 --- a/build/win32/i18n/messages.es.isl +++ b/build/win32/i18n/messages.es.isl @@ -6,4 +6,4 @@ AddToPath=Agregar a PATH (disponible despu RunAfter=Ejecutar %1 después de la instalación Other=Otros: SourceFile=Archivo de origen %1 -OpenWithCodeContextMenu=Abrir con %1 \ No newline at end of file +OpenWithCodeContextMenu=Abrir &con %1 diff --git a/build/yarn.lock b/build/yarn.lock index a0964d15ec..a0dfbca745 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -148,11 +148,12 @@ "@azure/abort-controller" "^1.0.0" tslib "^2.2.0" -"@azure/cosmos@^3.14.1": - version "3.17.1" - resolved "https://registry.npmjs.org/@azure/cosmos/-/cosmos-3.17.1.tgz" - integrity sha512-3pgPwNwAiTgiH/OgcntDLzrANy+roaaDFYoLOhC4bxoDC94nPCjpLYRRwueIpisZAdopPVrxQloNs9fEjVlL0A== +"@azure/cosmos@^3.17.3": + version "3.17.3" + resolved "https://registry.yarnpkg.com/@azure/cosmos/-/cosmos-3.17.3.tgz#380398496af8ef3473ae0a9ad8cdbab32d91eb08" + integrity sha512-wBglkQ6Irjv5Vo2iw8fd6eYj60WYRSSg4/0DBkeOP6BwQ4RA91znsOHd6s3qG6UAbNgYuzC9Nnq07vlFFZkHEw== dependencies: + "@azure/abort-controller" "^1.0.0" "@azure/core-auth" "^1.3.0" "@azure/core-rest-pipeline" "^1.2.0" "@azure/core-tracing" "^1.0.0" @@ -166,10 +167,10 @@ universal-user-agent "^6.0.0" uuid "^8.3.0" -"@azure/identity@^3.1.4": - version "3.1.4" - resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-3.1.4.tgz#7091ded0b8799096886fc3b00dc3b528479a0f6c" - integrity sha512-USvxmO6p7dFEcz1e0Kq/WQY6YbvX8hOeIibxxLBQC4Pxl4QK+DL72agZ9e5RYZc55QjC7Ja6q1+mQaXfmbrLhA== +"@azure/identity@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-2.1.0.tgz#89f0bfc1d1264dfd3d0cb19837c33a9c6706d548" + integrity sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw== dependencies: "@azure/abort-controller" "^1.0.0" "@azure/core-auth" "^1.3.0" @@ -178,9 +179,9 @@ "@azure/core-tracing" "^1.0.0" "@azure/core-util" "^1.0.0" "@azure/logger" "^1.0.0" - "@azure/msal-browser" "^2.32.2" - "@azure/msal-common" "^9.0.2" - "@azure/msal-node" "^1.14.6" + "@azure/msal-browser" "^2.26.0" + "@azure/msal-common" "^7.0.0" + "@azure/msal-node" "^1.10.0" events "^3.0.0" jws "^4.0.0" open "^8.0.0" @@ -195,7 +196,7 @@ dependencies: tslib "^2.0.0" -"@azure/msal-browser@^2.32.2": +"@azure/msal-browser@^2.26.0": version "2.37.0" resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.37.0.tgz#32d7af74eef53f2692f8a9d6bd6818c78faf4c1b" integrity sha512-YNGD/W/tw/5wDWlXOfmrVILaxVsorVLxYU2ovmL1PDvxkdudbQRyGk/76l4emqgDAl/kPQeqyivxjOU6w1YfvQ== @@ -207,12 +208,12 @@ resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-13.0.0.tgz#9c39184903b5d0fd6e643ccc12193fae220e912b" integrity sha512-GqCOg5H5bouvLij9NFXFkh+asRRxsPBRwnTDsfK7o0KcxYHJbuidKw8/VXpycahGXNxgtuhqtK/n5he+5NhyEA== -"@azure/msal-common@^9.0.2": - version "9.1.1" - resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-9.1.1.tgz#906d27905c956fe91bd8f31855fc624359098d83" - integrity sha512-we9xR8lvu47fF0h+J8KyXoRy9+G/fPzm3QEa2TrdR3jaVS3LKAyE2qyMuUkNdbVkvzl8Zr9f7l+IUSP22HeqXw== +"@azure/msal-common@^7.0.0": + version "7.6.0" + resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-7.6.0.tgz#b52e97ef540275f72611cff57937dfa0b34cdcca" + integrity sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q== -"@azure/msal-node@^1.14.6": +"@azure/msal-node@^1.10.0": version "1.17.2" resolved "https://registry.yarnpkg.com/@azure/msal-node/-/msal-node-1.17.2.tgz#42566443e0cdf476bcb43854c9fe47a2def40baf" integrity sha512-l8edYnA2LQj4ue3pjxVz1Qy4HuU5xbcoebfe2bGTRvBL9Q6n2Df47aGftkLIyimD1HxHuA4ZZOe23a/HshoYXw== @@ -251,10 +252,122 @@ global-agent "^2.0.2" global-tunnel-ng "^2.7.1" -"@esbuild/linux-loong64@0.14.54": - version "0.14.54" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028" - integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw== +"@esbuild/android-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.14.tgz#4624cea3c8941c91f9e9c1228f550d23f1cef037" + integrity sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg== + +"@esbuild/android-arm@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.14.tgz#74fae60fcab34c3f0e15cb56473a6091ba2b53a6" + integrity sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g== + +"@esbuild/android-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.14.tgz#f002fbc08d5e939d8314bd23bcfb1e95d029491f" + integrity sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng== + +"@esbuild/darwin-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.14.tgz#b8dcd79a1dd19564950b4ca51d62999011e2e168" + integrity sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw== + +"@esbuild/darwin-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.14.tgz#4b49f195d9473625efc3c773fc757018f2c0d979" + integrity sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g== + +"@esbuild/freebsd-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.14.tgz#480923fd38f644c6342c55e916cc7c231a85eeb7" + integrity sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A== + +"@esbuild/freebsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.14.tgz#a6b6b01954ad8562461cb8a5e40e8a860af69cbe" + integrity sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw== + +"@esbuild/linux-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.14.tgz#1fe2f39f78183b59f75a4ad9c48d079916d92418" + integrity sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g== + +"@esbuild/linux-arm@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.14.tgz#18d594a49b64e4a3a05022c005cb384a58056a2a" + integrity sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg== + +"@esbuild/linux-ia32@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.14.tgz#f7f0182a9cfc0159e0922ed66c805c9c6ef1b654" + integrity sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ== + +"@esbuild/linux-loong64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.14.tgz#5f5305fdffe2d71dd9a97aa77d0c99c99409066f" + integrity sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ== + +"@esbuild/linux-mips64el@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.14.tgz#a602e85c51b2f71d2aedfe7f4143b2f92f97f3f5" + integrity sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg== + +"@esbuild/linux-ppc64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.14.tgz#32d918d782105cbd9345dbfba14ee018b9c7afdf" + integrity sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ== + +"@esbuild/linux-riscv64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.14.tgz#38612e7b6c037dff7022c33f49ca17f85c5dec58" + integrity sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw== + +"@esbuild/linux-s390x@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.14.tgz#4397dff354f899e72fd035d72af59a700c465ccb" + integrity sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww== + +"@esbuild/linux-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.14.tgz#6c5cb99891b6c3e0c08369da3ef465e8038ad9c2" + integrity sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw== + +"@esbuild/netbsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.14.tgz#5fa5255a64e9bf3947c1b3bef5e458b50b211994" + integrity sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ== + +"@esbuild/openbsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.14.tgz#74d14c79dcb6faf446878cc64284aa4e02f5ca6f" + integrity sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g== + +"@esbuild/sunos-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.14.tgz#5c7d1c7203781d86c2a9b2ff77bd2f8036d24cfa" + integrity sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA== + +"@esbuild/win32-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.14.tgz#dc36ed84f1390e73b6019ccf0566c80045e5ca3d" + integrity sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ== + +"@esbuild/win32-ia32@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.14.tgz#0802a107afa9193c13e35de15a94fe347c588767" + integrity sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w== + +"@esbuild/win32-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.14.tgz#e81fb49de05fed91bf74251c9ca0343f4fc77d31" + integrity sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA== + +"@eslint-community/eslint-utils@^4.2.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" "@malept/cross-spawn-promise@^1.1.0": version "1.1.1" @@ -410,11 +523,6 @@ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^4.0.0": - version "4.0.0" - resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz" - integrity sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ== - "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz" @@ -422,13 +530,6 @@ dependencies: defer-to-connect "^1.0.1" -"@szmarczak/http-timer@^4.0.5": - version "4.0.5" - resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz" - integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ== - dependencies: - defer-to-connect "^2.0.0" - "@tootallnate/once@2": version "2.0.0" resolved "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz" @@ -453,21 +554,6 @@ dependencies: "@types/node" "*" -"@types/cacheable-request@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz" - integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "*" - "@types/node" "*" - "@types/responselike" "*" - -"@types/caseless@*": - version "0.12.1" - resolved "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz" - integrity sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A== - "@types/cssnano@^4.0.0": version "4.0.1" resolved "https://registry.npmjs.org/@types/cssnano/-/cssnano-4.0.1.tgz" @@ -497,14 +583,6 @@ resolved "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/eslint@4.16.1": - version "4.16.1" - resolved "https://registry.npmjs.org/@types/eslint/-/eslint-4.16.1.tgz" - integrity sha512-lRUXQAULl5geixTiP2K0iYvMUbCkEnuOwvLGjwff12I4ECxoW5QaWML5UUOZ1CvpQLILkddBdMPMZz4ByQizsg== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - "@types/estree@*": version "0.0.41" resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.41.tgz" @@ -520,13 +598,6 @@ resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.1.tgz#dd94fbc8c2e2ab8ab402ca8d04bb8c34965f0696" integrity sha512-31Dt9JaGfHretvwVxCBrCFL5iC9MQ3zOXpu+8C4qzW0cxc5rJJVGxB5c/vZ+wmeTk/JjPz/D0gv8BZ+Ip6iCqQ== -"@types/form-data@*": - version "2.2.1" - resolved "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz" - integrity sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ== - dependencies: - "@types/node" "*" - "@types/fs-extra@^9.0.12": version "9.0.12" resolved "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz" @@ -613,33 +684,16 @@ "@types/vinyl-fs" "*" chokidar "^3.3.1" -"@types/http-cache-semantics@*": - version "4.0.0" - resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz" - integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== - "@types/js-beautify@*": version "1.8.0" resolved "https://registry.npmjs.org/@types/js-beautify/-/js-beautify-1.8.0.tgz" integrity sha512-/siF86XrwDKLuHe8l7h6NhrAWgLdgqbxmjZv9NvGWmgYRZoTipkjKiWb0SQHy/jcR+ee0GvbG6uGd+LEBMGNvA== -"@types/json-schema@*": - version "7.0.4" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz" - integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== - "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/keyv@*": - version "3.1.1" - resolved "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz" - integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw== - dependencies: - "@types/node" "*" - "@types/mime@0.0.29": version "0.0.29" resolved "https://registry.npmjs.org/@types/mime/-/mime-0.0.29.tgz" @@ -712,16 +766,6 @@ dependencies: "@types/node" "*" -"@types/request@^2.47.0": - version "2.47.0" - resolved "https://registry.npmjs.org/@types/request/-/request-2.47.0.tgz" - integrity sha512-/KXM5oev+nNCLIgBjkwbk8VqxmzI56woD4VUxn95O+YeQ8hJzcSmIZ1IN3WexiqBb6srzDo2bdMbsXxgXNkz5Q== - dependencies: - "@types/caseless" "*" - "@types/form-data" "*" - "@types/node" "*" - "@types/tough-cookie" "*" - "@types/resolve@0.0.8": version "0.0.8" resolved "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz" @@ -729,13 +773,6 @@ dependencies: "@types/node" "*" -"@types/responselike@*", "@types/responselike@^1.0.0": - version "1.0.0" - resolved "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz" - integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== - dependencies: - "@types/node" "*" - "@types/rimraf@^2.0.4": version "2.0.4" resolved "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.4.tgz" @@ -744,15 +781,10 @@ "@types/glob" "*" "@types/node" "*" -"@types/source-list-map@*": - version "0.1.2" - resolved "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz" - integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== - -"@types/tapable@^1": - version "1.0.8" - resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz" - integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== +"@types/semver@^7.3.12": + version "7.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== "@types/through2@^2.0.36": version "2.0.38" @@ -773,11 +805,6 @@ resolved "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.1.tgz" integrity sha512-7cTXwKP/HLOPVgjg+YhBdQ7bMiobGMuoBmrGmqwIWJv8elC6t1DfVc/mn4fD9UE1IjhwmhaQ5pGVXkmXbH0rhg== -"@types/tough-cookie@*": - version "2.3.2" - resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.2.tgz" - integrity sha512-vOVmaruQG5EatOU/jM6yU2uCp3Lz6mK1P5Ztu4iJjfM4SVHU9XYktPUQtKlIXuahqXHdEyUarMrBEwg5Cwu+bA== - "@types/tunnel@^0.0.3": version "0.0.3" resolved "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz" @@ -785,13 +812,6 @@ dependencies: "@types/node" "*" -"@types/uglify-js@*": - version "3.13.1" - resolved "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.13.1.tgz" - integrity sha512-O3MmRAk6ZuAKa9CHgg0Pr0+lUOqoMLpc9AS4R8ano2auvsg7IE8syF3Xh/NPr26TWklxYcqoEEFdzLLs1fV9PQ== - dependencies: - source-map "^0.6.1" - "@types/underscore@^1.8.9": version "1.8.9" resolved "https://registry.npmjs.org/@types/underscore/-/underscore-1.8.9.tgz" @@ -828,27 +848,6 @@ dependencies: "@types/node" "*" -"@types/webpack-sources@*": - version "2.1.1" - resolved "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-2.1.1.tgz" - integrity sha512-MjM1R6iuw8XaVbtkCBz0N349cyqBjJHCbQiOeppe3VBeFvxqs74RKHAVt9LkxTnUWc7YLZOEsUfPUnmK6SBPKQ== - dependencies: - "@types/node" "*" - "@types/source-list-map" "*" - source-map "^0.7.3" - -"@types/webpack@^4.41.25": - version "4.41.30" - resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.30.tgz" - integrity sha512-GUHyY+pfuQ6haAfzu4S14F+R5iGRwN6b2FRNJY7U0NilmFAqbsOfK6j1HwuLBAqwRIT+pVdNDJGJ6e8rpp0KHA== - dependencies: - "@types/node" "*" - "@types/tapable" "^1" - "@types/uglify-js" "*" - "@types/webpack-sources" "*" - anymatch "^3.0.0" - source-map "^0.6.0" - "@types/xml2js@0.4.11": version "0.4.11" resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.11.tgz#bf46a84ecc12c41159a7bd9cf51ae84129af0e79" @@ -863,101 +862,59 @@ dependencies: "@types/node" "*" -"@typescript-eslint/experimental-utils@~5.10.0": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.10.2.tgz#dbb541e2070c7bd6e63d3e3a55b58be73a8fbb34" - integrity sha512-stRnIlxDduzxtaVLtEohESoXI1k7J6jvJHGyIkOT2pvXbg5whPM6f9tzJ51bJJxaJTdmvwgVFDNCopFRb2F5Gw== +"@typescript-eslint/experimental-utils@^5.57.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.60.0.tgz#48ffa47238592397c3d857fe1403eed3b1d5e604" + integrity sha512-ovid3u7CNBrr0Ct35LUPkNYH4e+z4Kc6dPfSG99oMmH9SfoEoefq09uSnJI4mUb/UM7a/peVM03G+MzLxrD16g== dependencies: - "@typescript-eslint/utils" "5.10.2" + "@typescript-eslint/utils" "5.60.0" -"@typescript-eslint/parser@^5.10.0": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz" - integrity sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw== +"@typescript-eslint/scope-manager@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.60.0.tgz#ae511967b4bd84f1d5e179bb2c82857334941c1c" + integrity sha512-hakuzcxPwXi2ihf9WQu1BbRj1e/Pd8ZZwVTG9kfbxAMZstKz8/9OoexIwnmLzShtsdap5U/CoQGRCWlSuPbYxQ== dependencies: - "@typescript-eslint/scope-manager" "5.38.1" - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/typescript-estree" "5.38.1" - debug "^4.3.4" + "@typescript-eslint/types" "5.60.0" + "@typescript-eslint/visitor-keys" "5.60.0" -"@typescript-eslint/scope-manager@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.2.tgz#92c0bc935ec00f3d8638cdffb3d0e70c9b879639" - integrity sha512-39Tm6f4RoZoVUWBYr3ekS75TYgpr5Y+X0xLZxXqcZNDWZdJdYbKd3q2IR4V9y5NxxiPu/jxJ8XP7EgHiEQtFnw== +"@typescript-eslint/types@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.60.0.tgz#3179962b28b4790de70e2344465ec97582ce2558" + integrity sha512-ascOuoCpNZBccFVNJRSC6rPq4EmJ2NkuoKnd6LDNyAQmdDnziAtxbCGWCbefG1CNzmDvd05zO36AmB7H8RzKPA== + +"@typescript-eslint/typescript-estree@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.60.0.tgz#4ddf1a81d32a850de66642d9b3ad1e3254fb1600" + integrity sha512-R43thAuwarC99SnvrBmh26tc7F6sPa2B3evkXp/8q954kYL6Ro56AwASYWtEEi+4j09GbiNAHqYwNNZuNlARGQ== dependencies: - "@typescript-eslint/types" "5.10.2" - "@typescript-eslint/visitor-keys" "5.10.2" - -"@typescript-eslint/scope-manager@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz" - integrity sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ== - dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" - -"@typescript-eslint/types@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.2.tgz#604d15d795c4601fffba6ecb4587ff9fdec68ce8" - integrity sha512-Qfp0qk/5j2Rz3p3/WhWgu4S1JtMcPgFLnmAKAW061uXxKSa7VWKZsDXVaMXh2N60CX9h6YLaBoy9PJAfCOjk3w== - -"@typescript-eslint/types@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.1.tgz" - integrity sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg== - -"@typescript-eslint/typescript-estree@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.2.tgz#810906056cd3ddcb35aa333fdbbef3713b0fe4a7" - integrity sha512-WHHw6a9vvZls6JkTgGljwCsMkv8wu8XU8WaYKeYhxhWXH/atZeiMW6uDFPLZOvzNOGmuSMvHtZKd6AuC8PrwKQ== - dependencies: - "@typescript-eslint/types" "5.10.2" - "@typescript-eslint/visitor-keys" "5.10.2" - debug "^4.3.2" - globby "^11.0.4" - is-glob "^4.0.3" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz" - integrity sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g== - dependencies: - "@typescript-eslint/types" "5.38.1" - "@typescript-eslint/visitor-keys" "5.38.1" + "@typescript-eslint/types" "5.60.0" + "@typescript-eslint/visitor-keys" "5.60.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.10.2.tgz#1fcd37547c32c648ab11aea7173ec30060ee87a8" - integrity sha512-vuJaBeig1NnBRkf7q9tgMLREiYD7zsMrsN1DA3wcoMDvr3BTFiIpKjGiYZoKPllfEwN7spUjv7ZqD+JhbVjEPg== +"@typescript-eslint/utils@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.60.0.tgz#4667c5aece82f9d4f24a667602f0f300864b554c" + integrity sha512-ba51uMqDtfLQ5+xHtwlO84vkdjrqNzOnqrnwbMHMRY8Tqeme8C2Q8Fc7LajfGR+e3/4LoYiWXUM6BpIIbHJ4hQ== dependencies: + "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.10.2" - "@typescript-eslint/types" "5.10.2" - "@typescript-eslint/typescript-estree" "5.10.2" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.60.0" + "@typescript-eslint/types" "5.60.0" + "@typescript-eslint/typescript-estree" "5.60.0" eslint-scope "^5.1.1" - eslint-utils "^3.0.0" + semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.10.2": - version "5.10.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.2.tgz#fdbf272d8e61c045d865bd6c8b41bea73d222f3d" - integrity sha512-zHIhYGGGrFJvvyfwHk5M08C5B5K4bewkm+rrvNTKk1/S15YHR+SA/QUF8ZWscXSfEaB8Nn2puZj+iHcoxVOD/Q== +"@typescript-eslint/visitor-keys@5.60.0": + version "5.60.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.60.0.tgz#b48b29da3f5f31dd1656281727004589d2722a66" + integrity sha512-wm9Uz71SbCyhUKgcaPRauBdTegUyY/ZWl8gLwD/i/ybJqscrrdVSFImpvUz16BLPChIeKBK5Fa9s6KDQjsjyWw== dependencies: - "@typescript-eslint/types" "5.10.2" - eslint-visitor-keys "^3.0.0" - -"@typescript-eslint/visitor-keys@5.38.1": - version "5.38.1" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz" - integrity sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA== - dependencies: - "@typescript-eslint/types" "5.38.1" + "@typescript-eslint/types" "5.60.0" eslint-visitor-keys "^3.3.0" "@vscode/iconv-lite-umd@0.7.0": @@ -965,10 +922,10 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/vsce@2.16.0": - version "2.16.0" - resolved "https://registry.yarnpkg.com/@vscode/vsce/-/vsce-2.16.0.tgz#a3ddcf7e84914576f35d891e236bc496c568776f" - integrity sha512-BhJ0zO7UxShLFBZM6jwOLt1ZVoqQ4r5Lj/kHNeYp0ICPXhz/erqBSMQnHkRgkjn2L/bh+TYFGkZyguhu/SKsjw== +"@vscode/vsce@^2.16.0": + version "2.19.0" + resolved "https://registry.yarnpkg.com/@vscode/vsce/-/vsce-2.19.0.tgz#342225662811245bc40d855636d000147c394b11" + integrity sha512-dAlILxC5ggOutcvJY24jxz913wimGiUrHaPkk16Gm9/PGFbz1YezWtrXsTKUtJws4fIlpX2UIlVlVESWq8lkfQ== dependencies: azure-devops-node-api "^11.0.1" chalk "^2.4.2" @@ -976,6 +933,7 @@ commander "^6.1.0" glob "^7.0.6" hosted-git-info "^4.0.2" + jsonc-parser "^3.2.0" leven "^3.1.0" markdown-it "^12.3.2" mime "^1.3.4" @@ -986,7 +944,7 @@ tmp "^0.2.1" typed-rest-client "^1.8.4" url-join "^4.0.1" - xml2js "^0.4.23" + xml2js "^0.5.0" yauzl "^2.3.1" yazl "^2.2.2" optionalDependencies: @@ -1052,14 +1010,6 @@ ansi-wrap@0.1.0, ansi-wrap@^0.1.0: resolved "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz" integrity sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw== -anymatch@^3.0.0: - version "3.1.2" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - anymatch@^3.1.1, anymatch@~3.1.1: version "3.1.3" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" @@ -1092,7 +1042,7 @@ are-we-there-yet@~1.1.2: argparse@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== arr-diff@^4.0.0: @@ -1160,9 +1110,9 @@ axios@0.21.4: follow-redirects "^1.14.0" azure-devops-node-api@^11.0.1: - version "11.1.1" - resolved "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.1.1.tgz" - integrity sha512-XDG91XzLZ15reP12s3jFkKS8oiagSICjnLwxEYieme4+4h3ZveFOFRA4iYIG40RyHXsiI0mefFYYMFIJbMpWcg== + version "11.2.0" + resolved "https://registry.yarnpkg.com/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz#bf04edbef60313117a0507415eed4790a420ad6b" + integrity sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA== dependencies: tunnel "0.0.6" typed-rest-client "^1.8.4" @@ -1208,8 +1158,8 @@ bluebird@^3.5.0: boolbase@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== boolean@^3.0.1: version "3.1.2" @@ -1287,11 +1237,6 @@ byline@^5.0.0: resolved "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz" - integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== - cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz" @@ -1305,22 +1250,9 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" -cacheable-request@^7.0.2: - version "7.0.2" - resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz" - integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^6.0.1" - responselike "^2.0.0" - call-bind@^1.0.0: version "1.0.2" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" @@ -1345,7 +1277,7 @@ chalk@^3.0.0: cheerio-select@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-2.1.0.tgz#4d8673286b8126ca2a8e42740d5e3c4884ae21b4" integrity sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g== dependencies: boolbase "^1.0.0" @@ -1356,9 +1288,9 @@ cheerio-select@^2.1.0: domutils "^3.0.1" cheerio@^1.0.0-rc.9: - version "1.0.0-rc.11" - resolved "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.11.tgz" - integrity sha512-bQwNaDIBKID5ts/DsdhxrjqFXYfLw4ste+wMKqWA8DyKcS4qwsPP4Bk8ZNaTJjvpiX/qW3BT4sU7d6Bh5i+dag== + version "1.0.0-rc.12" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.12.tgz#788bf7466506b1c6bf5fae51d24a2c4d62e47683" + integrity sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q== dependencies: cheerio-select "^2.1.0" dom-serializer "^2.0.0" @@ -1367,7 +1299,6 @@ cheerio@^1.0.0-rc.9: htmlparser2 "^8.0.1" parse5 "^7.0.0" parse5-htmlparser2-tree-adapter "^7.0.0" - tslib "^2.4.0" chokidar@3.5.1, chokidar@^3.3.1: version "3.5.1" @@ -1490,7 +1421,7 @@ commander@^5.0.0: commander@^6.1.0: version "6.2.1" - resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== commander@^7.0.0: @@ -1553,7 +1484,7 @@ cross-spawn@^7.0.1: css-select@^5.1.0: version "5.1.0" - resolved "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== dependencies: boolbase "^1.0.0" @@ -1564,7 +1495,7 @@ css-select@^5.1.0: css-what@^6.1.0: version "6.1.0" - resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== debug@4, debug@^4.3.4: @@ -1626,11 +1557,6 @@ defer-to-connect@^1.0.1: resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz" integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ== -defer-to-connect@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz" - integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg== - define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" @@ -1665,7 +1591,7 @@ detect-libc@^1.0.3: detect-libc@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== detect-node@^2.0.4: @@ -1714,7 +1640,7 @@ documentdb@1.13.0: dom-serializer@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== dependencies: domelementtype "^2.3.0" @@ -1723,30 +1649,40 @@ dom-serializer@^2.0.0: domelementtype@^2.3.0: version "2.3.0" - resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domhandler@^5.0.1, domhandler@^5.0.2, domhandler@^5.0.3: +domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" - resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== dependencies: domelementtype "^2.3.0" domutils@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz" - integrity sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q== + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== dependencies: dom-serializer "^2.0.0" domelementtype "^2.3.0" - domhandler "^5.0.1" + domhandler "^5.0.3" duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= +duplexify@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" + integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== + dependencies: + end-of-stream "^1.4.1" + inherits "^2.0.3" + readable-stream "^3.1.1" + stream-shift "^1.0.0" + ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" @@ -1783,14 +1719,14 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -entities@^4.2.0, entities@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/entities/-/entities-4.3.0.tgz" - integrity sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg== +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== entities@~2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== env-paths@^2.2.0: @@ -1803,132 +1739,33 @@ es6-error@^4.1.1: resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild-android-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be" - integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ== - -esbuild-android-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771" - integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg== - -esbuild-darwin-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25" - integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug== - -esbuild-darwin-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73" - integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw== - -esbuild-freebsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d" - integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg== - -esbuild-freebsd-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48" - integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q== - -esbuild-linux-32@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5" - integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw== - -esbuild-linux-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652" - integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg== - -esbuild-linux-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b" - integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig== - -esbuild-linux-arm@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59" - integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw== - -esbuild-linux-mips64le@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34" - integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw== - -esbuild-linux-ppc64le@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e" - integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ== - -esbuild-linux-riscv64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8" - integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg== - -esbuild-linux-s390x@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6" - integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA== - -esbuild-netbsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81" - integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w== - -esbuild-openbsd-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b" - integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw== - -esbuild-sunos-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da" - integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw== - -esbuild-windows-32@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31" - integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w== - -esbuild-windows-64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4" - integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ== - -esbuild-windows-arm64@0.14.54: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982" - integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg== - -esbuild@^0.14.2: - version "0.14.54" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2" - integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA== +esbuild@0.17.14: + version "0.17.14" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.14.tgz#d61a22de751a3133f3c6c7f9c1c3e231e91a3245" + integrity sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw== optionalDependencies: - "@esbuild/linux-loong64" "0.14.54" - esbuild-android-64 "0.14.54" - esbuild-android-arm64 "0.14.54" - esbuild-darwin-64 "0.14.54" - esbuild-darwin-arm64 "0.14.54" - esbuild-freebsd-64 "0.14.54" - esbuild-freebsd-arm64 "0.14.54" - esbuild-linux-32 "0.14.54" - esbuild-linux-64 "0.14.54" - esbuild-linux-arm "0.14.54" - esbuild-linux-arm64 "0.14.54" - esbuild-linux-mips64le "0.14.54" - esbuild-linux-ppc64le "0.14.54" - esbuild-linux-riscv64 "0.14.54" - esbuild-linux-s390x "0.14.54" - esbuild-netbsd-64 "0.14.54" - esbuild-openbsd-64 "0.14.54" - esbuild-sunos-64 "0.14.54" - esbuild-windows-32 "0.14.54" - esbuild-windows-64 "0.14.54" - esbuild-windows-arm64 "0.14.54" + "@esbuild/android-arm" "0.17.14" + "@esbuild/android-arm64" "0.17.14" + "@esbuild/android-x64" "0.17.14" + "@esbuild/darwin-arm64" "0.17.14" + "@esbuild/darwin-x64" "0.17.14" + "@esbuild/freebsd-arm64" "0.17.14" + "@esbuild/freebsd-x64" "0.17.14" + "@esbuild/linux-arm" "0.17.14" + "@esbuild/linux-arm64" "0.17.14" + "@esbuild/linux-ia32" "0.17.14" + "@esbuild/linux-loong64" "0.17.14" + "@esbuild/linux-mips64el" "0.17.14" + "@esbuild/linux-ppc64" "0.17.14" + "@esbuild/linux-riscv64" "0.17.14" + "@esbuild/linux-s390x" "0.17.14" + "@esbuild/linux-x64" "0.17.14" + "@esbuild/netbsd-x64" "0.17.14" + "@esbuild/openbsd-x64" "0.17.14" + "@esbuild/sunos-x64" "0.17.14" + "@esbuild/win32-arm64" "0.17.14" + "@esbuild/win32-ia32" "0.17.14" + "@esbuild/win32-x64" "0.17.14" escape-string-regexp@^1.0.5: version "1.0.5" @@ -1948,23 +1785,6 @@ eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.0.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz#c7f0f956124ce677047ddbc192a68f999454dedc" - integrity sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ== - eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz" @@ -2093,6 +1913,11 @@ follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +fork-stream@^0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/fork-stream/-/fork-stream-0.0.4.tgz#db849fce77f6708a5f8f386ae533a0907b54ae70" + integrity sha512-Pqq5NnT78ehvUnAk/We/Jr22vSvanRlFTpAmQ88xBY/M1TlHe+P0ILuEyXS595ysdGfaj22634LBkGMA2GTcpA== + form-data@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz" @@ -2125,7 +1950,7 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.1, fs-extra@^9.1.0: +fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -2165,13 +1990,14 @@ gauge@~2.7.3: wide-align "^1.1.0" get-intrinsic@^1.0.2: - version "1.1.1" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== dependencies: function-bind "^1.1.1" has "^1.0.3" - has-symbols "^1.0.1" + has-proto "^1.0.1" + has-symbols "^1.0.3" get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" @@ -2200,14 +2026,14 @@ glob-parent@^5.1.1, glob-parent@^5.1.2, glob-parent@~5.1.0: is-glob "^4.0.1" glob@^7.0.6: - version "7.1.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "^3.1.1" once "^1.3.0" path-is-absolute "^1.0.0" @@ -2253,7 +2079,7 @@ globalthis@^1.0.1: dependencies: define-properties "^1.1.3" -globby@^11.0.4, globby@^11.1.0: +globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -2265,23 +2091,6 @@ globby@^11.0.4, globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" -got@11.8.5: - version "11.8.5" - resolved "https://registry.npmjs.org/got/-/got-11.8.5.tgz" - integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - got@^9.6.0: version "9.6.0" resolved "https://registry.npmjs.org/got/-/got-9.6.0.tgz" @@ -2347,9 +2156,14 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-symbols@^1.0.1: +has-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" + integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== + +has-symbols@^1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has-unicode@^2.0.0: @@ -2366,20 +2180,20 @@ has@^1.0.3: hosted-git-info@^4.0.2: version "4.1.0" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== dependencies: lru-cache "^6.0.0" htmlparser2@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz" - integrity sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA== + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== dependencies: domelementtype "^2.3.0" - domhandler "^5.0.2" + domhandler "^5.0.3" domutils "^3.0.1" - entities "^4.3.0" + entities "^4.4.0" http-cache-semantics@^4.0.0: version "4.1.1" @@ -2395,14 +2209,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.0-beta.5.2" - resolved "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz" - integrity sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - https-proxy-agent@^5.0.0: version "5.0.1" resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" @@ -2567,11 +2373,6 @@ json-buffer@3.0.0: resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= -json-buffer@3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" - integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== - json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" @@ -2587,6 +2388,11 @@ jsonc-parser@^2.3.0: resolved "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.3.1.tgz" integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz" @@ -2649,7 +2455,7 @@ jws@^4.0.0: keytar@^7.7.0: version "7.9.0" - resolved "https://registry.npmjs.org/keytar/-/keytar-7.9.0.tgz" + resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.9.0.tgz#4c6225708f51b50cbf77c5aae81721964c2918cb" integrity sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ== dependencies: node-addon-api "^4.3.0" @@ -2662,21 +2468,14 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" -keyv@^4.0.0: - version "4.0.3" - resolved "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz" - integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== - dependencies: - json-buffer "3.0.1" - leven@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== linkify-it@^3.0.1: version "3.0.3" - resolved "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== dependencies: uc.micro "^1.0.1" @@ -2757,7 +2556,7 @@ magic-string@^0.25.2: markdown-it@^12.3.2: version "12.3.2" - resolved "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz" + resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== dependencies: argparse "^2.0.1" @@ -2775,8 +2574,13 @@ matcher@^3.0.0: mdurl@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" @@ -2805,7 +2609,7 @@ mime-types@^2.1.12: mime@^1.3.4, mime@^1.4.1: version "1.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== mimic-response@^1.0.0, mimic-response@^1.0.1: @@ -2823,13 +2627,20 @@ mimic-response@^3.1.0: resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== -minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" +minimatch@^3.0.3, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + minimist@^1.2.0, minimist@^1.2.3: version "1.2.6" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz" @@ -2857,7 +2668,7 @@ ms@2.1.2, ms@^2.1.1: mute-stream@~0.0.4: version "0.0.8" - resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nan@^2.14.0: @@ -2883,9 +2694,9 @@ node-abi@^2.21.0: semver "^5.4.1" node-abi@^3.3.0: - version "3.22.0" - resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz" - integrity sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w== + version "3.40.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.40.0.tgz#51d8ed44534f70ff1357dfbc3a89717b1ceac1b4" + integrity sha512-zNy02qivjjRosswoYmPi8hIKJRr8MpQyeKT6qlcq/OnOgA3Rhoae+IYOqsM9V5+JnHWmxKnWOT2GxvtqdtOCXA== dependencies: semver "^7.3.5" @@ -2896,7 +2707,7 @@ node-abort-controller@^3.0.0: node-addon-api@^4.3.0: version "4.3.0" - resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== node-fetch@2: @@ -2923,11 +2734,6 @@ normalize-url@^4.1.0: resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz" integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-conf@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz" @@ -2955,7 +2761,7 @@ npmlog@^4.0.1: nth-check@^2.0.1: version "2.1.1" - resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== dependencies: boolbase "^1.0.0" @@ -2971,9 +2777,9 @@ object-assign@^4.1.0, object-assign@^4.1.1: integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= object-inspect@^1.9.0: - version "1.12.1" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.1.tgz" - integrity sha512-Y/jF6vnvEtOPGiKD1+q+X0CiUYRQtEHp89MLLUJ7TUivtH8Ugn2+3A7Rynqk7BRsAoqeOQWnFnjpDrKSxDgIGA== + version "1.12.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" + integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== object-keys@^1.0.12: version "1.1.1" @@ -3014,11 +2820,6 @@ p-cancelable@^1.0.0: resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== -p-cancelable@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz" - integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -3038,25 +2839,25 @@ parse-node-version@^1.0.0: parse-semver@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz" - integrity sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg= + resolved "https://registry.yarnpkg.com/parse-semver/-/parse-semver-1.1.1.tgz#9a4afd6df063dc4826f93fba4a99cf223f666cb8" + integrity sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ== dependencies: semver "^5.1.0" parse5-htmlparser2-tree-adapter@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" integrity sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g== dependencies: domhandler "^5.0.2" parse5 "^7.0.0" parse5@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz" - integrity sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g== + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== dependencies: - entities "^4.3.0" + entities "^4.4.0" path-is-absolute@^1.0.0: version "1.0.1" @@ -3155,9 +2956,9 @@ prebuild-install@^6.0.1: tunnel-agent "^0.6.0" prebuild-install@^7.0.1: - version "7.1.0" - resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.0.tgz" - integrity sha512-CNcMgI1xBypOyGqjp3wOc8AAo1nMhZS3Cwd3iHIxOdAUbb+YxdNuM4Z5iIrZ8RLvOsf3F3bl7b7xGq6DjQoNYA== + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== dependencies: detect-libc "^2.0.0" expand-template "^2.0.3" @@ -3166,7 +2967,6 @@ prebuild-install@^7.0.1: mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" node-abi "^3.3.0" - npmlog "^4.0.1" pump "^3.0.0" rc "^1.2.7" simple-get "^4.0.0" @@ -3212,9 +3012,9 @@ pump@^3.0.0: once "^1.3.1" qs@^6.9.1: - version "6.11.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + version "6.11.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" + integrity sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA== dependencies: side-channel "^1.0.4" @@ -3223,11 +3023,6 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - rc@^1.2.7: version "1.2.8" resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" @@ -3240,8 +3035,8 @@ rc@^1.2.7: read@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/read/-/read-1.0.7.tgz" - integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ= + resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" + integrity sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ== dependencies: mute-stream "~0.0.4" @@ -3306,11 +3101,6 @@ replace-ext@^1.0.0: resolved "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz" integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== -resolve-alpn@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz" - integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA== - resolve@^1.11.0, resolve@^1.11.1: version "1.20.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz" @@ -3326,13 +3116,6 @@ responselike@^1.0.2: dependencies: lowercase-keys "^1.0.0" -responselike@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz" - integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw== - dependencies: - lowercase-keys "^2.0.0" - reusify@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" @@ -3449,7 +3232,14 @@ semver@^7.3.2: dependencies: lru-cache "^6.0.0" -semver@^7.3.5, semver@^7.3.7: +semver@^7.3.5: + version "7.5.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec" + integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw== + dependencies: + lru-cache "^6.0.0" + +semver@^7.3.7: version "7.3.7" resolved "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== @@ -3501,7 +3291,7 @@ shebang-regex@^3.0.0: side-channel@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: call-bind "^1.0.0" @@ -3529,7 +3319,7 @@ simple-get@^3.0.3: simple-get@^4.0.0: version "4.0.1" - resolved "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== dependencies: decompress-response "^6.0.0" @@ -3541,16 +3331,11 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1: +source-map@0.6.1, source-map@^0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@^0.7.3: - version "0.7.3" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz" @@ -3571,6 +3356,11 @@ stream-exhaust@^1.0.1: resolved "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz" integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + string-width@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz" @@ -3698,6 +3488,16 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +ternary-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ternary-stream/-/ternary-stream-3.0.0.tgz#7951930ea9e823924d956f03d516151a2d516253" + integrity sha512-oIzdi+UL/JdktkT+7KU5tSIQjj8pbShj3OASuvDEhm0NT5lppsm7aXWAmAq4/QMaBIyfuEcNLbAQA+HpaISobQ== + dependencies: + duplexify "^4.1.1" + fork-stream "^0.0.4" + merge-stream "^2.0.0" + through2 "^3.0.1" + through2@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.2.tgz#99f88931cfc761ec7678b41d5d7336b5b6a07bf4" @@ -3771,7 +3571,7 @@ tslib@^2.0.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz" integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== -tslib@^2.2.0, tslib@^2.4.0: +tslib@^2.2.0: version "2.4.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== @@ -3802,7 +3602,7 @@ type-fest@^0.13.1: typed-rest-client@^1.8.4: version "1.8.9" - resolved "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.9.tgz" + resolved "https://registry.yarnpkg.com/typed-rest-client/-/typed-rest-client-1.8.9.tgz#e560226bcadfe71b0fb5c416b587f8da3b8f92d8" integrity sha512-uSmjE38B80wjL85UFX3sTYEUlvZ1JgCRhsWj/fJ4rZ0FqDUFoIuodtiVeE+cUqiVTOKPdKrp/sdftD15MDek6g== dependencies: qs "^6.9.1" @@ -3811,7 +3611,7 @@ typed-rest-client@^1.8.4: uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" - resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== underscore@1.8.3: @@ -3820,9 +3620,9 @@ underscore@1.8.3: integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= underscore@^1.12.1: - version "1.13.3" - resolved "https://registry.npmjs.org/underscore/-/underscore-1.13.3.tgz" - integrity sha512-QvjkYpiD+dJJraRA8+dGAU4i7aBbb2s0S3jA45TFOvg2VgqvdCDd/3N6CqA8gluk1W91GLoXg5enMUx560QzuA== + version "1.13.6" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" + integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== universal-user-agent@^4.0.0: version "4.0.1" @@ -3848,7 +3648,7 @@ universalify@^2.0.0: url-join@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7" integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== url-parse-lax@^3.0.0: @@ -3965,14 +3765,6 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xml2js@^0.4.23: - version "0.4.23" - resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz" - integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - xml2js@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" @@ -4003,15 +3795,15 @@ yallist@^4.0.0: yauzl@^2.10.0, yauzl@^2.3.1: version "2.10.0" - resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.1.0" yazl@^2.2.2: version "2.5.1" - resolved "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== dependencies: buffer-crc32 "~0.2.3" diff --git a/cglicenses.json b/cglicenses.json index 5612be6269..7eb5f6f5fe 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -32,20 +32,6 @@ "Copyright (c) tunnel-agent authors" ] }, - { - // Reason: Waiting for https://github.com/segmentio/noop-logger/issues/2 - "name": "noop-logger", - "fullLicenseText": [ - "This project is licensed under the MIT license.", - "Copyrights are respective of each contributor listed at the beginning of each definition file.", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." - ] - }, { // Reason: The license at https://github.com/rbuckton/reflect-metadata/blob/master/LICENSE // does not include a clear Copyright statement (it's in https://github.com/rbuckton/reflect-metadata/blob/master/CopyrightNotice.txt). @@ -102,36 +88,6 @@ "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: Repository lacks license text. - // https://github.com/othiym23/emitter-listener/blob/master/package.json declares BSD-2-Clause. - // https://github.com/othiym23/emitter-listener/issues/3 - "name": "emitter-listener", - "fullLicenseText": [ - "BSD 2-Clause \"Simplified\" License", - "Copyright (c) 2018, Forrest L Norvell ", - "", - "Redistribution and use in source and binary forms, with or without", - "modification, are permitted provided that the following conditions are met:", - "", - "1. Redistributions of source code must retain the above copyright notice, this", - " list of conditions and the following disclaimer.", - "2. Redistributions in binary form must reproduce the above copyright notice,", - " this list of conditions and the following disclaimer in the documentation", - " and/or other materials provided with the distribution.", - "", - "THIS SOFTWARE IS PROVIDED BY THE 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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR", - "ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES", - "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;", - "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND", - "ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT", - "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS", - "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." - ] - }, { // Reason: Repository lacks license text. // https://github.com/tjwebb/fnv-plus/blob/master/package.json declares MIT. @@ -175,12 +131,114 @@ ] }, { - // Reason: Waiting for https://github.com/microsoft/vscode-markdown-languageservice/pull/9 - "name": "vscode-markdown-languageservice", + // Reason: Missing license file + "name": "@tokenizer/token", "fullLicenseText": [ - "MIT License", + "(The MIT License)", "", - "Copyright (c) Microsoft Corporation.", + "Copyright (c) 2020 Borewit", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + // Reason: Missing license file + "name": "readable-web-to-node-stream", + "fullLicenseText": [ + "(The MIT License)", + "", + "Copyright (c) 2019 Borewit", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + // Reason: The substack org has been deleted on GH + "name": "concat-map", + "fullLicenseText": [ + "This software is released under the MIT license:", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of", + "this software and associated documentation files (the \"Software\"), to deal in", + "the Software without restriction, including without limitation the rights to", + "use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of", + "the Software, and to permit persons to whom the Software is furnished to do so,", + "subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS", + "FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR", + "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER", + "IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", + "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + // Reason: The substack org has been deleted on GH + "name": "github-from-package", + "fullLicenseText": [ + "This software is released under the MIT license:", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of", + "this software and associated documentation files (the \"Software\"), to deal in", + "the Software without restriction, including without limitation the rights to", + "use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of", + "the Software, and to permit persons to whom the Software is furnished to do so,", + "subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS", + "FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR", + "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER", + "IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", + "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + // Reason: The substack org has been deleted on GH + "name": "minimist", + "fullLicenseText": [ + "This software is released under the MIT license:", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of", + "this software and associated documentation files (the \"Software\"), to deal in", + "the Software without restriction, including without limitation the rights to", + "use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of", + "the Software, and to permit persons to whom the Software is furnished to do so,", + "subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS", + "FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR", + "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER", + "IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", + "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + // Reason: The substack org has been deleted on GH + "name": "mkdirp", + "fullLicenseText": [ + "Copyright 2010 James Halliday (mail@substack.net)", + "", + "This project is free software released under the MIT/X11 license:", "", "Permission is hereby granted, free of charge, to any person obtaining a copy", "of this software and associated documentation files (the \"Software\"), to deal", @@ -189,16 +247,150 @@ "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 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" + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." + ] + }, + { + // Reason: repo URI is wrong on crate, pending https://github.com/warp-tech/russh/pull/53 + "name": "russh-cryptovec", + "fullLicenseTextUri": "https://raw.githubusercontent.com/warp-tech/russh/1da80d0d599b6ee2d257c544c0d6af4f649c9029/LICENSE-2.0.txt" + }, + { + // Reason: repo URI is wrong on crate, pending https://github.com/warp-tech/russh/pull/53 + "name": "russh-keys", + "fullLicenseTextUri": "https://raw.githubusercontent.com/warp-tech/russh/1da80d0d599b6ee2d257c544c0d6af4f649c9029/LICENSE-2.0.txt" + }, + { + // Reason: license is in a subdirectory in repo + "name": "dirs-next", + "fullLicenseTextUri": "https://raw.githubusercontent.com/xdg-rs/dirs/af4aa39daba0ac68e222962a5aca17360158b7cc/dirs/LICENSE-MIT" + }, + { + // Reason: license is in a subdirectory in repo + "name": "openssl", + "fullLicenseTextUri": "https://raw.githubusercontent.com/sfackler/rust-openssl/e43eb58540b27a17f8029c397e3edc12bbc9011f/openssl/LICENSE" + }, + { + // Reason: license is in a subdirectory in repo + "name": "openssl-sys", + "fullLicenseTextUri": "https://raw.githubusercontent.com/sfackler/rust-openssl/e43eb58540b27a17f8029c397e3edc12bbc9011f/openssl-sys/LICENSE-MIT" + }, + { + // Reason: Missing license file + "name": "openssl-macros", + "fullLicenseText": [ + "This software is released under the MIT license:", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of", + "this software and associated documentation files (the \"Software\"), to deal in", + "the Software without restriction, including without limitation the rights to", + "use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of", + "the Software, and to permit persons to whom the Software is furnished to do so,", + "subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS", + "FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR", + "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER", + "IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN", + "CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { // Reason: Missing license file + "name": "emitter-listener", + "fullLicenseText": [ + "This software is released under the BSD-2-Clause license:", + "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:", + "", + "1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.", + "", + "2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer", + "in the documentation and/or other materials provided with the distribution.", + "", + "THIS SOFTWARE IS PROVIDED BY THE 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 THE", + "COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,", + "BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED", + "AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT", + "OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + ] + }, + { // Reason: Missing license file + "name": "const_format_proc_macros", + "fullLicenseTextUri": "https://raw.githubusercontent.com/rodrimati1992/const_format_crates/b2207af46bfbd9f1a6bd12dbffd10feeea3d9fd7/LICENSE-ZLIB.md" + }, + { // Reason: Missing license file + "name": "const_format", + "fullLicenseTextUri": "https://raw.githubusercontent.com/rodrimati1992/const_format_crates/b2207af46bfbd9f1a6bd12dbffd10feeea3d9fd7/LICENSE-ZLIB.md" + }, + { // License is MIT/Apache and tool doesn't look in subfolders + "name": "toml", + "fullLicenseTextUri": "https://raw.githubusercontent.com/toml-rs/toml/main/crates/toml/LICENSE-MIT" + }, + { // License is MIT/Apache and tool doesn't look in subfolders + "name": "dirs-sys-next", + "fullLicenseTextUri": "https://raw.githubusercontent.com/xdg-rs/dirs/master/dirs-sys/LICENSE-MIT" + }, + { + "name": "https-proxy-agent", + "fullLicenseText": [ + "(The MIT License)", + "Copyright (c) 2013 Nathan Rajlich ", + "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." + ] + }, + { + "name": "data-uri-to-buffer", + "fullLicenseText": [ + "(The MIT License)", + "Copyright (c) 2014 Nathan Rajlich ", + "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." + ] + }, + { + "name": "socks-proxy-agent", + "fullLicenseText": [ + "(The MIT License)", + "Copyright (c) 2013 Nathan Rajlich ", + "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." + ] + }, + { + "name": "http-proxy-agent", + "fullLicenseText": [ + "(The MIT License)", + "Copyright (c) 2013 Nathan Rajlich ", + "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." + ] + }, + { + "name": "agent-base", + "fullLicenseText": [ + "(The MIT License)", + "Copyright (c) 2013 Nathan Rajlich ", + "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." ] } ] diff --git a/cgmanifest.json b/cgmanifest.json index 290816d4d2..0bfa77e76a 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "c53c15c92c076f8d7593518ba99a9f8a6fc5ead6" + "commitHash": "513ac8b2c47c7e1ac6b9f4fb5ea98e965cf29b66" } }, "licenseDetail": [ @@ -40,7 +40,475 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "102.0.5005.148" + "version": "108.0.5359.215" + }, + { + "component": { + "type": "git", + "git": { + "name": "ffmpeg", + "repositoryUrl": "https://chromium.googlesource.com/chromium/third_party/ffmpeg", + "commitHash": "b9f01c3c54576330b2cf8918c54d5ee5be8faefe" + } + }, + "isOnlyProductionDependency": true, + "license": "LGPL-2.1+", + "version": "5.1.git", + "licenseDetail": [ + " GNU LESSER GENERAL PUBLIC LICENSE", + " Version 2.1, February 1999", + " Copyright (C) 1991, 1999 Free Software Foundation, Inc.", + " 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA", + " Everyone is permitted to copy and distribute verbatim copies", + " of this license document, but changing it is not allowed.", + "[This is the first released version of the Lesser GPL. It also counts", + " as the successor of the GNU Library Public License, version 2, hence", + " the version number 2.1.]", + " Preamble", + " The licenses for most software are designed to take away your", + "freedom to share and change it. By contrast, the GNU General Public", + "Licenses are intended to guarantee your freedom to share and change", + "free software--to make sure the software is free for all its users.", + " This license, the Lesser General Public License, applies to some", + "specially designated software packages--typically libraries--of the", + "Free Software Foundation and other authors who decide to use it. You", + "can use it too, but we suggest you first think carefully about whether", + "this license or the ordinary General Public License is the better", + "strategy to use in any particular case, based on the explanations below.", + " When we speak of free software, we are referring to freedom of use,", + "not price. Our General Public Licenses are designed to make sure that", + "you have the freedom to distribute copies of free software (and charge", + "for this service if you wish); that you receive source code or can get", + "it if you want it; that you can change the software and use pieces of", + "it in new free programs; and that you are informed that you can do", + "these things.", + " To protect your rights, we need to make restrictions that forbid", + "distributors to deny you these rights or to ask you to surrender these", + "rights. These restrictions translate to certain responsibilities for", + "you if you distribute copies of the library or if you modify it.", + " For example, if you distribute copies of the library, whether gratis", + "or for a fee, you must give the recipients all the rights that we gave", + "you. You must make sure that they, too, receive or can get the source", + "code. If you link other code with the library, you must provide", + "complete object files to the recipients, so that they can relink them", + "with the library after making changes to the library and recompiling", + "it. And you must show them these terms so they know their rights.", + " We protect your rights with a two-step method: (1) we copyright the", + "library, and (2) we offer you this license, which gives you legal", + "permission to copy, distribute and/or modify the library.", + " To protect each distributor, we want to make it very clear that", + "there is no warranty for the free library. Also, if the library is", + "modified by someone else and passed on, the recipients should know", + "that what they have is not the original version, so that the original", + "author's reputation will not be affected by problems that might be", + "introduced by others.", + "", + " Finally, software patents pose a constant threat to the existence of", + "any free program. We wish to make sure that a company cannot", + "effectively restrict the users of a free program by obtaining a", + "restrictive license from a patent holder. Therefore, we insist that", + "any patent license obtained for a version of the library must be", + "consistent with the full freedom of use specified in this license.", + " Most GNU software, including some libraries, is covered by the", + "ordinary GNU General Public License. This license, the GNU Lesser", + "General Public License, applies to certain designated libraries, and", + "is quite different from the ordinary General Public License. We use", + "this license for certain libraries in order to permit linking those", + "libraries into non-free programs.", + " When a program is linked with a library, whether statically or using", + "a shared library, the combination of the two is legally speaking a", + "combined work, a derivative of the original library. The ordinary", + "General Public License therefore permits such linking only if the", + "entire combination fits its criteria of freedom. The Lesser General", + "Public License permits more lax criteria for linking other code with", + "the library.", + " We call this license the \"Lesser\" General Public License because it", + "does Less to protect the user's freedom than the ordinary General", + "Public License. It also provides other free software developers Less", + "of an advantage over competing non-free programs. These disadvantages", + "are the reason we use the ordinary General Public License for many", + "libraries. However, the Lesser license provides advantages in certain", + "special circumstances.", + " For example, on rare occasions, there may be a special need to", + "encourage the widest possible use of a certain library, so that it becomes", + "a de-facto standard. To achieve this, non-free programs must be", + "allowed to use the library. A more frequent case is that a free", + "library does the same job as widely used non-free libraries. In this", + "case, there is little to gain by limiting the free library to free", + "software only, so we use the Lesser General Public License.", + " In other cases, permission to use a particular library in non-free", + "programs enables a greater number of people to use a large body of", + "free software. For example, permission to use the GNU C Library in", + "non-free programs enables many more people to use the whole GNU", + "operating system, as well as its variant, the GNU/Linux operating", + "system.", + " Although the Lesser General Public License is Less protective of the", + "users' freedom, it does ensure that the user of a program that is", + "linked with the Library has the freedom and the wherewithal to run", + "that program using a modified version of the Library.", + " The precise terms and conditions for copying, distribution and", + "modification follow. Pay close attention to the difference between a", + "\"work based on the library\" and a \"work that uses the library\". The", + "former contains code derived from the library, whereas the latter must", + "be combined with the library in order to run.", + "", + " GNU LESSER GENERAL PUBLIC LICENSE", + " TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION", + " 0. This License Agreement applies to any software library or other", + "program which contains a notice placed by the copyright holder or", + "other authorized party saying it may be distributed under the terms of", + "this Lesser General Public License (also called \"this License\").", + "Each licensee is addressed as \"you\".", + " A \"library\" means a collection of software functions and/or data", + "prepared so as to be conveniently linked with application programs", + "(which use some of those functions and data) to form executables.", + " The \"Library\", below, refers to any such software library or work", + "which has been distributed under these terms. A \"work based on the", + "Library\" means either the Library or any derivative work under", + "copyright law: that is to say, a work containing the Library or a", + "portion of it, either verbatim or with modifications and/or translated", + "straightforwardly into another language. (Hereinafter, translation is", + "included without limitation in the term \"modification\".)", + " \"Source code\" for a work means the preferred form of the work for", + "making modifications to it. For a library, complete source code means", + "all the source code for all modules it contains, plus any associated", + "interface definition files, plus the scripts used to control compilation", + "and installation of the library.", + " Activities other than copying, distribution and modification are not", + "covered by this License; they are outside its scope. The act of", + "running a program using the Library is not restricted, and output from", + "such a program is covered only if its contents constitute a work based", + "on the Library (independent of the use of the Library in a tool for", + "writing it). Whether that is true depends on what the Library does", + "and what the program that uses the Library does.", + " 1. You may copy and distribute verbatim copies of the Library's", + "complete source code as you receive it, in any medium, provided that", + "you conspicuously and appropriately publish on each copy an", + "appropriate copyright notice and disclaimer of warranty; keep intact", + "all the notices that refer to this License and to the absence of any", + "warranty; and distribute a copy of this License along with the", + "Library.", + " You may charge a fee for the physical act of transferring a copy,", + "and you may at your option offer warranty protection in exchange for a", + "fee.", + "", + " 2. You may modify your copy or copies of the Library or any portion", + "of it, thus forming a work based on the Library, and copy and", + "distribute such modifications or work under the terms of Section 1", + "above, provided that you also meet all of these conditions:", + " a) The modified work must itself be a software library.", + " b) You must cause the files modified to carry prominent notices", + " stating that you changed the files and the date of any change.", + " c) You must cause the whole of the work to be licensed at no", + " charge to all third parties under the terms of this License.", + " d) If a facility in the modified Library refers to a function or a", + " table of data to be supplied by an application program that uses", + " the facility, other than as an argument passed when the facility", + " is invoked, then you must make a good faith effort to ensure that,", + " in the event an application does not supply such function or", + " table, the facility still operates, and performs whatever part of", + " its purpose remains meaningful.", + " (For example, a function in a library to compute square roots has", + " a purpose that is entirely well-defined independent of the", + " application. Therefore, Subsection 2d requires that any", + " application-supplied function or table used by this function must", + " be optional: if the application does not supply it, the square", + " root function must still compute square roots.)", + "These requirements apply to the modified work as a whole. If", + "identifiable sections of that work are not derived from the Library,", + "and can be reasonably considered independent and separate works in", + "themselves, then this License, and its terms, do not apply to those", + "sections when you distribute them as separate works. But when you", + "distribute the same sections as part of a whole which is a work based", + "on the Library, the distribution of the whole must be on the terms of", + "this License, whose permissions for other licensees extend to the", + "entire whole, and thus to each and every part regardless of who wrote", + "it.", + "Thus, it is not the intent of this section to claim rights or contest", + "your rights to work written entirely by you; rather, the intent is to", + "exercise the right to control the distribution of derivative or", + "collective works based on the Library.", + "In addition, mere aggregation of another work not based on the Library", + "with the Library (or with a work based on the Library) on a volume of", + "a storage or distribution medium does not bring the other work under", + "the scope of this License.", + " 3. You may opt to apply the terms of the ordinary GNU General Public", + "License instead of this License to a given copy of the Library. To do", + "this, you must alter all the notices that refer to this License, so", + "that they refer to the ordinary GNU General Public License, version 2,", + "instead of to this License. (If a newer version than version 2 of the", + "ordinary GNU General Public License has appeared, then you can specify", + "that version instead if you wish.) Do not make any other change in", + "these notices.", + "", + " Once this change is made in a given copy, it is irreversible for", + "that copy, so the ordinary GNU General Public License applies to all", + "subsequent copies and derivative works made from that copy.", + " This option is useful when you wish to copy part of the code of", + "the Library into a program that is not a library.", + " 4. You may copy and distribute the Library (or a portion or", + "derivative of it, under Section 2) in object code or executable form", + "under the terms of Sections 1 and 2 above provided that you accompany", + "it with the complete corresponding machine-readable source code, which", + "must be distributed under the terms of Sections 1 and 2 above on a", + "medium customarily used for software interchange.", + " If distribution of object code is made by offering access to copy", + "from a designated place, then offering equivalent access to copy the", + "source code from the same place satisfies the requirement to", + "distribute the source code, even though third parties are not", + "compelled to copy the source along with the object code.", + " 5. A program that contains no derivative of any portion of the", + "Library, but is designed to work with the Library by being compiled or", + "linked with it, is called a \"work that uses the Library\". Such a", + "work, in isolation, is not a derivative work of the Library, and", + "therefore falls outside the scope of this License.", + " However, linking a \"work that uses the Library\" with the Library", + "creates an executable that is a derivative of the Library (because it", + "contains portions of the Library), rather than a \"work that uses the", + "library\". The executable is therefore covered by this License.", + "Section 6 states terms for distribution of such executables.", + " When a \"work that uses the Library\" uses material from a header file", + "that is part of the Library, the object code for the work may be a", + "derivative work of the Library even though the source code is not.", + "Whether this is true is especially significant if the work can be", + "linked without the Library, or if the work is itself a library. The", + "threshold for this to be true is not precisely defined by law.", + " If such an object file uses only numerical parameters, data", + "structure layouts and accessors, and small macros and small inline", + "functions (ten lines or less in length), then the use of the object", + "file is unrestricted, regardless of whether it is legally a derivative", + "work. (Executables containing this object code plus portions of the", + "Library will still fall under Section 6.)", + " Otherwise, if the work is a derivative of the Library, you may", + "distribute the object code for the work under the terms of Section 6.", + "Any executables containing that work also fall under Section 6,", + "whether or not they are linked directly with the Library itself.", + "", + " 6. As an exception to the Sections above, you may also combine or", + "link a \"work that uses the Library\" with the Library to produce a", + "work containing portions of the Library, and distribute that work", + "under terms of your choice, provided that the terms permit", + "modification of the work for the customer's own use and reverse", + "engineering for debugging such modifications.", + " You must give prominent notice with each copy of the work that the", + "Library is used in it and that the Library and its use are covered by", + "this License. You must supply a copy of this License. If the work", + "during execution displays copyright notices, you must include the", + "copyright notice for the Library among them, as well as a reference", + "directing the user to the copy of this License. Also, you must do one", + "of these things:", + " a) Accompany the work with the complete corresponding", + " machine-readable source code for the Library including whatever", + " changes were used in the work (which must be distributed under", + " Sections 1 and 2 above); and, if the work is an executable linked", + " with the Library, with the complete machine-readable \"work that", + " uses the Library\", as object code and/or source code, so that the", + " user can modify the Library and then relink to produce a modified", + " executable containing the modified Library. (It is understood", + " that the user who changes the contents of definitions files in the", + " Library will not necessarily be able to recompile the application", + " to use the modified definitions.)", + " b) Use a suitable shared library mechanism for linking with the", + " Library. A suitable mechanism is one that (1) uses at run time a", + " copy of the library already present on the user's computer system,", + " rather than copying library functions into the executable, and (2)", + " will operate properly with a modified version of the library, if", + " the user installs one, as long as the modified version is", + " interface-compatible with the version that the work was made with.", + " c) Accompany the work with a written offer, valid for at", + " least three years, to give the same user the materials", + " specified in Subsection 6a, above, for a charge no more", + " than the cost of performing this distribution.", + " d) If distribution of the work is made by offering access to copy", + " from a designated place, offer equivalent access to copy the above", + " specified materials from the same place.", + " e) Verify that the user has already received a copy of these", + " materials or that you have already sent this user a copy.", + " For an executable, the required form of the \"work that uses the", + "Library\" must include any data and utility programs needed for", + "reproducing the executable from it. However, as a special exception,", + "the materials to be distributed need not include anything that is", + "normally distributed (in either source or binary form) with the major", + "components (compiler, kernel, and so on) of the operating system on", + "which the executable runs, unless that component itself accompanies", + "the executable.", + " It may happen that this requirement contradicts the license", + "restrictions of other proprietary libraries that do not normally", + "accompany the operating system. Such a contradiction means you cannot", + "use both them and the Library together in an executable that you", + "distribute.", + "", + " 7. You may place library facilities that are a work based on the", + "Library side-by-side in a single library together with other library", + "facilities not covered by this License, and distribute such a combined", + "library, provided that the separate distribution of the work based on", + "the Library and of the other library facilities is otherwise", + "permitted, and provided that you do these two things:", + " a) Accompany the combined library with a copy of the same work", + " based on the Library, uncombined with any other library", + " facilities. This must be distributed under the terms of the", + " Sections above.", + " b) Give prominent notice with the combined library of the fact", + " that part of it is a work based on the Library, and explaining", + " where to find the accompanying uncombined form of the same work.", + " 8. You may not copy, modify, sublicense, link with, or distribute", + "the Library except as expressly provided under this License. Any", + "attempt otherwise to copy, modify, sublicense, link with, or", + "distribute the Library is void, and will automatically terminate your", + "rights under this License. However, parties who have received copies,", + "or rights, from you under this License will not have their licenses", + "terminated so long as such parties remain in full compliance.", + " 9. You are not required to accept this License, since you have not", + "signed it. However, nothing else grants you permission to modify or", + "distribute the Library or its derivative works. These actions are", + "prohibited by law if you do not accept this License. Therefore, by", + "modifying or distributing the Library (or any work based on the", + "Library), you indicate your acceptance of this License to do so, and", + "all its terms and conditions for copying, distributing or modifying", + "the Library or works based on it.", + " 10. Each time you redistribute the Library (or any work based on the", + "Library), the recipient automatically receives a license from the", + "original licensor to copy, distribute, link with or modify the Library", + "subject to these terms and conditions. You may not impose any further", + "restrictions on the recipients' exercise of the rights granted herein.", + "You are not responsible for enforcing compliance by third parties with", + "this License.", + "", + " 11. If, as a consequence of a court judgment or allegation of patent", + "infringement or for any other reason (not limited to patent issues),", + "conditions are imposed on you (whether by court order, agreement or", + "otherwise) that contradict the conditions of this License, they do not", + "excuse you from the conditions of this License. If you cannot", + "distribute so as to satisfy simultaneously your obligations under this", + "License and any other pertinent obligations, then as a consequence you", + "may not distribute the Library at all. For example, if a patent", + "license would not permit royalty-free redistribution of the Library by", + "all those who receive copies directly or indirectly through you, then", + "the only way you could satisfy both it and this License would be to", + "refrain entirely from distribution of the Library.", + "If any portion of this section is held invalid or unenforceable under any", + "particular circumstance, the balance of the section is intended to apply,", + "and the section as a whole is intended to apply in other circumstances.", + "It is not the purpose of this section to induce you to infringe any", + "patents or other property right claims or to contest validity of any", + "such claims; this section has the sole purpose of protecting the", + "integrity of the free software distribution system which is", + "implemented by public license practices. Many people have made", + "generous contributions to the wide range of software distributed", + "through that system in reliance on consistent application of that", + "system; it is up to the author/donor to decide if he or she is willing", + "to distribute software through any other system and a licensee cannot", + "impose that choice.", + "This section is intended to make thoroughly clear what is believed to", + "be a consequence of the rest of this License.", + " 12. If the distribution and/or use of the Library is restricted in", + "certain countries either by patents or by copyrighted interfaces, the", + "original copyright holder who places the Library under this License may add", + "an explicit geographical distribution limitation excluding those countries,", + "so that distribution is permitted only in or among countries not thus", + "excluded. In such case, this License incorporates the limitation as if", + "written in the body of this License.", + " 13. The Free Software Foundation may publish revised and/or new", + "versions of the Lesser General Public License from time to time.", + "Such new versions will be similar in spirit to the present version,", + "but may differ in detail to address new problems or concerns.", + "Each version is given a distinguishing version number. If the Library", + "specifies a version number of this License which applies to it and", + "\"any later version\", you have the option of following the terms and", + "conditions either of that version or of any later version published by", + "the Free Software Foundation. If the Library does not specify a", + "license version number, you may choose any version ever published by", + "the Free Software Foundation.", + "", + " 14. If you wish to incorporate parts of the Library into other free", + "programs whose distribution conditions are incompatible with these,", + "write to the author to ask for permission. For software which is", + "copyrighted by the Free Software Foundation, write to the Free", + "Software Foundation; we sometimes make exceptions for this. Our", + "decision will be guided by the two goals of preserving the free status", + "of all derivatives of our free software and of promoting the sharing", + "and reuse of software generally.", + " NO WARRANTY", + " 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO", + "WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.", + "EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR", + "OTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY", + "KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE", + "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR", + "PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE", + "LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME", + "THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.", + " 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN", + "WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY", + "AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU", + "FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR", + "CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE", + "LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING", + "RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A", + "FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF", + "SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH", + "DAMAGES.", + " END OF TERMS AND CONDITIONS", + "", + " How to Apply These Terms to Your New Libraries", + " If you develop a new library, and you want it to be of the greatest", + "possible use to the public, we recommend making it free software that", + "everyone can redistribute and change. You can do so by permitting", + "redistribution under these terms (or, alternatively, under the terms of the", + "ordinary General Public License).", + " To apply these terms, attach the following notices to the library. It is", + "safest to attach them to the start of each source file to most effectively", + "convey the exclusion of warranty; and each file should have at least the", + "\"copyright\" line and a pointer to where the full notice is found.", + " ", + " Copyright (C) ", + " This library is free software; you can redistribute it and/or", + " modify it under the terms of the GNU Lesser General Public", + " License as published by the Free Software Foundation; either", + " version 2.1 of the License, or (at your option) any later version.", + " This library is distributed in the hope that it will be useful,", + " but WITHOUT ANY WARRANTY; without even the implied warranty of", + " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU", + " Lesser General Public License for more details.", + " You should have received a copy of the GNU Lesser General Public", + " License along with this library; if not, write to the Free Software", + " Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA", + "Also add information on how to contact you by electronic and paper mail.", + "You should also get your employer (if you work as a programmer) or your", + "school, if any, to sign a \"copyright disclaimer\" for the library, if", + "necessary. Here is a sample; alter the names:", + " Yoyodyne, Inc., hereby disclaims all copyright interest in the", + " library `Frob' (a library for tweaking knobs) written by James Random Hacker.", + " , 1 April 1990", + " Ty Coon, President of Vice", + "That's all there is to it!" + ] + }, + { + "component": { + "type": "other", + "other": { + "name": "H.264/AVC Video Standard", + "downloadUrl": "https://chromium.googlesource.com/chromium/third_party/ffmpeg", + "version": "5.1.git" + } + }, + "licenseDetail": [ + "This product is licensed under the AVC patent portfolio license for the personal", + "and non-commercial use of a consumer to (i) encode video in compliance with the AVC standard (\"AVC VIDEO\")", + "and/or (ii) decode AVC video that was encoded by a consumer", + "engaged in a personal and non-commercial activity and/or was obtained from a video provider", + "licensed to provide AVC video. No license is granted or shall be implied for any other use.", + "Additional information may be obtained from MPEG LA LLC. See http://www.MPEGLA.COM.", + "", + "For clarification purposes, this notice does not limit or inhibit the use of the product", + "for normal business uses that are personal to that business which do not include", + "(i) redistribution of the product to third parties, or", + "(ii) creation of content with AVC Standard compliant technologies for distribution to third parties." + ], + "version": "H.264 (08/21)", + "isOnlyProductionDependency": true, + "license": "OTHER" }, { "component": { @@ -48,11 +516,11 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "442e84a358d75152556b5d087e4dd6a51615330d" + "commitHash": "6b06e89c7dfccec6792008302af1cee57649445c" } }, "isOnlyProductionDependency": true, - "version": "16.14.2" + "version": "16.17.1" }, { "component": { @@ -60,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "f887fa45dfaeeddfe20c9835ae7ca3a0823b661b" + "commitHash": "c67ca40ed6054aadcdfb901aa1abaee2ccc690f3" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "19.1.8" + "version": "19.0.8" }, { "component": { @@ -550,7 +1018,7 @@ "commitHash": "5b871f95fd9cb8efa8ee9a80600510d5e5339137" } }, - "licenseDetail":[ + "licenseDetail": [ "MIT License", "", "Copyright (c) Microsoft Corporation.", @@ -573,6 +1041,64 @@ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", "SOFTWARE" ] + }, + { + "component": { + "type": "npm", + "npm": { + "name": "@iktakahiro/markdown-it-katex", + "version": "4.0.2" + } + }, + "repositoryUrl": "https://github.com/mjbvz/markdown-it-katex", + "licenseDetail": [ + "The MIT License (MIT)", + "", + "Copyright (c) 2016 Waylon Flinn", + "", + "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.", + "", + "---", + "", + "The MIT License (MIT)", + "", + "Copyright (c) 2018 Takahiro Ethan Ikeuchi @iktakahiro", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE." + ], + "license": "MIT" } ], "version": 1 diff --git a/cli/CONTRIBUTING.md b/cli/CONTRIBUTING.md new file mode 100644 index 0000000000..d119f1ac98 --- /dev/null +++ b/cli/CONTRIBUTING.md @@ -0,0 +1,20 @@ +# Setup + +0. Clone, and then run `git submodule update --init --recursive` +1. Get the extensions: [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) and [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb) +2. Ensure your workspace is set to the `launcher` folder being the root. + +## Building the CLI on Windows + +For the moment, we require OpenSSL on Windows, where it is not usually installed by default. To install it: + +1. Install (clone) vcpkg [using their instructions](https://github.com/Microsoft/vcpkg#quick-start-windows) +1. Add the location of the `vcpkg` directory to your system or user PATH. +1. Run`vcpkg install openssl:x64-windows-static-md` (after restarting your terminal for PATH changes to apply) +1. You should be able to then `cargo build` successfully + +OpenSSL is needed for the key exchange we do when forwarding Basis tunnels. When all interested Basis clients support ED25519, we would be able to solely use libsodium. At the time of writing however, there is [no active development](https://chromestatus.com/feature/4913922408710144) on this in Chromium. + +# Debug + +1. You can use the Debug tasks already configured to run the launcher. diff --git a/cli/Cargo.lock b/cli/Cargo.lock new file mode 100644 index 0000000000..bd207ab137 --- /dev/null +++ b/cli/Cargo.lock @@ -0,0 +1,2928 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "async-broadcast" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d26004fe83b2d1cd3a97609b21e39f9a31535822210fe83205d2ce48866ea61" +dependencies = [ + "event-listener", + "futures-core", + "parking_lot", +] + +[[package]] +name = "async-io" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e21f3a490c72b3b0cf44962180e60045de2925d8dff97918f7ee43c8f637c7" +dependencies = [ + "autocfg", + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-recursion" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cda8f4bcc10624c4e85bc66b3f452cca98cfa5ca002dc83a16aad2367641bea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a90ec2df9600c28a01c56c4784c9207a96d2451833aeceb8cc97e4c9548bb78" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "3.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "code-cli" +version = "0.1.0" +dependencies = [ + "async-trait", + "atty", + "base64", + "bytes", + "cfg-if", + "chrono", + "clap", + "clap_lex", + "console", + "const_format", + "core-foundation", + "dialoguer", + "dirs 4.0.0", + "flate2", + "futures", + "gethostname", + "hyper", + "indicatif", + "keyring", + "lazy_static", + "libc", + "log", + "open", + "opentelemetry", + "opentelemetry-application-insights", + "pin-project", + "rand 0.8.5", + "regex", + "reqwest", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "sha2", + "shell-escape", + "sysinfo", + "tar", + "tempfile", + "thiserror", + "tokio", + "tokio-util", + "tunnels", + "url", + "uuid", + "winapi", + "winreg", + "zbus 3.4.0", + "zip", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "console" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.42.0", +] + +[[package]] +name = "const_format" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cxx" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dialoguer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3c796f3b0b408d9fd581611b47fa850821fcb84aa640b83a3c1a5be2d691f2" +dependencies = [ + "console", + "shell-words", + "tempfile", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enumflags2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0" +dependencies = [ + "enumflags2_derive 0.6.4", + "serde", +] + +[[package]] +name = "enumflags2" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +dependencies = [ + "enumflags2_derive 0.7.4", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "filetime" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.36.1", +] + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-macro" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "h2" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "indicatif" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b" +dependencies = [ + "console", + "lazy_static", + "number_prefix", + "regex", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keyring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38fb8399ddcabfccb274577a8d90f0653e0b5b5977797c1c8834ad09839a10e5" +dependencies = [ + "byteorder", + "secret-service", + "security-framework", + "winapi", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.135" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c" + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.36.1", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nb-connect" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1bb540dc6ef51cfe1916ec038ce7a620daf3a111e2502d745197cd53d6bca15" +dependencies = [ + "libc", + "socket2", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "ntapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" +dependencies = [ + "winapi", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "open" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2423ffbf445b82e58c3b1543655968923dd06f85432f10be2bb4f1b7122f98c" +dependencies = [ + "pathdiff", + "windows-sys 0.36.1", +] + +[[package]] +name = "openssl" +version = "0.10.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d6c3d7288a106c0a363e4b0e8d308058d56902adefb16f4936f417ffef086e" +dependencies = [ + "opentelemetry_api", + "opentelemetry_sdk", +] + +[[package]] +name = "opentelemetry-application-insights" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e592c598da4cbf1b4c4c8c07df4d3fb3a0202326cccf90621dbf23286cb79ea6" +dependencies = [ + "bytes", + "chrono", + "flate2", + "http", + "once_cell", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-semantic-conventions", + "reqwest", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "opentelemetry-http" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc79add46364183ece1a4542592ca593e6421c60807232f5b8f7a31703825d" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry_api", + "reqwest", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b02e0230abb0ab6636d18e2ba8fa02903ea63772281340ccac18e0af3ec9eeb" +dependencies = [ + "opentelemetry", +] + +[[package]] +name = "opentelemetry_api" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c24f96e21e7acc813c7a8394ee94978929db2bcc46cf6b5014fc612bf7760c22" +dependencies = [ + "futures-channel", + "futures-util", + "indexmap", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ca41c4933371b61c2a2f214bf16931499af4ec90543604ec828f7a625c09113" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "once_cell", + "opentelemetry_api", + "percent-encoding", + "rand 0.8.5", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "ordered-stream" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034ce384018b245e8d8424bbe90577fbd91a533be74107e465e3474eb2285eef" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.36.1", +] + +[[package]] +name = "paste" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "polling" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899b00b9c8ab553c743b3e11e87c5c7d423b2a2de229ba95b24a756344748011" +dependencies = [ + "autocfg", + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.7", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "reqwest" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rmp" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44519172358fd6d58656c86ab8e7fbc9e1490c3e8f14d35ed78ca0dd07403c9f" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b13be192e0220b8afb7222aa5813cb62cc269ebb5cac346ca6487681d2913e" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + +[[package]] +name = "russh" +version = "0.34.0-beta.16" +source = "git+https://github.com/microsoft/vscode-russh?branch=main#d22cf71d9ea36751322eeb9aa1e8c438a3aa1aef" +dependencies = [ + "bitflags", + "byteorder", + "digest", + "flate2", + "futures", + "generic-array", + "hex-literal", + "hmac", + "log", + "num-bigint", + "once_cell", + "openssl", + "rand 0.8.5", + "russh-cryptovec", + "russh-keys", + "sha1", + "sha2", + "subtle", + "thiserror", + "tokio", +] + +[[package]] +name = "russh-cryptovec" +version = "0.7.0-beta.1" +source = "git+https://github.com/microsoft/vscode-russh?branch=main#d22cf71d9ea36751322eeb9aa1e8c438a3aa1aef" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "russh-keys" +version = "0.22.0-beta.7" +source = "git+https://github.com/microsoft/vscode-russh?branch=main#d22cf71d9ea36751322eeb9aa1e8c438a3aa1aef" +dependencies = [ + "bit-vec", + "byteorder", + "data-encoding", + "dirs 3.0.2", + "futures", + "inout", + "log", + "md5", + "num-bigint", + "num-integer", + "openssl", + "rand 0.7.3", + "rand_core 0.5.1", + "russh-cryptovec", + "serde", + "serde_derive", + "sha2", + "thiserror", + "tokio", + "tokio-stream", + "yasna", +] + +[[package]] +name = "rustix" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "secret-service" +version = "2.0.2" +source = "git+https://github.com/microsoft/vscode-secret-service-rs?rev=fbbaf222de10546609be26bb043e64356e855edb#fbbaf222de10546609be26bb043e64356e855edb" +dependencies = [ + "lazy_static", + "num", + "openssl", + "rand 0.8.5", + "serde", + "zbus 1.9.3", + "zbus_macros 1.9.3", + "zvariant 2.10.0", + "zvariant_derive 2.10.0", +] + +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41feea4228a6f1cd09ec7a3593a682276702cd67b5273544757dae23c096f074" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysinfo" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975fe381e0ecba475d4acff52466906d95b153a40324956552e027b2a9eaa89e" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "winapi", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.42.0", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" + +[[package]] +name = "thiserror" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.42.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f714dd15bead90401d77e04243611caec13726c2408afd5b31901dfcdcb3b181" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha-1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tunnels" +version = "0.1.0" +source = "git+https://github.com/microsoft/dev-tunnels?rev=730aa86f8ccd9e2dd4541693fbce763357da93f4#730aa86f8ccd9e2dd4541693fbce763357da93f4" +dependencies = [ + "async-trait", + "chrono", + "futures", + "hyper", + "log", + "reqwest", + "russh", + "russh-keys", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-tungstenite", + "tokio-util", + "tungstenite", + "url", + "uuid", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uds_windows" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d" +dependencies = [ + "tempfile", + "winapi", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.7", + "serde", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "yasna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" +dependencies = [ + "bit-vec", + "num-bigint", +] + +[[package]] +name = "zbus" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cbeb2291cd7267a94489b71376eda33496c1b9881adf6b36f26cc2779f3fc49" +dependencies = [ + "async-io", + "byteorder", + "derivative", + "enumflags2 0.6.4", + "fastrand", + "futures", + "nb-connect", + "nix 0.22.3", + "once_cell", + "polling", + "scoped-tls", + "serde", + "serde_repr", + "zbus_macros 1.9.3", + "zvariant 2.10.0", +] + +[[package]] +name = "zbus" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78a0b85c5608c27d2306d67e955b9c6e23a42d824205c85038a7afbe19c0ae22" +dependencies = [ + "async-broadcast", + "async-recursion", + "async-trait", + "byteorder", + "derivative", + "dirs 4.0.0", + "enumflags2 0.7.5", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "lazy_static", + "nix 0.25.0", + "once_cell", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "winapi", + "zbus_macros 3.4.0", + "zbus_names", + "zvariant 3.7.1", +] + +[[package]] +name = "zbus_macros" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa3959a7847cf95e3d51e312856617c5b1b77191176c65a79a5f14d778bbe0a6" +dependencies = [ + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zbus_macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18018648e7e10ed856809befe7309002b87b2b12d5b282cb5040d7974b58677" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "zbus_names" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a408fd8a352695690f53906dc7fd036be924ec51ea5e05666ff42685ed0af5" +dependencies = [ + "serde", + "static_assertions", + "zvariant 3.7.1", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" + +[[package]] +name = "zip" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" +dependencies = [ + "byteorder", + "crc32fast", + "flate2", + "thiserror", + "time", +] + +[[package]] +name = "zvariant" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68c7b55f2074489b7e8e07d2d0a6ee6b4f233867a653c664d8020ba53692525" +dependencies = [ + "byteorder", + "enumflags2 0.6.4", + "libc", + "serde", + "static_assertions", + "zvariant_derive 2.10.0", +] + +[[package]] +name = "zvariant" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794fb7f59af4105697b0449ba31731ee5dbb3e773a17dbdf3d36206ea1b1644" +dependencies = [ + "byteorder", + "enumflags2 0.7.5", + "libc", + "serde", + "static_assertions", + "zvariant_derive 3.7.1", +] + +[[package]] +name = "zvariant_derive" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ca5e22593eb4212382d60d26350065bf2a02c34b85bc850474a74b589a3de9" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zvariant_derive" +version = "3.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd58d4b6c8e26d3dd2149c8c40c6613ef6451b9885ff1296d1ac86c388351a54" +dependencies = [ + "proc-macro-crate 1.2.1", + "proc-macro2", + "quote", + "syn", +] diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000000..ac05391f65 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,87 @@ +[package] +name = "code-cli" +version = "0.1.0" +edition = "2021" +default-run = "code" + +[lib] +name = "cli" +path = "src/lib.rs" + +[[bin]] +name = "code" + +[dependencies] +futures = "0.3" +clap = { version = "3.0", features = ["derive", "env"] } +open = { version = "2.1.0" } +reqwest = { version = "0.11.9", default-features = false, features = ["json", "stream", "native-tls"] } +tokio = { version = "1.24.2", features = ["full"] } +tokio-util = { version = "0.7", features = ["compat", "codec"] } +flate2 = { version = "1.0.22" } +zip = { version = "0.5.13", default-features = false, features = ["time", "deflate"] } +regex = { version = "1.5.5" } +lazy_static = { version = "1.4.0" } +sysinfo = { version = "0.27.7", default-features = false } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +rmp-serde = "1.0" +uuid = { version = "0.8.2", features = ["serde", "v4"] } +dirs = "4.0.0" +rand = "0.8.5" +atty = "0.2.14" +opentelemetry = { version = "0.18.0", features = ["rt-tokio"] } +opentelemetry-application-insights = { version = "0.22.0", features = ["reqwest-client"] } +serde_bytes = "0.11.5" +chrono = { version = "0.4", features = ["serde"] } +gethostname = "0.2.3" +libc = "0.2" +tunnels = { git = "https://github.com/microsoft/dev-tunnels", rev = "730aa86f8ccd9e2dd4541693fbce763357da93f4", default-features = false, features = ["connections"] } +keyring = "1.1" +dialoguer = "0.10" +hyper = "0.14" +indicatif = "0.16" +tempfile = "3.4" +clap_lex = "0.2" +url = "2.3" +async-trait = "0.1" +log = "0.4" +const_format = "0.2" +sha2 = "0.10" +base64 = "0.13" +shell-escape = "0.1.5" +thiserror = "1.0" +cfg-if = "1.0.0" +pin-project = "1.0" +console = "0.15" +bytes = "1.4" +tar = { version = "0.4" } + +[build-dependencies] +serde = { version = "1.0" } +serde_json = { version = "1.0" } + +[target.'cfg(windows)'.dependencies] +winreg = "0.10" +winapi = "0.3.9" + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "0.9.3" + +[target.'cfg(target_os = "linux")'.dependencies] +zbus = { version = "3.4", default-features = false, features = ["tokio"] } + +[patch.crates-io] +russh = { git = "https://github.com/microsoft/vscode-russh", branch = "main" } +russh-cryptovec = { git = "https://github.com/microsoft/vscode-russh", branch = "main" } +russh-keys = { git = "https://github.com/microsoft/vscode-russh", branch = "main" } +secret-service = { git = "https://github.com/microsoft/vscode-secret-service-rs", rev = "fbbaf222de10546609be26bb043e64356e855edb" } + +[profile.release] +strip = true +lto = true +codegen-units = 1 + +[features] +default = [] +vscode-encrypt = [] diff --git a/cli/build.rs b/cli/build.rs new file mode 100644 index 0000000000..e668e5b35a --- /dev/null +++ b/cli/build.rs @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const FILE_HEADER: &str = "/*---------------------------------------------------------------------------------------------\n * Copyright (c) Microsoft Corporation. All rights reserved.\n * Licensed under the Source EULA. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/"; + +use std::{ + env, fs, io, + path::PathBuf, + process::{self, Command}, + str::FromStr, +}; + +fn main() { + let files = enumerate_source_files().expect("expected to enumerate files"); + ensure_file_headers(&files).expect("expected to ensure file headers"); + apply_build_environment_variables(); +} + +fn apply_build_environment_variables() { + // only do this for local, debug builds + if env::var("PROFILE").unwrap() != "debug" { + return; + } + + let pkg_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let mut cmd = Command::new("node"); + cmd.arg("../build/azure-pipelines/cli/prepare.js"); + cmd.current_dir(&pkg_dir); + cmd.env("VSCODE_CLI_PREPARE_OUTPUT", "json"); + + let mut distro_location = PathBuf::from_str(&pkg_dir).unwrap(); + distro_location.pop(); // vscode dir + distro_location.pop(); // parent dir + distro_location.push("vscode-distro"); // distro dir, perhaps? + if distro_location.exists() { + cmd.env("VSCODE_CLI_PREPARE_ROOT", distro_location); + cmd.env("VSCODE_QUALITY", "insider"); + } + + let output = cmd.output().expect("expected to run prepare script"); + if !output.status.success() { + eprint!( + "error running prepare script: {}", + String::from_utf8_lossy(&output.stderr) + ); + process::exit(output.status.code().unwrap_or(1)); + } + + let vars = serde_json::from_slice::>(&output.stdout) + .expect("expected to deserialize output"); + for (key, value) in vars { + println!("cargo:rustc-env={}={}", key, value); + } +} + +fn ensure_file_headers(files: &[PathBuf]) -> Result<(), io::Error> { + let mut ok = true; + + let crlf_header_str = str::replace(FILE_HEADER, "\n", "\r\n"); + let crlf_header = crlf_header_str.as_bytes(); + let lf_header = FILE_HEADER.as_bytes(); + for file in files { + let contents = fs::read(file)?; + + if !(contents.starts_with(lf_header) || contents.starts_with(crlf_header)) { + eprintln!("File missing copyright header: {}", file.display()); + ok = false; + } + } + + if !ok { + process::exit(1); + } + + Ok(()) +} + +/// Gets all "rs" files in the source directory +fn enumerate_source_files() -> Result, io::Error> { + let mut files = vec![]; + let mut queue = vec![]; + + let current_dir = env::current_dir()?.join("src"); + queue.push(current_dir); + + while !queue.is_empty() { + for entry in fs::read_dir(queue.pop().unwrap())? { + let entry = entry?; + let ftype = entry.file_type()?; + if ftype.is_dir() { + queue.push(entry.path()); + } else if ftype.is_file() && entry.file_name().to_string_lossy().ends_with(".rs") { + files.push(entry.path()); + } + } + } + + Ok(files) +} diff --git a/cli/rustfmt.toml b/cli/rustfmt.toml new file mode 100644 index 0000000000..218e203215 --- /dev/null +++ b/cli/rustfmt.toml @@ -0,0 +1 @@ +hard_tabs = true diff --git a/cli/src/async_pipe.rs b/cli/src/async_pipe.rs new file mode 100644 index 0000000000..f38cd73b2b --- /dev/null +++ b/cli/src/async_pipe.rs @@ -0,0 +1,183 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use crate::{constants::APPLICATION_NAME, util::errors::CodeError}; +use std::path::{Path, PathBuf}; +use uuid::Uuid; + +// todo: we could probably abstract this into some crate, if one doesn't already exist + +cfg_if::cfg_if! { + if #[cfg(unix)] { + pub type AsyncPipe = tokio::net::UnixStream; + pub type AsyncPipeWriteHalf = tokio::net::unix::OwnedWriteHalf; + pub type AsyncPipeReadHalf = tokio::net::unix::OwnedReadHalf; + + pub async fn get_socket_rw_stream(path: &Path) -> Result { + tokio::net::UnixStream::connect(path) + .await + .map_err(CodeError::AsyncPipeFailed) + } + + pub async fn listen_socket_rw_stream(path: &Path) -> Result { + tokio::net::UnixListener::bind(path) + .map(AsyncPipeListener) + .map_err(CodeError::AsyncPipeListenerFailed) + } + + pub struct AsyncPipeListener(tokio::net::UnixListener); + + impl AsyncPipeListener { + pub async fn accept(&mut self) -> Result { + self.0.accept().await.map_err(CodeError::AsyncPipeListenerFailed).map(|(s, _)| s) + } + } + + pub fn socket_stream_split(pipe: AsyncPipe) -> (AsyncPipeReadHalf, AsyncPipeWriteHalf) { + pipe.into_split() + } + } else { + use tokio::{time::sleep, io::{AsyncRead, AsyncWrite, ReadBuf}}; + use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions, NamedPipeClient, NamedPipeServer}; + use std::{time::Duration, pin::Pin, task::{Context, Poll}, io}; + use pin_project::pin_project; + + #[pin_project(project = AsyncPipeProj)] + pub enum AsyncPipe { + PipeClient(#[pin] NamedPipeClient), + PipeServer(#[pin] NamedPipeServer), + } + + impl AsyncRead for AsyncPipe { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + match self.project() { + AsyncPipeProj::PipeClient(c) => c.poll_read(cx, buf), + AsyncPipeProj::PipeServer(c) => c.poll_read(cx, buf), + } + } + } + + impl AsyncWrite for AsyncPipe { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match self.project() { + AsyncPipeProj::PipeClient(c) => c.poll_write(cx, buf), + AsyncPipeProj::PipeServer(c) => c.poll_write(cx, buf), + } + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + match self.project() { + AsyncPipeProj::PipeClient(c) => c.poll_write_vectored(cx, bufs), + AsyncPipeProj::PipeServer(c) => c.poll_write_vectored(cx, bufs), + } + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.project() { + AsyncPipeProj::PipeClient(c) => c.poll_flush(cx), + AsyncPipeProj::PipeServer(c) => c.poll_flush(cx), + } + } + + fn is_write_vectored(&self) -> bool { + match self { + AsyncPipe::PipeClient(c) => c.is_write_vectored(), + AsyncPipe::PipeServer(c) => c.is_write_vectored(), + } + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.project() { + AsyncPipeProj::PipeClient(c) => c.poll_shutdown(cx), + AsyncPipeProj::PipeServer(c) => c.poll_shutdown(cx), + } + } + } + + pub type AsyncPipeWriteHalf = tokio::io::WriteHalf; + pub type AsyncPipeReadHalf = tokio::io::ReadHalf; + + pub async fn get_socket_rw_stream(path: &Path) -> Result { + // Tokio says we can need to try in a loop. Do so. + // https://docs.rs/tokio/latest/tokio/net/windows/named_pipe/struct.NamedPipeClient.html + let client = loop { + match ClientOptions::new().open(path) { + Ok(client) => break client, + // ERROR_PIPE_BUSY https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- + Err(e) if e.raw_os_error() == Some(231) => sleep(Duration::from_millis(100)).await, + Err(e) => return Err(CodeError::AsyncPipeFailed(e)), + } + }; + + Ok(AsyncPipe::PipeClient(client)) + } + + pub struct AsyncPipeListener { + path: PathBuf, + server: NamedPipeServer + } + + impl AsyncPipeListener { + pub async fn accept(&mut self) -> Result { + // see https://docs.rs/tokio/latest/tokio/net/windows/named_pipe/struct.NamedPipeServer.html + // this is a bit weird in that the server becomes the client once + // they get a connection, and we create a new client. + + self.server + .connect() + .await + .map_err(CodeError::AsyncPipeListenerFailed)?; + + // Construct the next server to be connected before sending the one + // we already have of onto a task. This ensures that the server + // isn't closed (after it's done in the task) before a new one is + // available. Otherwise the client might error with + // `io::ErrorKind::NotFound`. + let next_server = ServerOptions::new() + .create(&self.path) + .map_err(CodeError::AsyncPipeListenerFailed)?; + + + Ok(AsyncPipe::PipeServer(std::mem::replace(&mut self.server, next_server))) + } + } + + pub async fn listen_socket_rw_stream(path: &Path) -> Result { + let server = ServerOptions::new() + .first_pipe_instance(true) + .create(path) + .map_err(CodeError::AsyncPipeListenerFailed)?; + + Ok(AsyncPipeListener { path: path.to_owned(), server }) + } + + pub fn socket_stream_split(pipe: AsyncPipe) -> (AsyncPipeReadHalf, AsyncPipeWriteHalf) { + tokio::io::split(pipe) + } + } +} + +/// Gets a random name for a pipe/socket on the paltform +pub fn get_socket_name() -> PathBuf { + cfg_if::cfg_if! { + if #[cfg(unix)] { + std::env::temp_dir().join(format!("{}-{}", APPLICATION_NAME, Uuid::new_v4())) + } else { + PathBuf::from(format!(r"\\.\pipe\{}-{}", APPLICATION_NAME, Uuid::new_v4())) + } + } +} diff --git a/cli/src/auth.rs b/cli/src/auth.rs new file mode 100644 index 0000000000..8e289c21b1 --- /dev/null +++ b/cli/src/auth.rs @@ -0,0 +1,639 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use crate::{ + constants::{get_default_user_agent, PRODUCT_NAME_LONG}, + debug, info, log, + state::{LauncherPaths, PersistedState}, + trace, + util::{ + errors::{ + wrap, AnyError, OAuthError, RefreshTokenNotAvailableError, StatusError, WrappedError, + }, + input::prompt_options, + }, + warning, +}; +use async_trait::async_trait; +use chrono::{DateTime, Duration, Utc}; +use gethostname::gethostname; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{cell::Cell, fmt::Display, path::PathBuf, sync::Arc}; +use tokio::time::sleep; +use tunnels::{ + contracts::PROD_FIRST_PARTY_APP_ID, + management::{Authorization, AuthorizationProvider, HttpError}, +}; + +#[derive(Deserialize)] +struct DeviceCodeResponse { + device_code: String, + user_code: String, + message: Option, + verification_uri: String, + expires_in: i64, +} + +#[derive(Deserialize)] +struct AuthenticationResponse { + access_token: String, + refresh_token: Option, + expires_in: Option, +} + +#[derive(Deserialize)] +struct AuthenticationError { + error: String, + error_description: Option, +} + +#[derive(clap::ArgEnum, Serialize, Deserialize, Debug, Clone, Copy)] +pub enum AuthProvider { + Microsoft, + Github, +} + +impl Display for AuthProvider { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AuthProvider::Microsoft => write!(f, "Microsoft Account"), + AuthProvider::Github => write!(f, "Github Account"), + } + } +} + +impl AuthProvider { + pub fn client_id(&self) -> &'static str { + match self { + AuthProvider::Microsoft => "aebc6443-996d-45c2-90f0-388ff96faa56", + AuthProvider::Github => "01ab8ac9400c4e429b23", + } + } + + pub fn code_uri(&self) -> &'static str { + match self { + AuthProvider::Microsoft => { + "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode" + } + AuthProvider::Github => "https://github.com/login/device/code", + } + } + + pub fn grant_uri(&self) -> &'static str { + match self { + AuthProvider::Microsoft => "https://login.microsoftonline.com/common/oauth2/v2.0/token", + AuthProvider::Github => "https://github.com/login/oauth/access_token", + } + } + + pub fn get_default_scopes(&self) -> String { + match self { + AuthProvider::Microsoft => format!( + "{}/.default+offline_access+profile+openid", + PROD_FIRST_PARTY_APP_ID + ), + AuthProvider::Github => "read:user+read:org".to_string(), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct StoredCredential { + #[serde(rename = "p")] + provider: AuthProvider, + #[serde(rename = "a")] + access_token: String, + #[serde(rename = "r")] + refresh_token: Option, + #[serde(rename = "e")] + expires_at: Option>, +} + +impl StoredCredential { + pub async fn is_expired(&self, log: &log::Logger, client: &reqwest::Client) -> bool { + match self.provider { + AuthProvider::Microsoft => self + .expires_at + .map(|e| Utc::now() + chrono::Duration::minutes(5) > e) + .unwrap_or(false), + + // Make an auth request to Github. Mark the credential as expired + // only on a verifiable 4xx code. We don't error on any failed + // request since then a drop in connection could "require" a refresh + AuthProvider::Github => { + let res = client + .get("https://api.github.com/user") + .header("Authorization", format!("token {}", self.access_token)) + .header("User-Agent", get_default_user_agent()) + .send() + .await; + let res = match res { + Ok(r) => r, + Err(e) => { + warning!(log, "failed to check Github token: {}", e); + return false; + } + }; + + if res.status().is_success() { + return false; + } + + let err = StatusError::from_res(res).await; + debug!(log, "github token looks expired: {:?}", err); + true + } + } + } + + fn from_response(auth: AuthenticationResponse, provider: AuthProvider) -> Self { + StoredCredential { + provider, + access_token: auth.access_token, + refresh_token: auth.refresh_token, + expires_at: auth.expires_in.map(|e| Utc::now() + Duration::seconds(e)), + } + } +} + +struct StorageWithLastRead { + storage: Box, + last_read: Cell, WrappedError>>, +} + +#[derive(Clone)] +pub struct Auth { + client: reqwest::Client, + log: log::Logger, + file_storage_path: PathBuf, + storage: Arc>>, +} + +trait StorageImplementation: Send + Sync { + fn read(&mut self) -> Result, WrappedError>; + fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError>; + fn clear(&mut self) -> Result<(), WrappedError>; +} + +// unseal decrypts and deserializes the value +fn seal(value: &T) -> String +where + T: Serialize + ?Sized, +{ + let dec = serde_json::to_string(value).expect("expected to serialize"); + if std::env::var("VSCODE_CLI_DISABLE_KEYCHAIN_ENCRYPT").is_ok() { + return dec; + } + encrypt(&dec) +} + +// unseal decrypts and deserializes the value +fn unseal(value: &str) -> Option +where + T: DeserializeOwned, +{ + // small back-compat for old unencrypted values, or if VSCODE_CLI_DISABLE_KEYCHAIN_ENCRYPT set + if let Ok(v) = serde_json::from_str::(value) { + return Some(v); + } + + let dec = decrypt(value)?; + serde_json::from_str::(&dec).ok() +} + +#[cfg(target_os = "windows")] +const KEYCHAIN_ENTRY_LIMIT: usize = 1024; +#[cfg(not(target_os = "windows"))] +const KEYCHAIN_ENTRY_LIMIT: usize = 128 * 1024; + +const CONTINUE_MARKER: &str = ""; + +#[derive(Default)] +struct KeyringStorage { + // keywring storage can be split into multiple entries due to entry length limits + // on Windows https://github.com/microsoft/vscode-cli/issues/358 + entries: Vec, +} + +macro_rules! get_next_entry { + ($self: expr, $i: expr) => { + match $self.entries.get($i) { + Some(e) => e, + None => { + let e = keyring::Entry::new("vscode-cli", &format!("vscode-cli-{}", $i)); + $self.entries.push(e); + $self.entries.last().unwrap() + } + } + }; +} + +impl StorageImplementation for KeyringStorage { + fn read(&mut self) -> Result, WrappedError> { + let mut str = String::new(); + + for i in 0.. { + let entry = get_next_entry!(self, i); + let next_chunk = match entry.get_password() { + Ok(value) => value, + Err(keyring::Error::NoEntry) => return Ok(None), // missing entries? + Err(e) => return Err(wrap(e, "error reading keyring")), + }; + + if next_chunk.ends_with(CONTINUE_MARKER) { + str.push_str(&next_chunk[..next_chunk.len() - CONTINUE_MARKER.len()]); + } else { + str.push_str(&next_chunk); + break; + } + } + + Ok(unseal(&str)) + } + + fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError> { + let sealed = seal(&value); + let step_size = KEYCHAIN_ENTRY_LIMIT - CONTINUE_MARKER.len(); + + for i in (0..sealed.len()).step_by(step_size) { + let entry = get_next_entry!(self, i / step_size); + + let cutoff = i + step_size; + let stored = if cutoff <= sealed.len() { + let mut part = sealed[i..cutoff].to_string(); + part.push_str(CONTINUE_MARKER); + entry.set_password(&part) + } else { + entry.set_password(&sealed[i..]) + }; + + if let Err(e) = stored { + return Err(wrap(e, "error updating keyring")); + } + } + + Ok(()) + } + + fn clear(&mut self) -> Result<(), WrappedError> { + self.read().ok(); // make sure component parts are available + for entry in self.entries.iter() { + entry + .delete_password() + .map_err(|e| wrap(e, "error updating keyring"))?; + } + self.entries.clear(); + + Ok(()) + } +} + +struct FileStorage(PersistedState>); + +impl StorageImplementation for FileStorage { + fn read(&mut self) -> Result, WrappedError> { + Ok(self.0.load().and_then(|s| unseal(&s))) + } + + fn store(&mut self, value: StoredCredential) -> Result<(), WrappedError> { + self.0.save(Some(seal(&value))) + } + + fn clear(&mut self) -> Result<(), WrappedError> { + self.0.save(None) + } +} + +impl Auth { + pub fn new(paths: &LauncherPaths, log: log::Logger) -> Auth { + Auth { + log, + client: reqwest::Client::new(), + file_storage_path: paths.root().join("token.json"), + storage: Arc::new(std::sync::Mutex::new(None)), + } + } + + fn with_storage(&self, op: F) -> T + where + F: FnOnce(&mut StorageWithLastRead) -> T, + { + let mut opt = self.storage.lock().unwrap(); + if let Some(s) = opt.as_mut() { + return op(s); + } + + let mut keyring_storage = KeyringStorage::default(); + let mut file_storage = FileStorage(PersistedState::new(self.file_storage_path.clone())); + + let keyring_storage_result = match std::env::var("VSCODE_CLI_USE_FILE_KEYCHAIN") { + Ok(_) => Err(wrap("", "user prefers file storage")), + _ => keyring_storage.read(), + }; + + let mut storage = match keyring_storage_result { + Ok(v) => StorageWithLastRead { + last_read: Cell::new(Ok(v)), + storage: Box::new(keyring_storage), + }, + Err(_) => StorageWithLastRead { + last_read: Cell::new(file_storage.read()), + storage: Box::new(file_storage), + }, + }; + + let out = op(&mut storage); + *opt = Some(storage); + out + } + + /// Gets a tunnel Authentication for use in the tunnel management API. + pub async fn get_tunnel_authentication(&self) -> Result { + let cred = self.get_credential().await?; + let auth = match cred.provider { + AuthProvider::Microsoft => Authorization::Bearer(cred.access_token), + AuthProvider::Github => Authorization::Github(format!( + "client_id={} {}", + cred.provider.client_id(), + cred.access_token + )), + }; + + Ok(auth) + } + + /// Reads the current details from the keyring. + pub fn get_current_credential(&self) -> Result, WrappedError> { + self.with_storage(|storage| { + let value = storage.last_read.replace(Ok(None)); + storage.last_read.set(value.clone()); + value + }) + } + + /// Clears login info from the keyring. + pub fn clear_credentials(&self) -> Result<(), WrappedError> { + self.with_storage(|storage| { + storage.storage.clear()?; + storage.last_read.set(Ok(None)); + Ok(()) + }) + } + + /// Runs the login flow, optionally pre-filling a provider and/or access token. + pub async fn login( + &self, + provider: Option, + access_token: Option, + ) -> Result { + let provider = match provider { + Some(p) => p, + None => self.prompt_for_provider().await?, + }; + + let credentials = match access_token { + Some(t) => StoredCredential { + provider, + access_token: t, + refresh_token: None, + expires_at: None, + }, + None => self.do_device_code_flow_with_provider(provider).await?, + }; + + self.store_credentials(credentials.clone()); + Ok(credentials) + } + + /// Gets the currently stored credentials, or asks the user to log in. + pub async fn get_credential(&self) -> Result { + let entry = match self.get_current_credential() { + Ok(Some(old_creds)) => { + trace!(self.log, "Found token in keyring"); + match self.get_refreshed_token(&old_creds).await { + Ok(Some(new_creds)) => { + self.store_credentials(new_creds.clone()); + new_creds + } + Ok(None) => old_creds, + Err(e) => { + info!(self.log, "error refreshing token: {}", e); + let new_creds = self + .do_device_code_flow_with_provider(old_creds.provider) + .await?; + self.store_credentials(new_creds.clone()); + new_creds + } + } + } + + Ok(None) => { + trace!(self.log, "No token in keyring, getting a new one"); + let creds = self.do_device_code_flow().await?; + self.store_credentials(creds.clone()); + creds + } + + Err(e) => { + warning!( + self.log, + "Error reading token from keyring, getting a new one: {}", + e + ); + let creds = self.do_device_code_flow().await?; + self.store_credentials(creds.clone()); + creds + } + }; + + Ok(entry) + } + + /// Stores credentials, logging a warning if it fails. + fn store_credentials(&self, creds: StoredCredential) { + self.with_storage(|storage| { + if let Err(e) = storage.storage.store(creds.clone()) { + warning!( + self.log, + "Failed to update keyring with new credentials: {}", + e + ); + } + storage.last_read.set(Ok(Some(creds))); + }) + } + + /// Refreshes the token in the credentials if necessary. Returns None if + /// the token is up to date, or Some new token otherwise. + async fn get_refreshed_token( + &self, + creds: &StoredCredential, + ) -> Result, AnyError> { + if !creds.is_expired(&self.log, &self.client).await { + return Ok(None); + } + + let refresh_token = match &creds.refresh_token { + Some(t) => t, + None => return Err(AnyError::from(RefreshTokenNotAvailableError())), + }; + + self.do_grant( + creds.provider, + format!( + "client_id={}&grant_type=refresh_token&refresh_token={}", + creds.provider.client_id(), + refresh_token + ), + ) + .await + .map(Some) + } + + /// Does a "grant token" request. + async fn do_grant( + &self, + provider: AuthProvider, + body: String, + ) -> Result { + let response = self + .client + .post(provider.grant_uri()) + .body(body) + .header("Accept", "application/json") + .send() + .await?; + + let status_code = response.status().as_u16(); + let body = response.bytes().await?; + if let Ok(body) = serde_json::from_slice::(&body) { + return Ok(StoredCredential::from_response(body, provider)); + } + + if let Ok(res) = serde_json::from_slice::(&body) { + return Err(OAuthError { + error: res.error, + error_description: res.error_description, + } + .into()); + } + + return Err(StatusError { + body: String::from_utf8_lossy(&body).to_string(), + status_code, + url: provider.grant_uri().to_string(), + } + .into()); + } + + /// Implements the device code flow, returning the credentials upon success. + async fn do_device_code_flow(&self) -> Result { + let provider = self.prompt_for_provider().await?; + self.do_device_code_flow_with_provider(provider).await + } + + async fn prompt_for_provider(&self) -> Result { + if std::env::var("VSCODE_CLI_ALLOW_MS_AUTH").is_err() { + return Ok(AuthProvider::Github); + } + + let provider = prompt_options( + format!("How would you like to log in to {}?", PRODUCT_NAME_LONG), + &[AuthProvider::Microsoft, AuthProvider::Github], + )?; + + Ok(provider) + } + + async fn do_device_code_flow_with_provider( + &self, + provider: AuthProvider, + ) -> Result { + loop { + let init_code = self + .client + .post(provider.code_uri()) + .header("Accept", "application/json") + .body(format!( + "client_id={}&scope={}", + provider.client_id(), + provider.get_default_scopes(), + )) + .send() + .await?; + + if !init_code.status().is_success() { + return Err(StatusError::from_res(init_code).await?.into()); + } + + let init_code_json = init_code.json::().await?; + let expires_at = Utc::now() + chrono::Duration::seconds(init_code_json.expires_in); + + match &init_code_json.message { + Some(m) => self.log.result(m), + None => self.log.result(&format!( + "To grant access to the server, please log into {} and use code {}", + init_code_json.verification_uri, init_code_json.user_code + )), + }; + + let body = format!( + "client_id={}&grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code={}", + provider.client_id(), + init_code_json.device_code + ); + + let mut interval_s = 5; + while Utc::now() < expires_at { + sleep(std::time::Duration::from_secs(interval_s)).await; + + match self.do_grant(provider, body.clone()).await { + Ok(creds) => return Ok(creds), + Err(AnyError::OAuthError(e)) if e.error == "slow_down" => { + interval_s += 5; // https://www.rfc-editor.org/rfc/rfc8628#section-3.5 + trace!(self.log, "refresh poll failed, slowing down"); + } + Err(e) => { + trace!(self.log, "refresh poll failed, retrying: {}", e); + } + } + } + } + } +} + +#[async_trait] +impl AuthorizationProvider for Auth { + async fn get_authorization(&self) -> Result { + self.get_tunnel_authentication() + .await + .map_err(|e| HttpError::AuthorizationError(e.to_string())) + } +} + +lazy_static::lazy_static! { + static ref HOSTNAME: Vec = gethostname().to_string_lossy().bytes().collect(); +} + +#[cfg(feature = "vscode-encrypt")] +fn encrypt(value: &str) -> String { + vscode_encrypt::encrypt(&HOSTNAME, value.as_bytes()).expect("expected to encrypt") +} + +#[cfg(feature = "vscode-encrypt")] +fn decrypt(value: &str) -> Option { + let b = vscode_encrypt::decrypt(&HOSTNAME, value).ok()?; + String::from_utf8(b).ok() +} + +#[cfg(not(feature = "vscode-encrypt"))] +fn encrypt(value: &str) -> String { + value.to_owned() +} + +#[cfg(not(feature = "vscode-encrypt"))] +fn decrypt(value: &str) -> Option { + Some(value.to_owned()) +} diff --git a/cli/src/bin/code/legacy_args.rs b/cli/src/bin/code/legacy_args.rs new file mode 100644 index 0000000000..1e858dd7bf --- /dev/null +++ b/cli/src/bin/code/legacy_args.rs @@ -0,0 +1,234 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::collections::HashMap; + +use cli::commands::args::{ + CliCore, Commands, DesktopCodeOptions, ExtensionArgs, ExtensionSubcommand, + InstallExtensionArgs, ListExtensionArgs, UninstallExtensionArgs, +}; + +/// Tries to parse the argv using the legacy CLI interface, looking for its +/// flags and generating a CLI with subcommands if those don't exist. +pub fn try_parse_legacy( + iter: impl IntoIterator>, +) -> Option { + let raw = clap_lex::RawArgs::new(iter); + let mut cursor = raw.cursor(); + raw.next(&mut cursor); // Skip the bin + + // First make a hashmap of all flags and capture positional arguments. + let mut args: HashMap> = HashMap::new(); + let mut last_arg = None; + while let Some(arg) = raw.next(&mut cursor) { + if let Some((long, value)) = arg.to_long() { + if let Ok(long) = long { + last_arg = Some(long.to_string()); + match args.get_mut(long) { + Some(prev) => { + if let Some(v) = value { + prev.push(v.to_str_lossy().to_string()); + } + } + None => { + if let Some(v) = value { + args.insert(long.to_string(), vec![v.to_str_lossy().to_string()]); + } else { + args.insert(long.to_string(), vec![]); + } + } + } + } + } else if let Ok(value) = arg.to_value() { + if let Some(last_arg) = &last_arg { + args.get_mut(last_arg) + .expect("expected to have last arg") + .push(value.to_string()); + } + } + } + + let get_first_arg_value = + |key: &str| args.get(key).and_then(|v| v.first()).map(|s| s.to_string()); + let desktop_code_options = DesktopCodeOptions { + extensions_dir: get_first_arg_value("extensions-dir"), + user_data_dir: get_first_arg_value("user-data-dir"), + use_version: None, + }; + + // Now translate them to subcommands. + // --list-extensions -> ext list + // --install-extension=id -> ext install + // --uninstall-extension=id -> ext uninstall + // --status -> status + + if args.contains_key("list-extensions") { + Some(CliCore { + subcommand: Some(Commands::Extension(ExtensionArgs { + subcommand: ExtensionSubcommand::List(ListExtensionArgs { + category: get_first_arg_value("category"), + show_versions: args.contains_key("show-versions"), + }), + desktop_code_options, + })), + ..Default::default() + }) + } else if let Some(exts) = args.remove("install-extension") { + Some(CliCore { + subcommand: Some(Commands::Extension(ExtensionArgs { + subcommand: ExtensionSubcommand::Install(InstallExtensionArgs { + id_or_path: exts, + pre_release: args.contains_key("pre-release"), + force: args.contains_key("force"), + }), + desktop_code_options, + })), + ..Default::default() + }) + } else if let Some(exts) = args.remove("uninstall-extension") { + Some(CliCore { + subcommand: Some(Commands::Extension(ExtensionArgs { + subcommand: ExtensionSubcommand::Uninstall(UninstallExtensionArgs { id: exts }), + desktop_code_options, + })), + ..Default::default() + }) + } else if args.contains_key("status") { + Some(CliCore { + subcommand: Some(Commands::Status), + ..Default::default() + }) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parses_list_extensions() { + let args = vec![ + "code", + "--list-extensions", + "--category", + "themes", + "--show-versions", + ]; + let cli = try_parse_legacy(args.into_iter()).unwrap(); + + if let Some(Commands::Extension(extension_args)) = cli.subcommand { + if let ExtensionSubcommand::List(list_args) = extension_args.subcommand { + assert_eq!(list_args.category, Some("themes".to_string())); + assert!(list_args.show_versions); + } else { + panic!( + "Expected list subcommand, got {:?}", + extension_args.subcommand + ); + } + } else { + panic!("Expected extension subcommand, got {:?}", cli.subcommand); + } + } + + #[test] + fn test_parses_install_extension() { + let args = vec![ + "code", + "--install-extension", + "connor4312.codesong", + "connor4312.hello-world", + "--pre-release", + "--force", + ]; + let cli = try_parse_legacy(args.into_iter()).unwrap(); + + if let Some(Commands::Extension(extension_args)) = cli.subcommand { + if let ExtensionSubcommand::Install(install_args) = extension_args.subcommand { + assert_eq!( + install_args.id_or_path, + vec!["connor4312.codesong", "connor4312.hello-world"] + ); + assert!(install_args.pre_release); + assert!(install_args.force); + } else { + panic!( + "Expected install subcommand, got {:?}", + extension_args.subcommand + ); + } + } else { + panic!("Expected extension subcommand, got {:?}", cli.subcommand); + } + } + + #[test] + fn test_parses_uninstall_extension() { + let args = vec!["code", "--uninstall-extension", "connor4312.codesong"]; + let cli = try_parse_legacy(args.into_iter()).unwrap(); + + if let Some(Commands::Extension(extension_args)) = cli.subcommand { + if let ExtensionSubcommand::Uninstall(uninstall_args) = extension_args.subcommand { + assert_eq!(uninstall_args.id, vec!["connor4312.codesong"]); + } else { + panic!( + "Expected uninstall subcommand, got {:?}", + extension_args.subcommand + ); + } + } else { + panic!("Expected extension subcommand, got {:?}", cli.subcommand); + } + } + + #[test] + fn test_parses_user_data_dir_and_extensions_dir() { + let args = vec![ + "code", + "--uninstall-extension", + "connor4312.codesong", + "--user-data-dir", + "foo", + "--extensions-dir", + "bar", + ]; + let cli = try_parse_legacy(args.into_iter()).unwrap(); + + if let Some(Commands::Extension(extension_args)) = cli.subcommand { + assert_eq!( + extension_args.desktop_code_options.user_data_dir, + Some("foo".to_string()) + ); + assert_eq!( + extension_args.desktop_code_options.extensions_dir, + Some("bar".to_string()) + ); + if let ExtensionSubcommand::Uninstall(uninstall_args) = extension_args.subcommand { + assert_eq!(uninstall_args.id, vec!["connor4312.codesong"]); + } else { + panic!( + "Expected uninstall subcommand, got {:?}", + extension_args.subcommand + ); + } + } else { + panic!("Expected extension subcommand, got {:?}", cli.subcommand); + } + } + + #[test] + fn test_status() { + let args = vec!["code", "--status"]; + let cli = try_parse_legacy(args.into_iter()).unwrap(); + + if let Some(Commands::Status) = cli.subcommand { + // no-op + } else { + panic!("Expected extension subcommand, got {:?}", cli.subcommand); + } + } +} diff --git a/cli/src/bin/code/main.rs b/cli/src/bin/code/main.rs new file mode 100644 index 0000000000..174469e014 --- /dev/null +++ b/cli/src/bin/code/main.rs @@ -0,0 +1,177 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +mod legacy_args; + +use std::process::Command; + +use clap::Parser; +use cli::{ + commands::{args, tunnels, update, version, CommandContext}, + constants::get_default_user_agent, + desktop, log, + state::LauncherPaths, + util::{ + errors::{wrap, AnyError}, + is_integrated_cli, + prereqs::PreReqChecker, + }, +}; +use legacy_args::try_parse_legacy; +use opentelemetry::sdk::trace::TracerProvider as SdkTracerProvider; +use opentelemetry::trace::TracerProvider; + +#[tokio::main] +async fn main() -> Result<(), std::convert::Infallible> { + let raw_args = std::env::args_os().collect::>(); + let parsed = try_parse_legacy(&raw_args) + .map(|core| args::AnyCli::Integrated(args::IntegratedCli { core })) + .unwrap_or_else(|| { + if let Ok(true) = is_integrated_cli() { + args::AnyCli::Integrated(args::IntegratedCli::parse_from(&raw_args)) + } else { + args::AnyCli::Standalone(args::StandaloneCli::parse_from(&raw_args)) + } + }); + + let core = parsed.core(); + let context_paths = LauncherPaths::migrate(core.global_options.cli_data_dir.clone()).unwrap(); + let context_args = core.clone(); + + // gets a command context without installing the global logger + let context_no_logger = || CommandContext { + http: reqwest::ClientBuilder::new() + .user_agent(get_default_user_agent()) + .build() + .unwrap(), + paths: context_paths, + log: make_logger(&context_args), + args: context_args, + }; + + // gets a command context with the global logger installer. Usually what most commands want. + macro_rules! context { + () => {{ + let context = context_no_logger(); + log::install_global_logger(context.log.clone()); + context + }}; + } + + let result = match parsed { + args::AnyCli::Standalone(args::StandaloneCli { + subcommand: Some(cmd), + .. + }) => match cmd { + args::StandaloneCommands::Update(args) => update::update(context!(), args).await, + }, + args::AnyCli::Standalone(args::StandaloneCli { core: c, .. }) + | args::AnyCli::Integrated(args::IntegratedCli { core: c, .. }) => match c.subcommand { + None => { + let context = context!(); + let ca = context.args.get_base_code_args(); + start_code(context, ca).await + } + + Some(args::Commands::Extension(extension_args)) => { + let context = context!(); + let mut ca = context.args.get_base_code_args(); + extension_args.add_code_args(&mut ca); + start_code(context, ca).await + } + + Some(args::Commands::Status) => { + let context = context!(); + let mut ca = context.args.get_base_code_args(); + ca.push("--status".to_string()); + start_code(context, ca).await + } + + Some(args::Commands::Version(version_args)) => match version_args.subcommand { + args::VersionSubcommand::Use(use_version_args) => { + version::switch_to(context!(), use_version_args).await + } + args::VersionSubcommand::Show => version::show(context!()).await, + }, + + Some(args::Commands::CommandShell) => tunnels::command_shell(context!()).await, + + Some(args::Commands::Tunnel(tunnel_args)) => match tunnel_args.subcommand { + Some(args::TunnelSubcommand::Prune) => tunnels::prune(context!()).await, + Some(args::TunnelSubcommand::Unregister) => tunnels::unregister(context!()).await, + Some(args::TunnelSubcommand::Kill) => tunnels::kill(context!()).await, + Some(args::TunnelSubcommand::Restart) => tunnels::restart(context!()).await, + Some(args::TunnelSubcommand::Status) => tunnels::status(context!()).await, + Some(args::TunnelSubcommand::Rename(rename_args)) => { + tunnels::rename(context!(), rename_args).await + } + Some(args::TunnelSubcommand::User(user_command)) => { + tunnels::user(context!(), user_command).await + } + Some(args::TunnelSubcommand::Service(service_args)) => { + tunnels::service(context_no_logger(), service_args).await + } + None => tunnels::serve(context_no_logger(), tunnel_args.serve_args).await, + }, + }, + }; + + match result { + Err(e) => print_and_exit(e), + Ok(code) => std::process::exit(code), + } +} + +fn make_logger(core: &args::CliCore) -> log::Logger { + let log_level = if core.global_options.verbose { + log::Level::Trace + } else { + core.global_options.log.unwrap_or(log::Level::Info) + }; + + let tracer = SdkTracerProvider::builder().build().tracer("codecli"); + let mut log = log::Logger::new(tracer, log_level); + if let Some(f) = &core.global_options.log_to_file { + log = log.tee(log::FileLogSink::new(log_level, f).expect("expected to make file logger")) + } + + log +} + +fn print_and_exit(err: E) -> ! +where + E: std::fmt::Display, +{ + log::emit(log::Level::Error, "", &format!("{}", err)); + std::process::exit(1); +} + +async fn start_code(context: CommandContext, args: Vec) -> Result { + // todo: once the integrated CLI takes the place of the Node.js CLI, this should + // redirect to the current installation without using the CodeVersionManager. + + let platform = PreReqChecker::new().verify().await?; + let version_manager = + desktop::CodeVersionManager::new(context.log.clone(), &context.paths, platform); + let version = match &context.args.editor_options.code_options.use_version { + Some(v) => desktop::RequestedVersion::try_from(v.as_str())?, + None => version_manager.get_preferred_version(), + }; + + let binary = match version_manager.try_get_entrypoint(&version).await { + Some(ep) => ep, + None => { + desktop::prompt_to_install(&version); + return Ok(1); + } + }; + + let code = Command::new(&binary) + .args(args) + .status() + .map(|s| s.code().unwrap_or(1)) + .map_err(|e| wrap(e, format!("error running editor from {}", binary.display())))?; + + Ok(code) +} diff --git a/cli/src/commands.rs b/cli/src/commands.rs new file mode 100644 index 0000000000..8b8d1fd2e1 --- /dev/null +++ b/cli/src/commands.rs @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +mod context; + +pub mod args; +pub mod tunnels; +pub mod update; +pub mod version; +pub use context::CommandContext; diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs new file mode 100644 index 0000000000..fa3f0d8459 --- /dev/null +++ b/cli/src/commands/args.rs @@ -0,0 +1,688 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{fmt, path::PathBuf}; + +use crate::{constants, log, options, tunnels::code_server::CodeServerArgs}; +use clap::{ArgEnum, Args, Parser, Subcommand}; +use const_format::concatcp; + +const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI"); +const HELP_COMMANDS: &str = "Usage: {name} [options][paths...] + +To read output from another program, append '-' (e.g. 'echo Hello World | {name} -')"; + +const STANDALONE_TEMPLATE: &str = concatcp!( + CLI_NAME, + " Standalone - {version} + +", + HELP_COMMANDS, + " +Running editor commands requires installing ", + constants::QUALITYLESS_PRODUCT_NAME, + ", and may differ slightly. + +{all-args}" +); +const INTEGRATED_TEMPLATE: &str = concatcp!( + CLI_NAME, + " - {version} + +", + HELP_COMMANDS, + " + +{all-args}" +); + +const COMMIT_IN_VERSION: &str = match constants::VSCODE_CLI_COMMIT { + Some(c) => c, + None => "unknown", +}; +const NUMBER_IN_VERSION: &str = match constants::VSCODE_CLI_VERSION { + Some(c) => c, + None => "dev", +}; +const VERSION: &str = concatcp!(NUMBER_IN_VERSION, " (commit ", COMMIT_IN_VERSION, ")"); + +#[derive(Parser, Debug, Default)] +#[clap( + help_template = INTEGRATED_TEMPLATE, + long_about = None, + version = VERSION, + )] +pub struct IntegratedCli { + #[clap(flatten)] + pub core: CliCore, +} + +/// Common CLI shared between intergated and standalone interfaces. +#[derive(Args, Debug, Default, Clone)] +pub struct CliCore { + /// One or more files, folders, or URIs to open. + #[clap(name = "paths")] + pub open_paths: Vec, + + #[clap(flatten, next_help_heading = Some("EDITOR OPTIONS"))] + pub editor_options: EditorOptions, + + #[clap(flatten, next_help_heading = Some("EDITOR TROUBLESHOOTING"))] + pub troubleshooting: EditorTroubleshooting, + + #[clap(flatten, next_help_heading = Some("GLOBAL OPTIONS"))] + pub global_options: GlobalOptions, + + #[clap(subcommand)] + pub subcommand: Option, +} + +#[derive(Parser, Debug, Default)] +#[clap( + help_template = STANDALONE_TEMPLATE, + long_about = None, + version = VERSION, + )] +pub struct StandaloneCli { + #[clap(flatten)] + pub core: CliCore, + + #[clap(subcommand)] + pub subcommand: Option, +} + +pub enum AnyCli { + Integrated(IntegratedCli), + Standalone(StandaloneCli), +} + +impl AnyCli { + pub fn core(&self) -> &CliCore { + match self { + AnyCli::Integrated(cli) => &cli.core, + AnyCli::Standalone(cli) => &cli.core, + } + } +} + +impl CliCore { + pub fn get_base_code_args(&self) -> Vec { + let mut args = self.open_paths.clone(); + self.editor_options.add_code_args(&mut args); + self.troubleshooting.add_code_args(&mut args); + self.global_options.add_code_args(&mut args); + args + } +} + +impl<'a> From<&'a CliCore> for CodeServerArgs { + fn from(cli: &'a CliCore) -> Self { + let mut args = CodeServerArgs { + log: cli.global_options.log, + accept_server_license_terms: true, + ..Default::default() + }; + + args.log = cli.global_options.log; + args.accept_server_license_terms = true; + + if cli.global_options.verbose { + args.verbose = true; + } + + if cli.global_options.disable_telemetry { + args.telemetry_level = Some(options::TelemetryLevel::Off); + } else if cli.global_options.telemetry_level.is_some() { + args.telemetry_level = cli.global_options.telemetry_level; + } + + args + } +} + +#[derive(Subcommand, Debug, Clone)] +pub enum StandaloneCommands { + /// Updates the CLI. + Update(StandaloneUpdateArgs), +} + +#[derive(Args, Debug, Clone)] +pub struct StandaloneUpdateArgs { + /// Only check for updates, without actually updating the CLI. + #[clap(long)] + pub check: bool, +} + +#[derive(Subcommand, Debug, Clone)] + +pub enum Commands { + /// Create a tunnel that's accessible on vscode.dev from anywhere. + /// Run `code tunnel --help` for more usage info. + Tunnel(TunnelArgs), + + /// Manage editor extensions. + #[clap(name = "ext")] + Extension(ExtensionArgs), + + /// Print process usage and diagnostics information. + Status, + + /// Changes the version of the editor you're using. + Version(VersionArgs), + + /// Runs the control server on process stdin/stdout + #[clap(hide = true)] + CommandShell, +} + +#[derive(Args, Debug, Clone)] +pub struct ExtensionArgs { + #[clap(subcommand)] + pub subcommand: ExtensionSubcommand, + + #[clap(flatten)] + pub desktop_code_options: DesktopCodeOptions, +} + +impl ExtensionArgs { + pub fn add_code_args(&self, target: &mut Vec) { + self.desktop_code_options.add_code_args(target); + self.subcommand.add_code_args(target); + } +} + +#[derive(Subcommand, Debug, Clone)] +pub enum ExtensionSubcommand { + /// List installed extensions. + List(ListExtensionArgs), + /// Install an extension. + Install(InstallExtensionArgs), + /// Uninstall an extension. + Uninstall(UninstallExtensionArgs), +} + +impl ExtensionSubcommand { + pub fn add_code_args(&self, target: &mut Vec) { + match self { + ExtensionSubcommand::List(args) => { + target.push("--list-extensions".to_string()); + if args.show_versions { + target.push("--show-versions".to_string()); + } + if let Some(category) = &args.category { + target.push(format!("--category={}", category)); + } + } + ExtensionSubcommand::Install(args) => { + for id in args.id_or_path.iter() { + target.push(format!("--install-extension={}", id)); + } + if args.pre_release { + target.push("--pre-release".to_string()); + } + if args.force { + target.push("--force".to_string()); + } + } + ExtensionSubcommand::Uninstall(args) => { + for id in args.id.iter() { + target.push(format!("--uninstall-extension={}", id)); + } + } + } + } +} + +#[derive(Args, Debug, Clone)] +pub struct ListExtensionArgs { + /// Filters installed extensions by provided category, when using --list-extensions. + #[clap(long, value_name = "category")] + pub category: Option, + + /// Show versions of installed extensions, when using --list-extensions. + #[clap(long)] + pub show_versions: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct InstallExtensionArgs { + /// Either an extension id or a path to a VSIX. The identifier of an + /// extension is '${publisher}.${name}'. Use '--force' argument to update + /// to latest version. To install a specific version provide '@${version}'. + /// For example: 'vscode.csharp@1.2.3'. + #[clap(name = "ext-id | id")] + pub id_or_path: Vec, + + /// Installs the pre-release version of the extension + #[clap(long)] + pub pre_release: bool, + + /// Update to the latest version of the extension if it's already installed. + #[clap(long)] + pub force: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct UninstallExtensionArgs { + /// One or more extension identifiers to uninstall. The identifier of an + /// extension is '${publisher}.${name}'. Use '--force' argument to update + /// to latest version. + #[clap(name = "ext-id")] + pub id: Vec, +} + +#[derive(Args, Debug, Clone)] +pub struct VersionArgs { + #[clap(subcommand)] + pub subcommand: VersionSubcommand, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum VersionSubcommand { + /// Switches the version of the editor in use. + Use(UseVersionArgs), + + /// Shows the currently configured editor version. + Show, +} + +#[derive(Args, Debug, Clone)] +pub struct UseVersionArgs { + /// The version of the editor you want to use. Can be "stable", "insiders", + /// a version number, or an absolute path to an existing install. + #[clap(value_name = "stable | insiders | x.y.z | path")] + pub name: String, + + /// The directory where the version can be found. + #[clap(long, value_name = "path")] + pub install_dir: Option, +} + +#[derive(Args, Debug, Default, Clone)] +pub struct EditorOptions { + /// Compare two files with each other. + #[clap(short, long, value_names = &["file", "file"])] + pub diff: Vec, + + /// Add folder(s) to the last active window. + #[clap(short, long, value_name = "folder")] + pub add: Option, + + /// Open a file at the path on the specified line and character position. + #[clap(short, long, value_name = "file:line[:character]")] + pub goto: Option, + + /// Force to open a new window. + #[clap(short, long)] + pub new_window: bool, + + /// Force to open a file or folder in an + #[clap(short, long)] + pub reuse_window: bool, + + /// Wait for the files to be closed before returning. + #[clap(short, long)] + pub wait: bool, + + /// The locale to use (e.g. en-US or zh-TW). + #[clap(long, value_name = "locale")] + pub locale: Option, + + /// Enables proposed API features for extensions. Can receive one or + /// more extension IDs to enable individually. + #[clap(long, value_name = "ext-id")] + pub enable_proposed_api: Vec, + + #[clap(flatten)] + pub code_options: DesktopCodeOptions, +} + +impl EditorOptions { + pub fn add_code_args(&self, target: &mut Vec) { + if !self.diff.is_empty() { + target.push("--diff".to_string()); + for file in self.diff.iter() { + target.push(file.clone()); + } + } + if let Some(add) = &self.add { + target.push("--add".to_string()); + target.push(add.clone()); + } + if let Some(goto) = &self.goto { + target.push("--goto".to_string()); + target.push(goto.clone()); + } + if self.new_window { + target.push("--new-window".to_string()); + } + if self.reuse_window { + target.push("--reuse-window".to_string()); + } + if self.wait { + target.push("--wait".to_string()); + } + if let Some(locale) = &self.locale { + target.push(format!("--locale={}", locale)); + } + if !self.enable_proposed_api.is_empty() { + for id in self.enable_proposed_api.iter() { + target.push(format!("--enable-proposed-api={}", id)); + } + } + self.code_options.add_code_args(target); + } +} + +/// Arguments applicable whenever the desktop editor is launched +#[derive(Args, Debug, Default, Clone)] +pub struct DesktopCodeOptions { + /// Set the root path for extensions. + #[clap(long, value_name = "dir")] + pub extensions_dir: Option, + + /// Specifies the directory that user data is kept in. Can be used to + /// open multiple distinct instances of the editor. + #[clap(long, value_name = "dir")] + pub user_data_dir: Option, + + /// Sets the editor version to use for this command. The preferred version + /// can be persisted with `code version use `. Can be "stable", + /// "insiders", a version number, or an absolute path to an existing install. + #[clap(long, value_name = "stable | insiders | x.y.z | path")] + pub use_version: Option, +} + +/// Argument specifying the output format. +#[derive(Args, Debug, Clone)] +pub struct OutputFormatOptions { + /// Set the data output formats. + #[clap(arg_enum, long, value_name = "format", default_value_t = OutputFormat::Text)] + pub format: OutputFormat, +} + +impl DesktopCodeOptions { + pub fn add_code_args(&self, target: &mut Vec) { + if let Some(extensions_dir) = &self.extensions_dir { + target.push(format!("--extensions-dir={}", extensions_dir)); + } + if let Some(user_data_dir) = &self.user_data_dir { + target.push(format!("--user-data-dir={}", user_data_dir)); + } + } +} + +#[derive(Args, Debug, Default, Clone)] +pub struct GlobalOptions { + /// Directory where CLI metadata should be stored. + #[clap(long, env = "VSCODE_CLI_DATA_DIR", global = true)] + pub cli_data_dir: Option, + + /// Print verbose output (implies --wait). + #[clap(long, global = true)] + pub verbose: bool, + + /// Log to a file in addition to stdout. Used when running as a service. + #[clap(long, global = true, hide = true)] + pub log_to_file: Option, + + /// Log level to use. + #[clap(long, arg_enum, value_name = "level", global = true)] + pub log: Option, + + /// Disable telemetry for the current command, even if it was previously + /// accepted as part of the license prompt or specified in '--telemetry-level' + #[clap(long, global = true, hide = true)] + pub disable_telemetry: bool, + + /// Sets the initial telemetry level + #[clap(arg_enum, long, global = true, hide = true)] + pub telemetry_level: Option, +} + +impl GlobalOptions { + pub fn add_code_args(&self, target: &mut Vec) { + if self.verbose { + target.push("--verbose".to_string()); + } + if let Some(log) = self.log { + target.push(format!("--log={}", log)); + } + if self.disable_telemetry { + target.push("--disable-telemetry".to_string()); + } + if let Some(telemetry_level) = &self.telemetry_level { + target.push(format!("--telemetry-level={}", telemetry_level)); + } + } +} + +#[derive(Args, Debug, Default, Clone)] +pub struct EditorTroubleshooting { + /// Run CPU profiler during startup. + #[clap(long)] + pub prof_startup: bool, + + /// Disable all installed extensions. + #[clap(long)] + pub disable_extensions: bool, + + /// Disable an extension. + #[clap(long, value_name = "ext-id")] + pub disable_extension: Vec, + + /// Turn sync on or off. + #[clap(arg_enum, long, value_name = "on | off")] + pub sync: Option, + + /// Allow debugging and profiling of extensions. Check the developer tools for the connection URI. + #[clap(long, value_name = "port")] + pub inspect_extensions: Option, + + /// Allow debugging and profiling of extensions with the extension host + /// being paused after start. Check the developer tools for the connection URI. + #[clap(long, value_name = "port")] + pub inspect_brk_extensions: Option, + + /// Disable GPU hardware acceleration. + #[clap(long)] + pub disable_gpu: bool, + + /// Shows all telemetry events which the editor collects. + #[clap(long)] + pub telemetry: bool, +} + +impl EditorTroubleshooting { + pub fn add_code_args(&self, target: &mut Vec) { + if self.prof_startup { + target.push("--prof-startup".to_string()); + } + if self.disable_extensions { + target.push("--disable-extensions".to_string()); + } + for id in self.disable_extension.iter() { + target.push(format!("--disable-extension={}", id)); + } + if let Some(sync) = &self.sync { + target.push(format!("--sync={}", sync)); + } + if let Some(port) = &self.inspect_extensions { + target.push(format!("--inspect-extensions={}", port)); + } + if let Some(port) = &self.inspect_brk_extensions { + target.push(format!("--inspect-brk-extensions={}", port)); + } + if self.disable_gpu { + target.push("--disable-gpu".to_string()); + } + if self.telemetry { + target.push("--telemetry".to_string()); + } + } +} + +#[derive(ArgEnum, Clone, Copy, Debug)] +pub enum SyncState { + On, + Off, +} + +impl fmt::Display for SyncState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SyncState::Off => write!(f, "off"), + SyncState::On => write!(f, "on"), + } + } +} + +#[derive(ArgEnum, Clone, Copy, Debug)] +pub enum OutputFormat { + Json, + Text, +} + +#[derive(Args, Clone, Debug, Default)] +pub struct ExistingTunnelArgs { + /// Name you'd like to assign preexisting tunnel to use to connect the tunnel + #[clap(long, hide = true)] + pub tunnel_name: Option, + + /// Token to authenticate and use preexisting tunnel + #[clap(long, hide = true)] + pub host_token: Option, + + /// ID of preexisting tunnel to use to connect the tunnel + #[clap(long, hide = true)] + pub tunnel_id: Option, + + /// Cluster of preexisting tunnel to use to connect the tunnel + #[clap(long, hide = true)] + pub cluster: Option, +} + +#[derive(Args, Debug, Clone, Default)] +pub struct TunnelServeArgs { + /// Optional details to connect to an existing tunnel + #[clap(flatten, next_help_heading = Some("ADVANCED OPTIONS"))] + pub tunnel: ExistingTunnelArgs, + + /// Randomly name machine for port forwarding service + #[clap(long)] + pub random_name: bool, + + /// Prevents the machine going to sleep while this command runs. + #[clap(long)] + pub no_sleep: bool, + + /// Sets the machine name for port forwarding service + #[clap(long)] + pub name: Option, + + /// Optional parent process id. If provided, the server will be stopped when the process of the given pid no longer exists + #[clap(long, hide = true)] + pub parent_process_id: Option, + + /// If set, the user accepts the server license terms and the server will be started without a user prompt. + #[clap(long)] + pub accept_server_license_terms: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct TunnelArgs { + #[clap(subcommand)] + pub subcommand: Option, + + #[clap(flatten)] + pub serve_args: TunnelServeArgs, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum TunnelSubcommand { + /// Delete all servers which are currently not running. + Prune, + + /// Stops any running tunnel on the system. + Kill, + + /// Restarts any running tunnel on the system. + Restart, + + /// Gets whether there is a tunnel running on the current machineiou. + Status, + + /// Rename the name of this machine associated with port forwarding service. + Rename(TunnelRenameArgs), + + /// Remove this machine's association with the port forwarding service. + Unregister, + + #[clap(subcommand)] + User(TunnelUserSubCommands), + + /// (Preview) Manages the tunnel when installed as a system service, + #[clap(subcommand)] + Service(TunnelServiceSubCommands), +} + +#[derive(Subcommand, Debug, Clone)] +pub enum TunnelServiceSubCommands { + /// Installs or re-installs the tunnel service on the machine. + Install(TunnelServiceInstallArgs), + + /// Uninstalls and stops the tunnel service. + Uninstall, + + /// Shows logs for the running service. + Log, + + /// Internal command for running the service + #[clap(hide = true)] + InternalRun, +} + +#[derive(Args, Debug, Clone)] +pub struct TunnelServiceInstallArgs { + /// If set, the user accepts the server license terms and the server will be started without a user prompt. + #[clap(long)] + pub accept_server_license_terms: bool, +} + +#[derive(Args, Debug, Clone)] +pub struct TunnelRenameArgs { + /// The name you'd like to rename your machine to. + pub name: String, +} + +#[derive(Subcommand, Debug, Clone)] +pub enum TunnelUserSubCommands { + /// Log in to port forwarding service + Login(LoginArgs), + + /// Log out of port forwarding service + Logout, + + /// Show the account that's logged into port forwarding service + Show, +} + +#[derive(Args, Debug, Clone)] +pub struct LoginArgs { + /// An access token to store for authentication. Note: this will not be + /// refreshed if it expires! + #[clap(long, requires = "provider")] + pub access_token: Option, + + /// The auth provider to use. If not provided, a prompt will be shown. + #[clap(arg_enum, long)] + pub provider: Option, +} + +#[derive(clap::ArgEnum, Debug, Clone, Copy)] +pub enum AuthProvider { + Microsoft, + Github, +} diff --git a/cli/src/commands/context.rs b/cli/src/commands/context.rs new file mode 100644 index 0000000000..b3d4559e62 --- /dev/null +++ b/cli/src/commands/context.rs @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use crate::{log, state::LauncherPaths}; + +use super::args::CliCore; + +pub struct CommandContext { + pub log: log::Logger, + pub paths: LauncherPaths, + pub args: CliCore, + pub http: reqwest::Client, +} diff --git a/cli/src/commands/output.rs b/cli/src/commands/output.rs new file mode 100644 index 0000000000..7db762351f --- /dev/null +++ b/cli/src/commands/output.rs @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::fmt::Display; + +use std::io::{BufWriter, Write}; + +use super::args::OutputFormat; + +pub struct Column { + max_width: usize, + heading: &'static str, + data: Vec, +} + +impl Column { + pub fn new(heading: &'static str) -> Self { + Column { + max_width: heading.len(), + heading, + data: vec![], + } + } + + pub fn add_row(&mut self, row: String) { + self.max_width = std::cmp::max(self.max_width, row.len()); + self.data.push(row); + } +} + +impl OutputFormat { + pub fn print_table(&self, table: OutputTable) -> Result<(), std::io::Error> { + match *self { + OutputFormat::Json => JsonTablePrinter().print(table, &mut std::io::stdout()), + OutputFormat::Text => TextTablePrinter().print(table, &mut std::io::stdout()), + } + } +} + +pub struct OutputTable { + cols: Vec, +} + +impl OutputTable { + pub fn new(cols: Vec) -> Self { + OutputTable { cols } + } +} + +trait TablePrinter { + fn print(&self, table: OutputTable, out: &mut dyn std::io::Write) + -> Result<(), std::io::Error>; +} + +pub struct JsonTablePrinter(); + +impl TablePrinter for JsonTablePrinter { + fn print( + &self, + table: OutputTable, + out: &mut dyn std::io::Write, + ) -> Result<(), std::io::Error> { + let mut bw = BufWriter::new(out); + bw.write_all(b"[")?; + + if !table.cols.is_empty() { + let data_len = table.cols[0].data.len(); + for i in 0..data_len { + if i > 0 { + bw.write_all(b",{")?; + } else { + bw.write_all(b"{")?; + } + for col in &table.cols { + serde_json::to_writer(&mut bw, col.heading)?; + bw.write_all(b":")?; + serde_json::to_writer(&mut bw, &col.data[i])?; + } + } + } + + bw.write_all(b"]")?; + bw.flush() + } +} + +/// Type that prints the output as an ASCII, markdown-style table. +pub struct TextTablePrinter(); + +impl TablePrinter for TextTablePrinter { + fn print( + &self, + table: OutputTable, + out: &mut dyn std::io::Write, + ) -> Result<(), std::io::Error> { + let mut bw = BufWriter::new(out); + + let sizes = table.cols.iter().map(|c| c.max_width).collect::>(); + + // print headers + write_columns(&mut bw, table.cols.iter().map(|c| c.heading), &sizes)?; + // print --- separators + write_columns( + &mut bw, + table.cols.iter().map(|c| "-".repeat(c.max_width)), + &sizes, + )?; + // print each column + if !table.cols.is_empty() { + let data_len = table.cols[0].data.len(); + for i in 0..data_len { + write_columns(&mut bw, table.cols.iter().map(|c| &c.data[i]), &sizes)?; + } + } + + bw.flush() + } +} + +fn write_columns( + mut w: impl Write, + cols: impl Iterator, + sizes: &[usize], +) -> Result<(), std::io::Error> +where + T: Display, +{ + w.write_all(b"|")?; + for (i, col) in cols.enumerate() { + write!(w, " {:width$} |", col, width = sizes[i])?; + } + w.write_all(b"\r\n") +} diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs new file mode 100644 index 0000000000..d2ec3daad4 --- /dev/null +++ b/cli/src/commands/tunnels.rs @@ -0,0 +1,427 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use async_trait::async_trait; +use sha2::{Digest, Sha256}; +use std::{str::FromStr, time::Duration}; +use sysinfo::Pid; + +use super::{ + args::{ + AuthProvider, CliCore, ExistingTunnelArgs, TunnelRenameArgs, TunnelServeArgs, + TunnelServiceSubCommands, TunnelUserSubCommands, + }, + CommandContext, +}; + +use crate::{ + auth::Auth, + constants::{APPLICATION_NAME, TUNNEL_CLI_LOCK_NAME, TUNNEL_SERVICE_LOCK_NAME}, + log, + state::LauncherPaths, + tunnels::{ + code_server::CodeServerArgs, + create_service_manager, dev_tunnels, legal, + paths::get_all_servers, + protocol, serve_stream, + shutdown_signal::ShutdownRequest, + singleton_client::do_single_rpc_call, + singleton_server::{ + make_singleton_server, start_singleton_server, BroadcastLogSink, SingletonServerArgs, + }, + Next, ServeStreamParams, ServiceContainer, ServiceManager, + }, + util::{ + app_lock::AppMutex, + errors::{wrap, AnyError, CodeError}, + prereqs::PreReqChecker, + }, +}; +use crate::{ + singleton::{acquire_singleton, SingletonConnection}, + tunnels::{ + dev_tunnels::ActiveTunnel, + singleton_client::{start_singleton_client, SingletonClientArgs}, + SleepInhibitor, + }, +}; + +impl From for crate::auth::AuthProvider { + fn from(auth_provider: AuthProvider) -> Self { + match auth_provider { + AuthProvider::Github => crate::auth::AuthProvider::Github, + AuthProvider::Microsoft => crate::auth::AuthProvider::Microsoft, + } + } +} + +impl From for Option { + fn from(d: ExistingTunnelArgs) -> Option { + if let (Some(tunnel_id), Some(tunnel_name), Some(cluster), Some(host_token)) = + (d.tunnel_id, d.tunnel_name, d.cluster, d.host_token) + { + Some(dev_tunnels::ExistingTunnel { + tunnel_id, + tunnel_name, + host_token, + cluster, + }) + } else { + None + } + } +} + +struct TunnelServiceContainer { + args: CliCore, +} + +impl TunnelServiceContainer { + fn new(args: CliCore) -> Self { + Self { args } + } +} + +#[async_trait] +impl ServiceContainer for TunnelServiceContainer { + async fn run_service( + &mut self, + log: log::Logger, + launcher_paths: LauncherPaths, + ) -> Result<(), AnyError> { + let csa = (&self.args).into(); + serve_with_csa( + launcher_paths, + log, + TunnelServeArgs { + random_name: true, // avoid prompting + ..Default::default() + }, + csa, + TUNNEL_SERVICE_LOCK_NAME, + ) + .await?; + Ok(()) + } +} + +pub async fn command_shell(ctx: CommandContext) -> Result { + let platform = PreReqChecker::new().verify().await?; + serve_stream( + tokio::io::stdin(), + tokio::io::stderr(), + ServeStreamParams { + log: ctx.log, + launcher_paths: ctx.paths, + platform, + requires_auth: true, + exit_barrier: ShutdownRequest::create_rx([ShutdownRequest::CtrlC]), + code_server_args: (&ctx.args).into(), + }, + ) + .await; + + Ok(0) +} + +pub async fn service( + ctx: CommandContext, + service_args: TunnelServiceSubCommands, +) -> Result { + let manager = create_service_manager(ctx.log.clone(), &ctx.paths); + match service_args { + TunnelServiceSubCommands::Install(args) => { + // ensure logged in, otherwise subsequent serving will fail + Auth::new(&ctx.paths, ctx.log.clone()) + .get_credential() + .await?; + + // likewise for license consent + legal::require_consent(&ctx.paths, args.accept_server_license_terms)?; + + let current_exe = + std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?; + + manager + .register( + current_exe, + &[ + "--verbose", + "--cli-data-dir", + ctx.paths.root().as_os_str().to_string_lossy().as_ref(), + "tunnel", + "service", + "internal-run", + ], + ) + .await?; + ctx.log.result(format!("Service successfully installed! You can use `{} tunnel service log` to monitor it, and `{} tunnel service uninstall` to remove it.", APPLICATION_NAME, APPLICATION_NAME)); + } + TunnelServiceSubCommands::Uninstall => { + manager.unregister().await?; + } + TunnelServiceSubCommands::Log => { + manager.show_logs().await?; + } + TunnelServiceSubCommands::InternalRun => { + manager + .run(ctx.paths.clone(), TunnelServiceContainer::new(ctx.args)) + .await?; + } + } + + Ok(0) +} + +pub async fn user(ctx: CommandContext, user_args: TunnelUserSubCommands) -> Result { + let auth = Auth::new(&ctx.paths, ctx.log.clone()); + match user_args { + TunnelUserSubCommands::Login(login_args) => { + auth.login( + login_args.provider.map(|p| p.into()), + login_args.access_token.to_owned(), + ) + .await?; + } + TunnelUserSubCommands::Logout => { + auth.clear_credentials()?; + } + TunnelUserSubCommands::Show => { + if let Ok(Some(_)) = auth.get_current_credential() { + ctx.log.result("logged in"); + } else { + ctx.log.result("not logged in"); + return Ok(1); + } + } + } + + Ok(0) +} + +/// Remove the tunnel used by this gateway, if any. +pub async fn rename(ctx: CommandContext, rename_args: TunnelRenameArgs) -> Result { + let auth = Auth::new(&ctx.paths, ctx.log.clone()); + let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths); + dt.rename_tunnel(&rename_args.name).await?; + ctx.log.result(format!( + "Successfully renamed this gateway to {}", + &rename_args.name + )); + + Ok(0) +} + +/// Remove the tunnel used by this gateway, if any. +pub async fn unregister(ctx: CommandContext) -> Result { + let auth = Auth::new(&ctx.paths, ctx.log.clone()); + let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths); + dt.remove_tunnel().await?; + Ok(0) +} + +pub async fn restart(ctx: CommandContext) -> Result { + do_single_rpc_call::<_, ()>( + &ctx.paths.tunnel_lockfile(), + ctx.log, + protocol::singleton::METHOD_RESTART, + protocol::EmptyObject {}, + ) + .await + .map(|_| 0) + .map_err(|e| e.into()) +} + +pub async fn kill(ctx: CommandContext) -> Result { + do_single_rpc_call::<_, ()>( + &ctx.paths.tunnel_lockfile(), + ctx.log, + protocol::singleton::METHOD_SHUTDOWN, + protocol::EmptyObject {}, + ) + .await + .map(|_| 0) + .map_err(|e| e.into()) +} + +pub async fn status(ctx: CommandContext) -> Result { + let status = do_single_rpc_call::<_, protocol::singleton::Status>( + &ctx.paths.tunnel_lockfile(), + ctx.log.clone(), + protocol::singleton::METHOD_STATUS, + protocol::EmptyObject {}, + ) + .await; + + match status { + Err(CodeError::NoRunningTunnel) => { + ctx.log.result(CodeError::NoRunningTunnel.to_string()); + Ok(1) + } + Err(e) => Err(e.into()), + Ok(s) => { + ctx.log.result(serde_json::to_string(&s).unwrap()); + Ok(0) + } + } +} + +/// Removes unused servers. +pub async fn prune(ctx: CommandContext) -> Result { + get_all_servers(&ctx.paths) + .into_iter() + .map(|s| s.server_paths(&ctx.paths)) + .filter(|s| s.get_running_pid().is_none()) + .try_for_each(|s| { + ctx.log + .result(format!("Deleted {}", s.server_dir.display())); + s.delete() + }) + .map_err(AnyError::from)?; + + ctx.log.result("Successfully removed all unused servers"); + + Ok(0) +} + +/// Starts the gateway server. +pub async fn serve(ctx: CommandContext, gateway_args: TunnelServeArgs) -> Result { + let CommandContext { + log, paths, args, .. + } = ctx; + + let no_sleep = match gateway_args.no_sleep.then(SleepInhibitor::new) { + Some(i) => match i.await { + Ok(i) => Some(i), + Err(e) => { + warning!(log, "Could not inhibit sleep: {}", e); + None + } + }, + None => None, + }; + + legal::require_consent(&paths, gateway_args.accept_server_license_terms)?; + + let csa = (&args).into(); + let result = serve_with_csa(paths, log, gateway_args, csa, TUNNEL_CLI_LOCK_NAME).await; + drop(no_sleep); + + result +} + +fn get_connection_token(tunnel: &ActiveTunnel) -> String { + let mut hash = Sha256::new(); + hash.update(tunnel.id.as_bytes()); + let result = hash.finalize(); + base64::encode_config(result, base64::URL_SAFE_NO_PAD) +} + +async fn serve_with_csa( + paths: LauncherPaths, + mut log: log::Logger, + gateway_args: TunnelServeArgs, + mut csa: CodeServerArgs, + app_mutex_name: Option<&'static str>, +) -> Result { + let log_broadcast = BroadcastLogSink::new(); + log = log.tee(log_broadcast.clone()); + log::install_global_logger(log.clone()); // re-install so that library logs are captured + + // Intentionally read before starting the server. If the server updated and + // respawn is requested, the old binary will get renamed, and then + // current_exe will point to the wrong path. + let current_exe = std::env::current_exe().unwrap(); + + let mut vec = vec![ + ShutdownRequest::CtrlC, + ShutdownRequest::ExeUninstalled(current_exe.to_owned()), + ]; + if let Some(p) = gateway_args + .parent_process_id + .and_then(|p| Pid::from_str(&p).ok()) + { + vec.push(ShutdownRequest::ParentProcessKilled(p)); + } + let shutdown = ShutdownRequest::create_rx(vec); + + let server = loop { + if shutdown.is_open() { + return Ok(0); + } + + match acquire_singleton(paths.tunnel_lockfile()).await { + Ok(SingletonConnection::Client(stream)) => { + debug!(log, "starting as client to singleton"); + let should_exit = start_singleton_client(SingletonClientArgs { + log: log.clone(), + shutdown: shutdown.clone(), + stream, + }) + .await; + if should_exit { + return Ok(0); + } + } + Ok(SingletonConnection::Singleton(server)) => break server, + Err(e) => { + warning!(log, "error access singleton, retrying: {}", e); + tokio::time::sleep(Duration::from_secs(2)).await + } + } + }; + + debug!(log, "starting as new singleton"); + + let mut server = + make_singleton_server(log_broadcast.clone(), log.clone(), server, shutdown.clone()); + let platform = spanf!(log, log.span("prereq"), PreReqChecker::new().verify())?; + let _lock = app_mutex_name.map(AppMutex::new); + + let auth = Auth::new(&paths, log.clone()); + let mut dt = dev_tunnels::DevTunnels::new(&log, auth, &paths); + loop { + let tunnel = if let Some(d) = gateway_args.tunnel.clone().into() { + dt.start_existing_tunnel(d).await + } else { + dt.start_new_launcher_tunnel(gateway_args.name.as_deref(), gateway_args.random_name) + .await + }?; + + csa.connection_token = Some(get_connection_token(&tunnel)); + + let mut r = start_singleton_server(SingletonServerArgs { + log: log.clone(), + tunnel, + paths: &paths, + code_server_args: &csa, + platform, + log_broadcast: &log_broadcast, + shutdown: shutdown.clone(), + server: &mut server, + }) + .await?; + r.tunnel.close().await.ok(); + + match r.next { + Next::Respawn => { + warning!(log, "respawn requested, starting new server"); + // reuse current args, but specify no-forward since tunnels will + // already be running in this process, and we cannot do a login + let args = std::env::args().skip(1).collect::>(); + let exit = std::process::Command::new(current_exe) + .args(args) + .spawn() + .map_err(|e| wrap(e, "error respawning after update"))? + .wait() + .map_err(|e| wrap(e, "error waiting for child"))?; + + return Ok(exit.code().unwrap_or(1)); + } + Next::Exit => return Ok(0), + Next::Restart => continue, + } + } +} diff --git a/cli/src/commands/update.rs b/cli/src/commands/update.rs new file mode 100644 index 0000000000..ec27f3ba9f --- /dev/null +++ b/cli/src/commands/update.rs @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::sync::Arc; + +use indicatif::ProgressBar; + +use crate::{ + constants::PRODUCT_NAME_LONG, + self_update::SelfUpdate, + update_service::UpdateService, + util::{errors::AnyError, http::ReqwestSimpleHttp, input::ProgressBarReporter}, +}; + +use super::{args::StandaloneUpdateArgs, CommandContext}; + +pub async fn update(ctx: CommandContext, args: StandaloneUpdateArgs) -> Result { + let update_service = UpdateService::new( + ctx.log.clone(), + Arc::new(ReqwestSimpleHttp::with_client(ctx.http.clone())), + ); + let update_service = SelfUpdate::new(&update_service)?; + + let current_version = update_service.get_current_release().await?; + if update_service.is_up_to_date_with(¤t_version) { + ctx.log.result(format!( + "{} is already to to date ({})", + PRODUCT_NAME_LONG, current_version.commit + )); + return Ok(1); + } + + if args.check { + ctx.log + .result(format!("Update to {} is available", current_version)); + return Ok(0); + } + + let pb = ProgressBar::new(1); + pb.set_message("Downloading..."); + update_service + .do_update(¤t_version, ProgressBarReporter::from(pb)) + .await?; + ctx.log + .result(format!("Successfully updated to {}", current_version)); + + Ok(0) +} diff --git a/cli/src/commands/version.rs b/cli/src/commands/version.rs new file mode 100644 index 0000000000..3d2dc9211a --- /dev/null +++ b/cli/src/commands/version.rs @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::path::{Path, PathBuf}; + +use crate::{ + desktop::{prompt_to_install, CodeVersionManager, RequestedVersion}, + log, + util::{ + errors::{AnyError, NoInstallInUserProvidedPath}, + prereqs::PreReqChecker, + }, +}; + +use super::{args::UseVersionArgs, CommandContext}; + +pub async fn switch_to(ctx: CommandContext, args: UseVersionArgs) -> Result { + let platform = PreReqChecker::new().verify().await?; + let vm = CodeVersionManager::new(ctx.log.clone(), &ctx.paths, platform); + let version = RequestedVersion::try_from(args.name.as_str())?; + + let maybe_path = match args.install_dir { + Some(d) => Some( + CodeVersionManager::get_entrypoint_for_install_dir(&PathBuf::from(&d)) + .await + .ok_or(NoInstallInUserProvidedPath(d))?, + ), + None => vm.try_get_entrypoint(&version).await, + }; + + match maybe_path { + Some(p) => { + vm.set_preferred_version(version.clone(), p.clone()).await?; + print_now_using(&ctx.log, &version, &p); + Ok(0) + } + None => { + prompt_to_install(&version); + Ok(1) + } + } +} + +pub async fn show(ctx: CommandContext) -> Result { + let platform = PreReqChecker::new().verify().await?; + let vm = CodeVersionManager::new(ctx.log.clone(), &ctx.paths, platform); + + let version = vm.get_preferred_version(); + println!("Current quality: {}", version); + match vm.try_get_entrypoint(&version).await { + Some(p) => println!("Installation path: {}", p.display()), + None => println!("No existing installation found"), + } + + Ok(0) +} + +fn print_now_using(log: &log::Logger, version: &RequestedVersion, path: &Path) { + log.result(format!("Now using {} from {}", version, path.display())); +} diff --git a/cli/src/constants.rs b/cli/src/constants.rs new file mode 100644 index 0000000000..179baa8612 --- /dev/null +++ b/cli/src/constants.rs @@ -0,0 +1,129 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::collections::HashMap; + +use const_format::concatcp; +use lazy_static::lazy_static; + +use crate::options::Quality; + +pub const CONTROL_PORT: u16 = 31545; + +/// Protocol version sent to clients. This can be used to indiciate new or +/// changed capabilities that clients may wish to leverage. +/// 1 - Initial protocol version +/// 2 - Addition of `serve.compressed` property to control whether servermsg's +/// are compressed bidirectionally. +/// 3 - The server's connection token is set to a SHA256 hash of the tunnel ID +/// 4 - The server's msgpack messages are no longer length-prefixed +pub const PROTOCOL_VERSION: u32 = 4; + +/// Prefix for the tunnel tag that includes the version. +pub const PROTOCOL_VERSION_TAG_PREFIX: &str = "protocolv"; +/// Tag for the current protocol version, which is included in dev tunnels. +pub const PROTOCOL_VERSION_TAG: &str = concatcp!("protocolv", PROTOCOL_VERSION); + +pub const VSCODE_CLI_VERSION: Option<&'static str> = option_env!("VSCODE_CLI_VERSION"); +pub const VSCODE_CLI_AI_KEY: Option<&'static str> = option_env!("VSCODE_CLI_AI_KEY"); +pub const VSCODE_CLI_AI_ENDPOINT: Option<&'static str> = option_env!("VSCODE_CLI_AI_ENDPOINT"); +pub const VSCODE_CLI_QUALITY: Option<&'static str> = option_env!("VSCODE_CLI_QUALITY"); +pub const DOCUMENTATION_URL: Option<&'static str> = option_env!("VSCODE_CLI_DOCUMENTATION_URL"); +pub const VSCODE_CLI_COMMIT: Option<&'static str> = option_env!("VSCODE_CLI_COMMIT"); +pub const VSCODE_CLI_UPDATE_ENDPOINT: Option<&'static str> = + option_env!("VSCODE_CLI_UPDATE_ENDPOINT"); + +/// Windows lock name for the running tunnel service. Used by the setup script +/// to detect a tunnel process. See #179265. +pub const TUNNEL_SERVICE_LOCK_NAME: Option<&'static str> = + option_env!("VSCODE_CLI_TUNNEL_SERVICE_MUTEX"); + +/// Windows lock name for the running tunnel without a service. Used by the setup +/// script to detect a tunnel process. See #179265. +pub const TUNNEL_CLI_LOCK_NAME: Option<&'static str> = option_env!("VSCODE_CLI_TUNNEL_CLI_MUTEX"); + +pub const TUNNEL_SERVICE_USER_AGENT_ENV_VAR: &str = "TUNNEL_SERVICE_USER_AGENT"; + +/// Application name as it appears on the CLI. +pub const APPLICATION_NAME: &str = match option_env!("VSCODE_CLI_APPLICATION_NAME") { + Some(n) => n, + None => "code", +}; + +/// Full name of the product with its version. +pub const PRODUCT_NAME_LONG: &str = match option_env!("VSCODE_CLI_NAME_LONG") { + Some(n) => n, + None => "Code - OSS", +}; + +/// Name of the application without quality information. +pub const QUALITYLESS_PRODUCT_NAME: &str = match option_env!("VSCODE_CLI_QUALITYLESS_PRODUCT_NAME") +{ + Some(n) => n, + None => "Code", +}; + +/// Name of the application without quality information. +pub const QUALITYLESS_SERVER_NAME: &str = concatcp!(QUALITYLESS_PRODUCT_NAME, " Server"); + +/// Web URL the editor is hosted at. For VS Code, this is vscode.dev. +pub const EDITOR_WEB_URL: Option<&'static str> = option_env!("VSCODE_CLI_EDITOR_WEB_URL"); + +/// Name shown in places where we need to tell a user what a process is, e.g. in sleep inhibition. +pub const TUNNEL_ACTIVITY_NAME: &str = concatcp!(PRODUCT_NAME_LONG, " Tunnel"); + +const NONINTERACTIVE_VAR: &str = "VSCODE_CLI_NONINTERACTIVE"; + +/// Default data CLI data directory. +pub const DEFAULT_DATA_PARENT_DIR: &str = match option_env!("VSCODE_CLI_DEFAULT_PARENT_DATA_DIR") { + Some(n) => n, + None => ".vscode-oss", +}; + +pub fn get_default_user_agent() -> String { + format!( + "vscode-server-launcher/{}", + VSCODE_CLI_VERSION.unwrap_or("dev") + ) +} + +const NO_COLOR_ENV: &str = "NO_COLOR"; + +lazy_static! { + pub static ref TUNNEL_SERVICE_USER_AGENT: String = + match std::env::var(TUNNEL_SERVICE_USER_AGENT_ENV_VAR) { + Ok(ua) if !ua.is_empty() => format!("{} {}", ua, get_default_user_agent()), + _ => get_default_user_agent(), + }; + + /// Map of quality names to arrays of app IDs used for them, for example, `{"stable":["ABC123"]}` + pub static ref WIN32_APP_IDS: Option>> = + option_env!("VSCODE_CLI_WIN32_APP_IDS").and_then(|s| serde_json::from_str(s).unwrap()); + + /// Map of quality names to desktop download URIs + pub static ref QUALITY_DOWNLOAD_URIS: Option> = + option_env!("VSCODE_CLI_QUALITY_DOWNLOAD_URIS").and_then(|s| serde_json::from_str(s).unwrap()); + + /// Map of qualities to the long name of the app in that quality + pub static ref PRODUCT_NAME_LONG_MAP: Option> = + option_env!("VSCODE_CLI_NAME_LONG_MAP").and_then(|s| serde_json::from_str(s).unwrap()); + + /// Map of qualities to the application name + pub static ref APPLICATION_NAME_MAP: Option> = + option_env!("VSCODE_CLI_APPLICATION_NAME_MAP").and_then(|s| serde_json::from_str(s).unwrap()); + + /// Map of qualities to the server name + pub static ref SERVER_NAME_MAP: Option> = + option_env!("VSCODE_CLI_SERVER_NAME_MAP").and_then(|s| serde_json::from_str(s).unwrap()); + + /// Whether i/o interactions are allowed in the current CLI. + pub static ref IS_A_TTY: bool = atty::is(atty::Stream::Stdin); + + /// Whether i/o interactions are allowed in the current CLI. + pub static ref COLORS_ENABLED: bool = *IS_A_TTY && std::env::var(NO_COLOR_ENV).is_err(); + + /// Whether i/o interactions are allowed in the current CLI. + pub static ref IS_INTERACTIVE_CLI: bool = *IS_A_TTY && std::env::var(NONINTERACTIVE_VAR).is_err(); +} diff --git a/cli/src/desktop.rs b/cli/src/desktop.rs new file mode 100644 index 0000000000..55e14468a5 --- /dev/null +++ b/cli/src/desktop.rs @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +mod version_manager; + +pub use version_manager::{prompt_to_install, CodeVersionManager, RequestedVersion}; diff --git a/cli/src/desktop/version_manager.rs b/cli/src/desktop/version_manager.rs new file mode 100644 index 0000000000..f8e9fe6dcc --- /dev/null +++ b/cli/src/desktop/version_manager.rs @@ -0,0 +1,617 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + ffi::OsString, + fmt, io, + path::{Path, PathBuf}, +}; + +use lazy_static::lazy_static; +use regex::Regex; +use serde::{Deserialize, Serialize}; + +use crate::{ + constants::{QUALITYLESS_PRODUCT_NAME, QUALITY_DOWNLOAD_URIS}, + log, + options::{self, Quality}, + state::{LauncherPaths, PersistedState}, + update_service::Platform, + util::errors::{AnyError, InvalidRequestedVersion}, +}; + +/// Parsed instance that a user can request. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(tag = "t", content = "c")] +pub enum RequestedVersion { + Quality(options::Quality), + Version { + version: String, + quality: options::Quality, + }, + Commit { + commit: String, + quality: options::Quality, + }, + Path(String), +} + +lazy_static! { + static ref SEMVER_RE: Regex = Regex::new(r"^\d+\.\d+\.\d+(-insider)?$").unwrap(); + static ref COMMIT_RE: Regex = Regex::new(r"^[a-z]+/[a-e0-f]{40}$").unwrap(); +} + +impl RequestedVersion { + pub fn get_command(&self) -> String { + match self { + RequestedVersion::Quality(quality) => { + format!("code version use {}", quality.get_machine_name()) + } + RequestedVersion::Version { version, .. } => { + format!("code version use {}", version) + } + RequestedVersion::Commit { commit, quality } => { + format!("code version use {}/{}", quality.get_machine_name(), commit) + } + RequestedVersion::Path(path) => { + format!("code version use {}", path) + } + } + } +} + +impl std::fmt::Display for RequestedVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + RequestedVersion::Quality(quality) => write!(f, "{}", quality.get_capitalized_name()), + RequestedVersion::Version { version, .. } => { + write!(f, "{}", version) + } + RequestedVersion::Commit { commit, quality } => { + write!(f, "{}/{}", quality, commit) + } + RequestedVersion::Path(path) => write!(f, "{}", path), + } + } +} + +impl TryFrom<&str> for RequestedVersion { + type Error = InvalidRequestedVersion; + + fn try_from(s: &str) -> Result { + if let Ok(quality) = options::Quality::try_from(s) { + return Ok(RequestedVersion::Quality(quality)); + } + + if SEMVER_RE.is_match(s) { + return Ok(RequestedVersion::Version { + quality: if s.ends_with("-insider") { + options::Quality::Insiders + } else { + options::Quality::Stable + }, + version: s.to_string(), + }); + } + + if Path::is_absolute(&PathBuf::from(s)) { + return Ok(RequestedVersion::Path(s.to_string())); + } + + if COMMIT_RE.is_match(s) { + let idx = s.find('/').expect("expected a /"); + if let Ok(quality) = options::Quality::try_from(&s[0..idx]) { + return Ok(RequestedVersion::Commit { + commit: s[idx + 1..].to_string(), + quality, + }); + } + } + + Err(InvalidRequestedVersion()) + } +} + +#[derive(Serialize, Deserialize, Clone, Default)] +struct Stored { + /// Map of requested versions to locations where those versions are installed. + versions: Vec<(RequestedVersion, OsString)>, + current: usize, +} + +pub struct CodeVersionManager { + state: PersistedState, + log: log::Logger, +} + +impl CodeVersionManager { + pub fn new(log: log::Logger, lp: &LauncherPaths, _platform: Platform) -> Self { + CodeVersionManager { + log, + state: PersistedState::new(lp.root().join("versions.json")), + } + } + + /// Tries to find the binary entrypoint for VS Code installed in the path. + pub async fn get_entrypoint_for_install_dir(path: &Path) -> Option { + use tokio::sync::mpsc; + + // Check whether the user is supplying a path to the CLI directly (e.g. #164622) + if let Ok(true) = path.metadata().map(|m| m.is_file()) { + let result = std::process::Command::new(path) + .args(["--version"]) + .output() + .map(|o| o.status.success()); + + if let Ok(true) = result { + return Some(path.to_owned()); + } + } + + let (tx, mut rx) = mpsc::channel(1); + + // Look for all the possible paths in parallel + for entry in DESKTOP_CLI_RELATIVE_PATH.split(',') { + let my_path = path.join(entry); + let my_tx = tx.clone(); + tokio::spawn(async move { + if tokio::fs::metadata(&my_path).await.is_ok() { + my_tx.send(my_path).await.ok(); + } + }); + } + + drop(tx); // drop so rx gets None if no sender emits + + rx.recv().await + } + + /// Sets the "version" as the persisted one for the user. + pub async fn set_preferred_version( + &self, + version: RequestedVersion, + path: PathBuf, + ) -> Result<(), AnyError> { + let mut stored = self.state.load(); + stored.current = self.store_version_path(&mut stored, version, path); + self.state.save(stored)?; + Ok(()) + } + + /// Stores or updates the path used for the given version. Returns the index + /// that the path exists at. + fn store_version_path( + &self, + state: &mut Stored, + version: RequestedVersion, + path: PathBuf, + ) -> usize { + if let Some(i) = state.versions.iter().position(|(v, _)| v == &version) { + state.versions[i].1 = path.into_os_string(); + i + } else { + state + .versions + .push((version.clone(), path.into_os_string())); + state.versions.len() - 1 + } + } + + /// Gets the currently preferred version based on set_preferred_version. + pub fn get_preferred_version(&self) -> RequestedVersion { + let stored = self.state.load(); + stored + .versions + .get(stored.current) + .map(|(v, _)| v.clone()) + .unwrap_or(RequestedVersion::Quality(options::Quality::Stable)) + } + + /// Tries to get the entrypoint for the version, if one can be found. + pub async fn try_get_entrypoint(&self, version: &RequestedVersion) -> Option { + let mut state = self.state.load(); + if let Some((_, install_path)) = state.versions.iter().find(|(v, _)| v == version) { + let p = PathBuf::from(install_path); + if p.exists() { + return Some(p); + } + } + + // For simple quality requests, see if that's installed already on the system + let candidates = match &version { + RequestedVersion::Quality(q) => match detect_installed_program(&self.log, *q) { + Ok(p) => p, + Err(e) => { + warning!(self.log, "error looking up installed applications: {}", e); + return None; + } + }, + _ => return None, + }; + + let found = match candidates.into_iter().next() { + Some(p) => p, + None => return None, + }; + + // stash the found path for faster lookup + self.store_version_path(&mut state, version.clone(), found.clone()); + if let Err(e) = self.state.save(state) { + debug!(self.log, "error caching version path: {}", e); + } + + Some(found) + } +} + +/// Shows a nice UI prompt to users asking them if they want to install the +/// requested version. +pub fn prompt_to_install(version: &RequestedVersion) { + println!( + "No installation of {} {} was found.", + QUALITYLESS_PRODUCT_NAME, version + ); + + if let RequestedVersion::Quality(quality) = version { + if let Some(uri) = QUALITY_DOWNLOAD_URIS.as_ref().and_then(|m| m.get(quality)) { + // todo: on some platforms, we may be able to help automate installation. For example, + // we can unzip the app ourselves on macOS and on windows we can download and spawn the GUI installer + #[cfg(target_os = "linux")] + println!("Install it from your system's package manager or {}, restart your shell, and try again.", uri); + #[cfg(target_os = "macos")] + println!("Download and unzip it from {} and try again.", uri); + #[cfg(target_os = "windows")] + println!("Install it from {} and try again.", uri); + } + } + + println!(); + println!("If you already installed {} and we didn't detect it, run `{} --install-dir /path/to/installation`", QUALITYLESS_PRODUCT_NAME, version.get_command()); +} + +#[cfg(target_os = "macos")] +fn detect_installed_program(log: &log::Logger, quality: Quality) -> io::Result> { + // easy, fast detection for where apps are usually installed + let mut probable = PathBuf::from("/Applications"); + let app_name = quality.get_long_name(); + probable.push(format!("{}.app", app_name)); + if probable.exists() { + probable.extend(["Contents/Resources", "app", "bin", "code"]); + return Ok(vec![probable]); + } + + // _Much_ slower detection using the system_profiler (~10s for me). While the + // profiler can output nicely structure plist xml, pulling in an xml parser + // just for this is overkill. The default output looks something like... + // + // Visual Studio Code - Exploration 2: + // + // Version: 1.73.0-exploration + // Obtained from: Identified Developer + // Last Modified: 9/23/22, 10:16 AM + // Kind: Intel + // Signed by: Developer ID Application: Microsoft Corporation (UBF8T346G9), Developer ID Certification Authority, Apple Root CA + // Location: /Users/connor/Downloads/Visual Studio Code - Exploration 2.app + // + // So, use a simple state machine that looks for the first line, and then for + // the `Location:` line for the path. + info!(log, "Searching for installations on your machine, this is done once and will take about 10 seconds..."); + + let stdout = std::process::Command::new("system_profiler") + .args(["SPApplicationsDataType", "-detailLevel", "mini"]) + .output()? + .stdout; + + enum State { + LookingForName, + LookingForLocation, + } + + let mut state = State::LookingForName; + let mut output: Vec = vec![]; + const LOCATION_PREFIX: &str = "Location:"; + for mut line in String::from_utf8_lossy(&stdout).lines() { + line = line.trim(); + match state { + State::LookingForName => { + if line.starts_with(app_name) && line.ends_with(':') { + state = State::LookingForLocation; + } + } + State::LookingForLocation => { + if let Some(suffix) = line.strip_prefix(LOCATION_PREFIX) { + output.push( + [suffix.trim(), "Contents/Resources", "app", "bin", "code"] + .iter() + .collect(), + ); + state = State::LookingForName; + } + } + } + } + + // Sort shorter paths to the front, preferring "more global" installs, and + // incidentally preferring local installs over Parallels 'installs'. + output.sort_by_key(|a| a.as_os_str().len()); + + Ok(output) +} + +#[cfg(windows)] +fn detect_installed_program(_log: &log::Logger, quality: Quality) -> io::Result> { + use crate::constants::WIN32_APP_IDS; + use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; + use winreg::RegKey; + + let mut output: Vec = vec![]; + let app_ids = match WIN32_APP_IDS.as_ref().and_then(|m| m.get(&quality)) { + Some(ids) => ids, + None => return Ok(output), + }; + + let scopes = [ + ( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall", + ), + ( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", + ), + ( + HKEY_CURRENT_USER, + "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", + ), + ]; + + for (scope, key) in scopes { + let cur_ver = match RegKey::predef(scope).open_subkey(key) { + Ok(k) => k, + Err(_) => continue, + }; + + for key in cur_ver.enum_keys().flatten() { + if app_ids.iter().any(|id| key.contains(id)) { + let sk = cur_ver.open_subkey(&key)?; + if let Ok(location) = sk.get_value::("InstallLocation") { + output.push( + [ + location.as_str(), + "bin", + &format!("{}.cmd", quality.get_application_name()), + ] + .iter() + .collect(), + ) + } + } + } + } + + Ok(output) +} + +// Looks for the given binary name in the PATH, returning all candidate matches. +// Based on https://github.dev/microsoft/vscode-js-debug/blob/7594d05518df6700df51771895fcad0ddc7f92f9/src/common/pathUtils.ts#L15 +#[cfg(target_os = "linux")] +fn detect_installed_program(log: &log::Logger, quality: Quality) -> io::Result> { + let path = match std::env::var("PATH") { + Ok(p) => p, + Err(e) => { + info!(log, "PATH is empty ({}), skipping detection", e); + return Ok(vec![]); + } + }; + + let name = quality.get_application_name(); + let current_exe = std::env::current_exe().expect("expected to read current exe"); + let mut output = vec![]; + for dir in path.split(':') { + let target: PathBuf = [dir, name].iter().collect(); + match std::fs::canonicalize(&target) { + Ok(m) if m == current_exe => continue, + Ok(_) => {} + Err(_) => continue, + }; + + // note: intentionally store the non-canonicalized version, since if it's a + // symlink, (1) it's probably desired to use it and (2) resolving the link + // breaks snap installations. + output.push(target); + } + + Ok(output) +} + +const DESKTOP_CLI_RELATIVE_PATH: &str = if cfg!(target_os = "macos") { + "Contents/Resources/app/bin/code" +} else if cfg!(target_os = "windows") { + "bin/code.cmd,bin/code-insiders.cmd,bin/code-exploration.cmd" +} else { + "bin/code,bin/code-insiders,bin/code-exploration" +}; + +#[cfg(test)] +mod tests { + use std::{ + fs::{create_dir_all, File}, + io::Write, + }; + + use super::*; + + fn make_fake_vscode_install(path: &Path) { + let bin = DESKTOP_CLI_RELATIVE_PATH + .split(',') + .next() + .expect("expected exe path"); + + let binary_file_path = path.join(bin); + let parent_dir_path = binary_file_path.parent().expect("expected parent path"); + + create_dir_all(parent_dir_path).expect("expected to create parent dir"); + + let mut binary_file = File::create(binary_file_path).expect("expected to make file"); + binary_file + .write_all(b"") + .expect("expected to write binary"); + } + + fn make_multiple_vscode_install() -> tempfile::TempDir { + let dir = tempfile::tempdir().expect("expected to make temp dir"); + make_fake_vscode_install(&dir.path().join("desktop/stable")); + make_fake_vscode_install(&dir.path().join("desktop/1.68.2")); + dir + } + + #[test] + fn test_detect_installed_program() { + // developers can run this test and debug output manually; VS Code will not + // be installed in CI, so the test only makes sure it doesn't error out + let result = detect_installed_program(&log::Logger::test(), Quality::Insiders); + println!("result: {:?}", result); + assert!(result.is_ok()); + } + + #[test] + fn test_requested_version_parses() { + assert_eq!( + RequestedVersion::try_from("1.2.3").unwrap(), + RequestedVersion::Version { + quality: options::Quality::Stable, + version: "1.2.3".to_string(), + } + ); + + assert_eq!( + RequestedVersion::try_from("1.2.3-insider").unwrap(), + RequestedVersion::Version { + quality: options::Quality::Insiders, + version: "1.2.3-insider".to_string(), + } + ); + + assert_eq!( + RequestedVersion::try_from("stable").unwrap(), + RequestedVersion::Quality(options::Quality::Stable) + ); + + assert_eq!( + RequestedVersion::try_from("insiders").unwrap(), + RequestedVersion::Quality(options::Quality::Insiders) + ); + + assert_eq!( + RequestedVersion::try_from("insiders/92fd228156aafeb326b23f6604028d342152313b") + .unwrap(), + RequestedVersion::Commit { + commit: "92fd228156aafeb326b23f6604028d342152313b".to_string(), + quality: options::Quality::Insiders + } + ); + + assert_eq!( + RequestedVersion::try_from("stable/92fd228156aafeb326b23f6604028d342152313b").unwrap(), + RequestedVersion::Commit { + commit: "92fd228156aafeb326b23f6604028d342152313b".to_string(), + quality: options::Quality::Stable + } + ); + + let exe = std::env::current_exe() + .expect("expected to get exe") + .to_string_lossy() + .to_string(); + assert_eq!( + RequestedVersion::try_from(exe.as_str()).unwrap(), + RequestedVersion::Path(exe), + ); + } + + #[tokio::test] + async fn test_set_preferred_version() { + let dir = make_multiple_vscode_install(); + let lp = LauncherPaths::new_without_replacements(dir.path().to_owned()); + let vm1 = CodeVersionManager::new(log::Logger::test(), &lp, Platform::LinuxARM64); + + assert_eq!( + vm1.get_preferred_version(), + RequestedVersion::Quality(options::Quality::Stable) + ); + vm1.set_preferred_version( + RequestedVersion::Quality(options::Quality::Exploration), + dir.path().join("desktop/stable"), + ) + .await + .expect("expected to store"); + vm1.set_preferred_version( + RequestedVersion::Quality(options::Quality::Insiders), + dir.path().join("desktop/stable"), + ) + .await + .expect("expected to store"); + assert_eq!( + vm1.get_preferred_version(), + RequestedVersion::Quality(options::Quality::Insiders) + ); + + let vm2 = CodeVersionManager::new(log::Logger::test(), &lp, Platform::LinuxARM64); + assert_eq!( + vm2.get_preferred_version(), + RequestedVersion::Quality(options::Quality::Insiders) + ); + } + + #[tokio::test] + async fn test_gets_entrypoint() { + let dir = make_multiple_vscode_install(); + + assert!(CodeVersionManager::get_entrypoint_for_install_dir( + &dir.path().join("desktop").join("stable") + ) + .await + .is_some()); + + assert!( + CodeVersionManager::get_entrypoint_for_install_dir(&dir.path().join("invalid")) + .await + .is_none() + ); + } + + #[tokio::test] + async fn test_gets_entrypoint_as_binary() { + let dir = tempfile::tempdir().expect("expected to make temp dir"); + + #[cfg(windows)] + let binary_file_path = { + let path = dir.path().join("code.cmd"); + File::create(&path).expect("expected to create file"); + path + }; + + #[cfg(unix)] + let binary_file_path = { + use std::fs; + use std::os::unix::fs::PermissionsExt; + + let path = dir.path().join("code"); + { + let mut f = File::create(&path).expect("expected to create file"); + f.write_all(b"#!/bin/sh") + .expect("expected to write to file"); + } + fs::set_permissions(&path, fs::Permissions::from_mode(0o777)) + .expect("expected to set permissions"); + path + }; + + assert_eq!( + CodeVersionManager::get_entrypoint_for_install_dir(&binary_file_path).await, + Some(binary_file_path) + ); + } +} diff --git a/cli/src/download_cache.rs b/cli/src/download_cache.rs new file mode 100644 index 0000000000..60d8b1556e --- /dev/null +++ b/cli/src/download_cache.rs @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + fs::create_dir_all, + path::{Path, PathBuf}, +}; + +use futures::Future; +use tokio::fs::remove_dir_all; + +use crate::{ + state::PersistedState, + util::errors::{wrap, AnyError, WrappedError}, +}; + +const KEEP_LRU: usize = 5; +const STAGING_SUFFIX: &str = ".staging"; + +#[derive(Clone)] +pub struct DownloadCache { + path: PathBuf, + state: PersistedState>, +} + +impl DownloadCache { + pub fn new(path: PathBuf) -> DownloadCache { + DownloadCache { + state: PersistedState::new(path.join("lru.json")), + path, + } + } + + /// Gets the download cache path. Names of cache entries can be formed by + /// joining them to the path. + pub fn path(&self) -> &Path { + &self.path + } + + /// Gets whether a cache exists with the name already. Marks it as recently + /// used if it does exist. + pub fn exists(&self, name: &str) -> Option { + let p = self.path.join(name); + if !p.exists() { + return None; + } + + let _ = self.touch(name.to_string()); + Some(p) + } + + /// Removes the item from the cache, if it exists + pub fn delete(&self, name: &str) -> Result<(), WrappedError> { + let f = self.path.join(name); + if f.exists() { + std::fs::remove_dir_all(f).map_err(|e| wrap(e, "error removing cached folder"))?; + } + + self.state.update(|l| { + l.retain(|n| n != name); + }) + } + + /// Calls the function to create the cached folder if it doesn't exist, + /// returning the path where the folder is. Note that the path passed to + /// the `do_create` method is a staging path and will not be the same as the + /// final returned path. + pub async fn create( + &self, + name: impl AsRef, + do_create: F, + ) -> Result + where + F: FnOnce(PathBuf) -> T, + T: Future> + Send, + { + let name = name.as_ref(); + let target_dir = self.path.join(name); + if target_dir.exists() { + return Ok(target_dir); + } + + let temp_dir = self.path.join(format!("{}{}", name, STAGING_SUFFIX)); + let _ = remove_dir_all(&temp_dir).await; // cleanup any existing + + create_dir_all(&temp_dir).map_err(|e| wrap(e, "error creating server directory"))?; + do_create(temp_dir.clone()).await?; + + let _ = self.touch(name.to_string()); + std::fs::rename(&temp_dir, &target_dir) + .map_err(|e| wrap(e, "error renaming downloaded server"))?; + + Ok(target_dir) + } + + fn touch(&self, name: String) -> Result<(), AnyError> { + self.state.update(|l| { + if let Some(index) = l.iter().position(|s| s == &name) { + l.remove(index); + } + l.insert(0, name); + + if l.len() <= KEEP_LRU { + return; + } + + if let Some(f) = l.last() { + let f = self.path.join(f); + if !f.exists() || std::fs::remove_dir_all(f).is_ok() { + l.pop(); + } + } + })?; + + Ok(()) + } +} diff --git a/cli/src/json_rpc.rs b/cli/src/json_rpc.rs new file mode 100644 index 0000000000..8d81d3cc21 --- /dev/null +++ b/cli/src/json_rpc.rs @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use tokio::{ + io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader}, + pin, + sync::mpsc, +}; + +use crate::{ + rpc::{self, MaybeSync, Serialization}, + util::{ + errors::InvalidRpcDataError, + sync::{Barrier, Receivable}, + }, +}; +use std::io; + +#[derive(Clone)] +pub struct JsonRpcSerializer {} + +impl Serialization for JsonRpcSerializer { + fn serialize(&self, value: impl serde::Serialize) -> Vec { + let mut v = serde_json::to_vec(&value).unwrap(); + v.push(b'\n'); + v + } + + fn deserialize( + &self, + b: &[u8], + ) -> Result { + serde_json::from_slice(b).map_err(|e| InvalidRpcDataError(e.to_string()).into()) + } +} + +/// Creates a new RPC Builder that serializes to JSON. +#[allow(dead_code)] +pub fn new_json_rpc() -> rpc::RpcBuilder { + rpc::RpcBuilder::new(JsonRpcSerializer {}) +} + +#[allow(dead_code)] +pub async fn start_json_rpc( + dispatcher: rpc::RpcDispatcher, + read: impl AsyncRead + Unpin, + mut write: impl AsyncWrite + Unpin, + mut msg_rx: impl Receivable>, + mut shutdown_rx: Barrier, +) -> io::Result> { + let (write_tx, mut write_rx) = mpsc::channel::>(8); + let mut read = BufReader::new(read); + + let mut read_buf = String::new(); + let shutdown_fut = shutdown_rx.wait(); + pin!(shutdown_fut); + + loop { + tokio::select! { + r = &mut shutdown_fut => return Ok(r.ok()), + Some(w) = write_rx.recv() => { + write.write_all(&w).await?; + }, + Some(w) = msg_rx.recv_msg() => { + write.write_all(&w).await?; + }, + n = read.read_line(&mut read_buf) => { + let r = match n { + Ok(0) => return Ok(None), + Ok(n) => dispatcher.dispatch(read_buf[..n].as_bytes()), + Err(e) => return Err(e) + }; + + read_buf.truncate(0); + + match r { + MaybeSync::Sync(Some(v)) => { + write.write_all(&v).await?; + }, + MaybeSync::Sync(None) => continue, + MaybeSync::Future(fut) => { + let write_tx = write_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + let _ = write_tx.send(v).await; + } + }); + }, + MaybeSync::Stream((dto, fut)) => { + if let Some(dto) = dto { + dispatcher.register_stream(write_tx.clone(), dto).await; + } + let write_tx = write_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + let _ = write_tx.send(v).await; + } + }); + } + } + } + } + } +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs new file mode 100644 index 0000000000..3f68c70721 --- /dev/null +++ b/cli/src/lib.rs @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// todo: we should reduce the exported surface area over time as things are +// moved into a common CLI +pub mod auth; +pub mod constants; +#[macro_use] +pub mod log; +pub mod commands; +pub mod desktop; +pub mod options; +pub mod self_update; +pub mod state; +pub mod tunnels; +pub mod update_service; +pub mod util; + +mod download_cache; +mod async_pipe; +mod json_rpc; +mod msgpack_rpc; +mod rpc; +mod singleton; diff --git a/cli/src/log.rs b/cli/src/log.rs new file mode 100644 index 0000000000..8c3137c5fb --- /dev/null +++ b/cli/src/log.rs @@ -0,0 +1,455 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use chrono::Local; +use opentelemetry::{ + sdk::trace::{Tracer, TracerProvider}, + trace::{SpanBuilder, Tracer as TraitTracer, TracerProvider as TracerProviderTrait}, +}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::{ + io::Write, + sync::atomic::{AtomicU32, Ordering}, +}; +use std::{path::Path, sync::Arc}; + +use crate::constants::COLORS_ENABLED; + +static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(0); + +// Gets a next incrementing number that can be used in logs +pub fn next_counter() -> u32 { + INSTANCE_COUNTER.fetch_add(1, Ordering::SeqCst) +} + +// Log level +#[derive(clap::ArgEnum, PartialEq, Eq, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)] +#[derive(Default)] +pub enum Level { + Trace = 0, + Debug, + #[default] + Info, + Warn, + Error, + Critical, + Off, +} + + + +impl fmt::Display for Level { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Level::Critical => write!(f, "critical"), + Level::Debug => write!(f, "debug"), + Level::Error => write!(f, "error"), + Level::Info => write!(f, "info"), + Level::Off => write!(f, "off"), + Level::Trace => write!(f, "trace"), + Level::Warn => write!(f, "warn"), + } + } +} + +impl Level { + pub fn name(&self) -> Option<&str> { + match self { + Level::Trace => Some("trace"), + Level::Debug => Some("debug"), + Level::Info => Some("info"), + Level::Warn => Some("warn"), + Level::Error => Some("error"), + Level::Critical => Some("critical"), + Level::Off => None, + } + } + + pub fn color_code(&self) -> Option<&str> { + if !*COLORS_ENABLED { + return None; + } + + match self { + Level::Trace => None, + Level::Debug => Some("\x1b[36m"), + Level::Info => Some("\x1b[35m"), + Level::Warn => Some("\x1b[33m"), + Level::Error => Some("\x1b[31m"), + Level::Critical => Some("\x1b[31m"), + Level::Off => None, + } + } + + pub fn to_u8(self) -> u8 { + self as u8 + } +} + +pub fn new_tunnel_prefix() -> String { + format!("[tunnel.{}]", next_counter()) +} + +pub fn new_code_server_prefix() -> String { + format!("[codeserver.{}]", next_counter()) +} + +pub fn new_rpc_prefix() -> String { + format!("[rpc.{}]", next_counter()) +} + +// Base logger implementation +#[derive(Clone)] +pub struct Logger { + tracer: Arc, + sink: Vec>, + prefix: Option, +} + +// Copy trick from https://stackoverflow.com/a/30353928 +pub trait LogSinkClone { + fn clone_box(&self) -> Box; +} + +impl LogSinkClone for T +where + T: 'static + LogSink + Clone, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +pub trait LogSink: LogSinkClone + Sync + Send { + fn write_log(&self, level: Level, prefix: &str, message: &str); + fn write_result(&self, message: &str); +} + +impl Clone for Box { + fn clone(&self) -> Box { + self.clone_box() + } +} + +/// The basic log sink that writes output to stdout, with colors when relevant. +#[derive(Clone)] +pub struct StdioLogSink { + level: Level, +} + +impl LogSink for StdioLogSink { + fn write_log(&self, level: Level, prefix: &str, message: &str) { + if level < self.level { + return; + } + + emit(level, prefix, message); + } + + fn write_result(&self, message: &str) { + println!("{}", message); + } +} + +#[derive(Clone)] +pub struct FileLogSink { + level: Level, + file: Arc>, +} + +impl FileLogSink { + pub fn new(level: Level, path: &Path) -> std::io::Result { + let file = std::fs::File::create(path)?; + Ok(Self { + level, + file: Arc::new(std::sync::Mutex::new(file)), + }) + } +} + +impl LogSink for FileLogSink { + fn write_log(&self, level: Level, prefix: &str, message: &str) { + if level < self.level { + return; + } + + let line = format(level, prefix, message, false); + + // ignore any errors, not much we can do if logging fails... + self.file.lock().unwrap().write_all(line.as_bytes()).ok(); + } + + fn write_result(&self, _message: &str) {} +} + +impl Logger { + pub fn test() -> Self { + Self { + tracer: Arc::new(TracerProvider::builder().build().tracer("codeclitest")), + sink: vec![], + prefix: None, + } + } + + pub fn new(tracer: Tracer, level: Level) -> Self { + Self { + tracer: Arc::new(tracer), + sink: vec![Box::new(StdioLogSink { level })], + prefix: None, + } + } + + pub fn span(&self, name: &str) -> SpanBuilder { + self.tracer.span_builder(format!("serverlauncher/{}", name)) + } + + pub fn tracer(&self) -> &Tracer { + &self.tracer + } + + pub fn emit(&self, level: Level, message: &str) { + let prefix = self.prefix.as_deref().unwrap_or(""); + for sink in &self.sink { + sink.write_log(level, prefix, message); + } + } + + pub fn result(&self, message: impl AsRef) { + for sink in &self.sink { + sink.write_result(message.as_ref()); + } + } + + pub fn prefixed(&self, prefix: &str) -> Logger { + Logger { + prefix: Some(match &self.prefix { + Some(p) => format!("{}{} ", p, prefix), + None => format!("{} ", prefix), + }), + ..self.clone() + } + } + + /// Creates a new logger with the additional log sink added. + pub fn tee(&self, sink: T) -> Logger + where + T: LogSink + 'static, + { + let mut new_sinks = self.sink.clone(); + new_sinks.push(Box::new(sink)); + + Logger { + sink: new_sinks, + ..self.clone() + } + } + + /// Creates a new logger with the sink replace with the given sink. + pub fn with_sink(&self, sink: T) -> Logger + where + T: LogSink + 'static, + { + Logger { + sink: vec![Box::new(sink)], + ..self.clone() + } + } + + pub fn get_download_logger<'a>(&'a self, prefix: &'static str) -> DownloadLogger<'a> { + DownloadLogger { + prefix, + logger: self, + } + } +} + +pub struct DownloadLogger<'a> { + prefix: &'static str, + logger: &'a Logger, +} + +impl<'a> crate::util::io::ReportCopyProgress for DownloadLogger<'a> { + fn report_progress(&mut self, bytes_so_far: u64, total_bytes: u64) { + if total_bytes > 0 { + self.logger.emit( + Level::Trace, + &format!( + "{} {}/{} ({:.0}%)", + self.prefix, + bytes_so_far, + total_bytes, + (bytes_so_far as f64 / total_bytes as f64) * 100.0, + ), + ); + } else { + self.logger.emit( + Level::Trace, + &format!("{} {}/{}", self.prefix, bytes_so_far, total_bytes,), + ); + } + } +} + +fn format(level: Level, prefix: &str, message: &str, use_colors: bool) -> String { + let current = Local::now(); + let timestamp = current.format("%Y-%m-%d %H:%M:%S").to_string(); + + let name = level.name().unwrap(); + + if use_colors { + if let Some(c) = level.color_code() { + return format!( + "\x1b[2m[{}]\x1b[0m {}{}\x1b[0m {}{}\n", + timestamp, c, name, prefix, message + ); + } + } + + format!("[{}] {} {}{}\n", timestamp, name, prefix, message) +} + +pub fn emit(level: Level, prefix: &str, message: &str) { + let line = format(level, prefix, message, true); + if level == Level::Trace { + print!("\x1b[2m{}\x1b[0m", line); + } else { + print!("{}", line); + } +} + +/// Installs the logger instance as the global logger for the 'log' service. +/// Replaces any existing registered logger. Note that the logger will be leaked/ +pub fn install_global_logger(log: Logger) { + log::set_logger(Box::leak(Box::new(RustyLogger(log)))) + .map(|()| log::set_max_level(log::LevelFilter::Debug)) + .expect("expected to make logger"); +} + +/// Logger that uses the common rust "log" crate and directs back to one of +/// our managed loggers. +struct RustyLogger(Logger); + +impl log::Log for RustyLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + metadata.level() <= log::Level::Debug + } + + fn log(&self, record: &log::Record) { + if !self.enabled(record.metadata()) { + return; + } + + // exclude noisy log modules: + let src = match record.module_path() { + Some("russh::cipher") => return, + Some("russh::negotiation") => return, + Some(s) => s, + None => "", + }; + + self.0.emit( + match record.level() { + log::Level::Debug => Level::Debug, + log::Level::Error => Level::Error, + log::Level::Info => Level::Info, + log::Level::Trace => Level::Trace, + log::Level::Warn => Level::Warn, + }, + &format!("[{}] {}", src, record.args()), + ); + } + + fn flush(&self) {} +} + +#[macro_export] +macro_rules! error { + ($logger:expr, $str:expr) => { + $logger.emit(log::Level::Error, $str) + }; + ($logger:expr, $($fmt:expr),+) => { + $logger.emit(log::Level::Error, &format!($($fmt),+)) + }; + } + +#[macro_export] +macro_rules! trace { + ($logger:expr, $str:expr) => { + $logger.emit(log::Level::Trace, $str) + }; + ($logger:expr, $($fmt:expr),+) => { + $logger.emit(log::Level::Trace, &format!($($fmt),+)) + }; + } + +#[macro_export] +macro_rules! debug { + ($logger:expr, $str:expr) => { + $logger.emit(log::Level::Debug, $str) + }; + ($logger:expr, $($fmt:expr),+) => { + $logger.emit(log::Level::Debug, &format!($($fmt),+)) + }; + } + +#[macro_export] +macro_rules! info { + ($logger:expr, $str:expr) => { + $logger.emit(log::Level::Info, $str) + }; + ($logger:expr, $($fmt:expr),+) => { + $logger.emit(log::Level::Info, &format!($($fmt),+)) + }; + } + +#[macro_export] +macro_rules! warning { + ($logger:expr, $str:expr) => { + $logger.emit(log::Level::Warn, $str) + }; + ($logger:expr, $($fmt:expr),+) => { + $logger.emit(log::Level::Warn, &format!($($fmt),+)) + }; + } + +#[macro_export] +macro_rules! span { + ($logger:expr, $span:expr, $func:expr) => {{ + use opentelemetry::trace::TraceContextExt; + + let span = $span.start($logger.tracer()); + let cx = opentelemetry::Context::current_with_span(span); + let guard = cx.clone().attach(); + let t = $func; + + if let Err(e) = &t { + cx.span().record_error(e); + } + + std::mem::drop(guard); + + t + }}; +} + +#[macro_export] +macro_rules! spanf { + ($logger:expr, $span:expr, $func:expr) => {{ + use opentelemetry::trace::{FutureExt, TraceContextExt}; + + let span = $span.start($logger.tracer()); + let cx = opentelemetry::Context::current_with_span(span); + let t = $func.with_context(cx.clone()).await; + + if let Err(e) = &t { + cx.span().record_error(e); + } + + cx.span().end(); + + t + }}; +} diff --git a/cli/src/msgpack_rpc.rs b/cli/src/msgpack_rpc.rs new file mode 100644 index 0000000000..c53adbb11f --- /dev/null +++ b/cli/src/msgpack_rpc.rs @@ -0,0 +1,195 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use bytes::Buf; +use serde::de::DeserializeOwned; +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + pin, + sync::mpsc, +}; +use tokio_util::codec::Decoder; + +use crate::{ + rpc::{self, MaybeSync, Serialization}, + util::{ + errors::{AnyError, InvalidRpcDataError}, + sync::{Barrier, Receivable}, + }, +}; +use std::io::{self, Cursor, ErrorKind}; + +#[derive(Copy, Clone)] +pub struct MsgPackSerializer {} + +impl Serialization for MsgPackSerializer { + fn serialize(&self, value: impl serde::Serialize) -> Vec { + rmp_serde::to_vec_named(&value).expect("expected to serialize") + } + + fn deserialize(&self, b: &[u8]) -> Result { + rmp_serde::from_slice(b).map_err(|e| InvalidRpcDataError(e.to_string()).into()) + } +} + +pub type MsgPackCaller = rpc::RpcCaller; + +/// Creates a new RPC Builder that serializes to msgpack. +pub fn new_msgpack_rpc() -> rpc::RpcBuilder { + rpc::RpcBuilder::new(MsgPackSerializer {}) +} + +/// Starting processing msgpack rpc over the given i/o. It's recommended that +/// the reader be passed in as a BufReader for efficiency. +pub async fn start_msgpack_rpc< + C: Send + Sync + 'static, + X: Clone, + S: Send + Sync + Serialization, + Read: AsyncRead + Unpin, + Write: AsyncWrite + Unpin, +>( + dispatcher: rpc::RpcDispatcher, + mut read: Read, + mut write: Write, + mut msg_rx: impl Receivable>, + mut shutdown_rx: Barrier, +) -> io::Result<(Option, Read, Write)> { + let (write_tx, mut write_rx) = mpsc::channel::>(8); + let mut decoder = MsgPackCodec::new(); + let mut decoder_buf = bytes::BytesMut::new(); + + let shutdown_fut = shutdown_rx.wait(); + pin!(shutdown_fut); + + loop { + tokio::select! { + r = read.read_buf(&mut decoder_buf) => { + r?; + + while let Some(frame) = decoder.decode(&mut decoder_buf)? { + match dispatcher.dispatch_with_partial(&frame.vec, frame.obj) { + MaybeSync::Sync(Some(v)) => { + let _ = write_tx.send(v).await; + }, + MaybeSync::Sync(None) => continue, + MaybeSync::Future(fut) => { + let write_tx = write_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + let _ = write_tx.send(v).await; + } + }); + } + MaybeSync::Stream((stream, fut)) => { + if let Some(stream) = stream { + dispatcher.register_stream(write_tx.clone(), stream).await; + } + let write_tx = write_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + let _ = write_tx.send(v).await; + } + }); + } + } + }; + }, + Some(m) = write_rx.recv() => { + write.write_all(&m).await?; + }, + Some(m) = msg_rx.recv_msg() => { + write.write_all(&m).await?; + }, + r = &mut shutdown_fut => return Ok((r.ok(), read, write)), + } + + write.flush().await?; + } +} + +/// Reader that reads msgpack object messages in a cancellation-safe way using Tokio's codecs. +/// +/// rmp_serde does not support async reads, and does not plan to. But we know every +/// type in protocol is some kind of object, so by asking to deserialize the +/// requested object from a reader (repeatedly, if incomplete) we can +/// accomplish streaming. +pub struct MsgPackCodec { + _marker: std::marker::PhantomData, +} + +impl MsgPackCodec { + pub fn new() -> Self { + Self { + _marker: std::marker::PhantomData::default(), + } + } +} + +pub struct MsgPackDecoded { + pub obj: T, + pub vec: Vec, +} + +impl tokio_util::codec::Decoder for MsgPackCodec { + type Item = MsgPackDecoded; + type Error = io::Error; + + fn decode(&mut self, src: &mut bytes::BytesMut) -> Result, Self::Error> { + let bytes_ref = src.as_ref(); + let mut cursor = Cursor::new(bytes_ref); + + match rmp_serde::decode::from_read::<_, T>(&mut cursor) { + Err( + rmp_serde::decode::Error::InvalidDataRead(e) + | rmp_serde::decode::Error::InvalidMarkerRead(e), + ) if e.kind() == ErrorKind::UnexpectedEof => { + src.reserve(1024); + Ok(None) + } + Err(e) => Err(std::io::Error::new( + std::io::ErrorKind::InvalidData, + e.to_string(), + )), + Ok(obj) => { + let len = cursor.position() as usize; + let vec = src[..len].to_vec(); + src.advance(len); + Ok(Some(MsgPackDecoded { obj, vec })) + } + } + } +} + + let msg = src[U32_SIZE..].to_vec(); +mod tests { + use serde::{Deserialize, Serialize}; + + use super::*; + + #[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] + pub struct Msg { + pub x: i32, + } + + #[test] + fn test_protocol() { + let mut c = MsgPackCodec::::new(); + let mut buf = bytes::BytesMut::new(); + + assert!(c.decode(&mut buf).unwrap().is_none()); + + buf.extend_from_slice(rmp_serde::to_vec_named(&Msg { x: 1 }).unwrap().as_slice()); + buf.extend_from_slice(rmp_serde::to_vec_named(&Msg { x: 2 }).unwrap().as_slice()); + + src.resize(0, 0); + c.decode(&mut buf).unwrap().expect("expected msg1").obj, + Msg { x: 1 } + ); + assert_eq!( + c.decode(&mut buf).unwrap().expect("expected msg1").obj, + Msg { x: 2 } + ); + } +} diff --git a/cli/src/options.rs b/cli/src/options.rs new file mode 100644 index 0000000000..83dda3a6ad --- /dev/null +++ b/cli/src/options.rs @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::fmt; + +use serde::{Deserialize, Serialize}; + +use crate::constants::{APPLICATION_NAME_MAP, PRODUCT_NAME_LONG_MAP, SERVER_NAME_MAP}; + +#[derive(clap::ArgEnum, Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum Quality { + #[serde(rename = "stable")] + Stable, + #[serde(rename = "exploration")] + Exploration, + #[serde(other)] + Insiders, +} + +impl Quality { + /// Lowercased quality name in paths and protocol + pub fn get_machine_name(&self) -> &'static str { + match self { + Quality::Insiders => "insiders", + Quality::Exploration => "exploration", + Quality::Stable => "stable", + } + } + + /// Uppercased quality display name for humans + pub fn get_capitalized_name(&self) -> &'static str { + match self { + Quality::Insiders => "Insiders", + Quality::Exploration => "Exploration", + Quality::Stable => "Stable", + } + } + + /// Product long name + pub fn get_long_name(&self) -> &'static str { + PRODUCT_NAME_LONG_MAP + .as_ref() + .and_then(|m| m.get(self)) + .map(|s| s.as_str()) + .unwrap_or("Code - OSS") + } + + /// Product application name + pub fn get_application_name(&self) -> &'static str { + APPLICATION_NAME_MAP + .as_ref() + .and_then(|m| m.get(self)) + .map(|s| s.as_str()) + .unwrap_or("code") + } + + /// Server application name + pub fn server_entrypoint(&self) -> String { + let mut server_name = SERVER_NAME_MAP + .as_ref() + .and_then(|m| m.get(self)) + .map(|s| s.as_str()) + .unwrap_or("code-server-oss") + .to_string(); + + if cfg!(windows) { + server_name.push_str(".cmd"); + } + + server_name + } +} + +impl fmt::Display for Quality { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.get_capitalized_name()) + } +} + +impl TryFrom<&str> for Quality { + type Error = String; + + fn try_from(s: &str) -> Result { + match s { + "stable" => Ok(Quality::Stable), + "insiders" | "insider" => Ok(Quality::Insiders), + "exploration" => Ok(Quality::Exploration), + _ => Err(format!( + "Unknown quality: {}. Must be one of stable, insiders, or exploration.", + s + )), + } + } +} + +#[derive(clap::ArgEnum, Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum TelemetryLevel { + Off, + Crash, + Error, + All, +} + +impl fmt::Display for TelemetryLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + TelemetryLevel::Off => write!(f, "off"), + TelemetryLevel::Crash => write!(f, "crash"), + TelemetryLevel::Error => write!(f, "error"), + TelemetryLevel::All => write!(f, "all"), + } + } +} diff --git a/cli/src/rpc.rs b/cli/src/rpc.rs new file mode 100644 index 0000000000..cd55b9785a --- /dev/null +++ b/cli/src/rpc.rs @@ -0,0 +1,693 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + collections::HashMap, + future, + sync::{ + atomic::{AtomicU32, Ordering}, + Arc, Mutex, + }, +}; + +use crate::log; +use futures::{future::BoxFuture, Future, FutureExt}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt, DuplexStream, WriteHalf}, + sync::{mpsc, oneshot}, +}; + +use crate::util::errors::AnyError; + +pub type SyncMethod = Arc, &[u8]) -> Option>>; +pub type AsyncMethod = + Arc, &[u8]) -> BoxFuture<'static, Option>>>; +pub type Duplex = Arc< + dyn Send + + Sync + + Fn(Option, &[u8]) -> (Option, BoxFuture<'static, Option>>), +>; + +pub enum Method { + Sync(SyncMethod), + Async(AsyncMethod), + Duplex(Duplex), +} + +/// Serialization is given to the RpcBuilder and defines how data gets serialized +/// when callinth methods. +pub trait Serialization: Send + Sync + 'static { + fn serialize(&self, value: impl Serialize) -> Vec; + fn deserialize(&self, b: &[u8]) -> Result; +} + +/// RPC is a basic, transport-agnostic builder for RPC methods. You can +/// register methods to it, then call `.build()` to get a "dispatcher" type. +pub struct RpcBuilder { + serializer: Arc, + methods: HashMap<&'static str, Method>, + calls: Arc>>, +} + +impl RpcBuilder { + /// Creates a new empty RPC builder. + pub fn new(serializer: S) -> Self { + Self { + serializer: Arc::new(serializer), + methods: HashMap::new(), + calls: Arc::new(std::sync::Mutex::new(HashMap::new())), + } + } + + /// Creates a caller that will be connected to any eventual dispatchers, + /// and that sends data to the "tx" channel. + pub fn get_caller(&mut self, sender: mpsc::UnboundedSender>) -> RpcCaller { + RpcCaller { + serializer: self.serializer.clone(), + calls: self.calls.clone(), + sender, + } + } + + /// Gets a method builder. + pub fn methods(self, context: C) -> RpcMethodBuilder { + RpcMethodBuilder { + context: Arc::new(context), + serializer: self.serializer, + methods: self.methods, + calls: self.calls, + } + } +} + +pub struct RpcMethodBuilder { + context: Arc, + serializer: Arc, + methods: HashMap<&'static str, Method>, + calls: Arc>>, +} + +#[derive(Serialize)] +struct DuplexStreamStarted { + pub for_request_id: u32, + pub stream_ids: Vec, +} + +impl RpcMethodBuilder { + /// Registers a synchronous rpc call that returns its result directly. + pub fn register_sync(&mut self, method_name: &'static str, callback: F) + where + P: DeserializeOwned, + R: Serialize, + F: Fn(P, &C) -> Result + Send + Sync + 'static, + { + if self.methods.contains_key(method_name) { + panic!("Method already registered: {}", method_name); + } + + let serial = self.serializer.clone(); + let context = self.context.clone(); + self.methods.insert( + method_name, + Method::Sync(Arc::new(move |id, body| { + let param = match serial.deserialize::>(body) { + Ok(p) => p, + Err(err) => { + return id.map(|id| { + serial.serialize(&ErrorResponse { + id, + error: ResponseError { + code: 0, + message: format!("{:?}", err), + }, + }) + }) + } + }; + + match callback(param.params, &context) { + Ok(result) => id.map(|id| serial.serialize(&SuccessResponse { id, result })), + Err(err) => id.map(|id| { + serial.serialize(&ErrorResponse { + id, + error: ResponseError { + code: -1, + message: format!("{:?}", err), + }, + }) + }), + } + })), + ); + } + + /// Registers an async rpc call that returns a Future. + pub fn register_async(&mut self, method_name: &'static str, callback: F) + where + P: DeserializeOwned + Send + 'static, + R: Serialize + Send + Sync + 'static, + Fut: Future> + Send, + F: (Fn(P, Arc) -> Fut) + Clone + Send + Sync + 'static, + { + let serial = self.serializer.clone(); + let context = self.context.clone(); + self.methods.insert( + method_name, + Method::Async(Arc::new(move |id, body| { + let param = match serial.deserialize::>(body) { + Ok(p) => p, + Err(err) => { + return future::ready(id.map(|id| { + serial.serialize(&ErrorResponse { + id, + error: ResponseError { + code: 0, + message: format!("{:?}", err), + }, + }) + })) + .boxed(); + } + }; + + let callback = callback.clone(); + let serial = serial.clone(); + let context = context.clone(); + let fut = async move { + match callback(param.params, context).await { + Ok(result) => { + id.map(|id| serial.serialize(&SuccessResponse { id, result })) + } + Err(err) => id.map(|id| { + serial.serialize(&ErrorResponse { + id, + error: ResponseError { + code: -1, + message: format!("{:?}", err), + }, + }) + }), + } + }; + + fut.boxed() + })), + ); + } + + /// Registers an async rpc call that returns a Future containing a duplex + /// stream that should be handled by the client. + pub fn register_duplex( + &mut self, + method_name: &'static str, + streams: usize, + callback: F, + ) where + P: DeserializeOwned + Send + 'static, + R: Serialize + Send + Sync + 'static, + Fut: Future> + Send, + F: (Fn(Vec, P, Arc) -> Fut) + Clone + Send + Sync + 'static, + { + let serial = self.serializer.clone(); + let context = self.context.clone(); + self.methods.insert( + method_name, + Method::Duplex(Arc::new(move |id, body| { + let param = match serial.deserialize::>(body) { + Ok(p) => p, + Err(err) => { + return ( + None, + future::ready(id.map(|id| { + serial.serialize(&ErrorResponse { + id, + error: ResponseError { + code: 0, + message: format!("{:?}", err), + }, + }) + })) + .boxed(), + ); + } + }; + + let callback = callback.clone(); + let serial = serial.clone(); + let context = context.clone(); + + let mut dto = StreamDto { + req_id: id.unwrap_or(0), + streams: Vec::with_capacity(streams), + }; + let mut servers = Vec::with_capacity(streams); + + for _ in 0..streams { + let (client, server) = tokio::io::duplex(8192); + servers.push(server); + dto.streams.push((next_message_id(), client)); + } + + let fut = async move { + match callback(servers, param.params, context).await { + Ok(r) => id.map(|id| serial.serialize(&SuccessResponse { id, result: r })), + Err(err) => id.map(|id| { + serial.serialize(&ErrorResponse { + id, + error: ResponseError { + code: -1, + message: format!("{:?}", err), + }, + }) + }), + } + }; + + (Some(dto), fut.boxed()) + })), + ); + } + + /// Builds into a usable, sync rpc dispatcher. + pub fn build(mut self, log: log::Logger) -> RpcDispatcher { + let streams = Streams::default(); + + let s1 = streams.clone(); + self.register_async(METHOD_STREAM_ENDED, move |m: StreamEndedParams, _| { + let s1 = s1.clone(); + async move { + s1.remove(m.stream).await; + Ok(()) + } + }); + + let s2 = streams.clone(); + self.register_sync(METHOD_STREAM_DATA, move |m: StreamDataIncomingParams, _| { + s2.write(m.stream, m.segment); + Ok(()) + }); + + RpcDispatcher { + log, + context: self.context, + calls: self.calls, + serializer: self.serializer, + methods: Arc::new(self.methods), + streams, + } + } +} + +type DispatchMethod = Box; + +/// Dispatcher returned from a Builder that provides a transport-agnostic way to +/// deserialize and dispatch RPC calls. This structure may get more advanced as +/// time goes on... +#[derive(Clone)] +pub struct RpcCaller { + serializer: Arc, + calls: Arc>>, + sender: mpsc::UnboundedSender>, +} + +impl RpcCaller { + pub fn serialize_notify(serializer: &S, method: M, params: A) -> Vec + where + S: Serialization, + M: AsRef + serde::Serialize, + A: Serialize, + { + serializer.serialize(&FullRequest { + id: None, + method, + params, + }) + } + + /// Enqueues an outbound call. Returns whether the message was enqueued. + pub fn notify(&self, method: M, params: A) -> bool + where + M: AsRef + serde::Serialize, + A: Serialize, + { + self.sender + .send(Self::serialize_notify(&self.serializer, method, params)) + .is_ok() + } + + /// Enqueues an outbound call, returning its result. + pub fn call(&self, method: M, params: A) -> oneshot::Receiver> + where + M: AsRef + serde::Serialize, + A: Serialize, + R: DeserializeOwned + Send + 'static, + { + let (tx, rx) = oneshot::channel(); + let id = next_message_id(); + let body = self.serializer.serialize(&FullRequest { + id: Some(id), + method, + params, + }); + + if self.sender.send(body).is_err() { + drop(tx); + return rx; + } + + let serializer = self.serializer.clone(); + self.calls.lock().unwrap().insert( + id, + Box::new(move |body| { + match body { + Outcome::Error(e) => tx.send(Err(e)).ok(), + Outcome::Success(r) => match serializer.deserialize::>(&r) { + Ok(r) => tx.send(Ok(r.result)).ok(), + Err(err) => tx + .send(Err(ResponseError { + code: 0, + message: err.to_string(), + })) + .ok(), + }, + }; + }), + ); + + rx + } +} + +/// Dispatcher returned from a Builder that provides a transport-agnostic way to +/// deserialize and handle RPC calls. This structure may get more advanced as +/// time goes on... +#[derive(Clone)] +pub struct RpcDispatcher { + log: log::Logger, + context: Arc, + serializer: Arc, + methods: Arc>, + calls: Arc>>, + streams: Streams, +} + +static MESSAGE_ID_COUNTER: AtomicU32 = AtomicU32::new(0); +fn next_message_id() -> u32 { + MESSAGE_ID_COUNTER.fetch_add(1, Ordering::SeqCst) +} + +impl RpcDispatcher { + /// Runs the incoming request, returning the result of the call synchronously + /// or in a future. (The caller can then decide whether to run the future + /// sequentially in its receive loop, or not.) + /// + /// The future or return result will be optional bytes that should be sent + /// back to the socket. + pub fn dispatch(&self, body: &[u8]) -> MaybeSync { + match self.serializer.deserialize::(body) { + Ok(partial) => self.dispatch_with_partial(body, partial), + Err(_err) => { + warning!(self.log, "Failed to deserialize request, hex: {:X?}", body); + MaybeSync::Sync(None) + } + } + } + + /// Like dispatch, but allows passing an existing PartialIncoming. + pub fn dispatch_with_partial(&self, body: &[u8], partial: PartialIncoming) -> MaybeSync { + let id = partial.id; + + if let Some(method_name) = partial.method { + let method = self.methods.get(method_name.as_str()); + match method { + Some(Method::Sync(callback)) => MaybeSync::Sync(callback(id, body)), + Some(Method::Async(callback)) => MaybeSync::Future(callback(id, body)), + Some(Method::Duplex(callback)) => MaybeSync::Stream(callback(id, body)), + None => MaybeSync::Sync(id.map(|id| { + self.serializer.serialize(&ErrorResponse { + id, + error: ResponseError { + code: -1, + message: format!("Method not found: {}", method_name), + }, + }) + })), + } + } else if let Some(err) = partial.error { + if let Some(cb) = self.calls.lock().unwrap().remove(&id.unwrap()) { + cb(Outcome::Error(err)); + } + MaybeSync::Sync(None) + } else { + if let Some(cb) = self.calls.lock().unwrap().remove(&id.unwrap()) { + cb(Outcome::Success(body.to_vec())); + } + MaybeSync::Sync(None) + } + } + + /// Registers a stream call returned from dispatch(). + pub async fn register_stream( + &self, + write_tx: mpsc::Sender> + Send>, + dto: StreamDto, + ) { + let r = write_tx + .send( + self.serializer + .serialize(&FullRequest { + id: None, + method: METHOD_STREAMS_STARTED, + params: DuplexStreamStarted { + stream_ids: dto.streams.iter().map(|(id, _)| *id).collect(), + for_request_id: dto.req_id, + }, + }) + .into(), + ) + .await; + + if r.is_err() { + return; + } + + for (stream_id, duplex) in dto.streams { + let (mut read, write) = tokio::io::split(duplex); + self.streams.insert(stream_id, write); + + let write_tx = write_tx.clone(); + let serial = self.serializer.clone(); + tokio::spawn(async move { + let mut buf = vec![0; 4096]; + loop { + match read.read(&mut buf).await { + Ok(0) | Err(_) => break, + Ok(n) => { + let r = write_tx + .send( + serial + .serialize(&FullRequest { + id: None, + method: METHOD_STREAM_DATA, + params: StreamDataParams { + segment: &buf[..n], + stream: stream_id, + }, + }) + .into(), + ) + .await; + + if r.is_err() { + return; + } + } + } + } + + let _ = write_tx + .send( + serial + .serialize(&FullRequest { + id: None, + method: METHOD_STREAM_ENDED, + params: StreamEndedParams { stream: stream_id }, + }) + .into(), + ) + .await; + }); + } + } + + pub fn context(&self) -> Arc { + self.context.clone() + } +} + +struct StreamRec { + write: Option>, + q: Vec>, +} + +#[derive(Clone, Default)] +struct Streams { + map: Arc>>, +} + +impl Streams { + pub async fn remove(&self, id: u32) { + let stream = self.map.lock().unwrap().remove(&id); + if let Some(s) = stream { + // if there's no 'write' right now, it'll shut down in the write_loop + if let Some(mut w) = s.write { + let _ = w.shutdown().await; + } + } + } + + pub fn write(&self, id: u32, buf: Vec) { + let mut map = self.map.lock().unwrap(); + if let Some(s) = map.get_mut(&id) { + s.q.push(buf); + + if let Some(w) = s.write.take() { + tokio::spawn(write_loop(id, w, self.map.clone())); + } + } + } + + pub fn insert(&self, id: u32, stream: WriteHalf) { + self.map.lock().unwrap().insert( + id, + StreamRec { + write: Some(stream), + q: Vec::new(), + }, + ); + } +} + +/// Write loop started by `Streams.write`. It takes the WriteHalf, and +/// runs until there's no more items in the 'write queue'. At that point, if the +/// record still exists in the `streams` (i.e. we haven't shut down), it'll +/// return the WriteHalf so that the next `write` call starts +/// the loop again. Otherwise, it'll shut down the WriteHalf. +/// +/// This is the equivalent of the same write_loop in the server_multiplexer. +/// I couldn't figure out a nice way to abstract it without introducing +/// performance overhead... +async fn write_loop( + id: u32, + mut w: WriteHalf, + streams: Arc>>, +) { + let mut items_vec = vec![]; + loop { + { + let mut lock = streams.lock().unwrap(); + let stream_rec = match lock.get_mut(&id) { + Some(b) => b, + None => break, + }; + + if stream_rec.q.is_empty() { + stream_rec.write = Some(w); + return; + } + + std::mem::swap(&mut stream_rec.q, &mut items_vec); + } + + for item in items_vec.drain(..) { + if w.write_all(&item).await.is_err() { + break; + } + } + } + + let _ = w.shutdown().await; // got here from `break` above, meaning our record got cleared. Close the bridge if so +} + +const METHOD_STREAMS_STARTED: &str = "streams_started"; +const METHOD_STREAM_DATA: &str = "stream_data"; +const METHOD_STREAM_ENDED: &str = "stream_ended"; + +trait AssertIsSync: Sync {} +impl AssertIsSync for RpcDispatcher {} + +/// Approximate shape that is used to determine what kind of data is incoming. +#[derive(Deserialize, Debug)] +pub struct PartialIncoming { + pub id: Option, + pub method: Option, + pub error: Option, +} + +#[derive(Deserialize)] +struct StreamDataIncomingParams { + #[serde(with = "serde_bytes")] + pub segment: Vec, + pub stream: u32, +} + +#[derive(Serialize, Deserialize)] +struct StreamDataParams<'a> { + #[serde(with = "serde_bytes")] + pub segment: &'a [u8], + pub stream: u32, +} + +#[derive(Serialize, Deserialize)] +struct StreamEndedParams { + pub stream: u32, +} + +#[derive(Serialize)] +pub struct FullRequest, P> { + pub id: Option, + pub method: M, + pub params: P, +} + +#[derive(Deserialize)] +struct RequestParams

{ + pub params: P, +} + +#[derive(Serialize, Deserialize)] +struct SuccessResponse { + pub id: u32, + pub result: T, +} + +#[derive(Serialize, Deserialize)] +struct ErrorResponse { + pub id: u32, + pub error: ResponseError, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ResponseError { + pub code: i32, + pub message: String, +} + +enum Outcome { + Success(Vec), + Error(ResponseError), +} + +pub struct StreamDto { + req_id: u32, + streams: Vec<(u32, DuplexStream)>, +} + +pub enum MaybeSync { + Stream((Option, BoxFuture<'static, Option>>)), + Future(BoxFuture<'static, Option>>), + Sync(Option>), +} diff --git a/cli/src/self_update.rs b/cli/src/self_update.rs new file mode 100644 index 0000000000..2b749ed888 --- /dev/null +++ b/cli/src/self_update.rs @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{fs, path::Path, process::Command}; +use tempfile::tempdir; + +use crate::{ + constants::{VSCODE_CLI_COMMIT, VSCODE_CLI_QUALITY}, + options::Quality, + update_service::{unzip_downloaded_release, Platform, Release, TargetKind, UpdateService}, + util::{ + errors::{wrap, AnyError, CorruptDownload, UpdatesNotConfigured}, + http, + io::{ReportCopyProgress, SilentCopyProgress}, + }, +}; + +pub struct SelfUpdate<'a> { + commit: &'static str, + quality: Quality, + platform: Platform, + update_service: &'a UpdateService, +} + +impl<'a> SelfUpdate<'a> { + pub fn new(update_service: &'a UpdateService) -> Result { + let commit = VSCODE_CLI_COMMIT + .ok_or_else(|| UpdatesNotConfigured("unknown build commit".to_string()))?; + + let quality = VSCODE_CLI_QUALITY + .ok_or_else(|| UpdatesNotConfigured("no configured quality".to_string())) + .and_then(|q| Quality::try_from(q).map_err(UpdatesNotConfigured))?; + + let platform = Platform::env_default().ok_or_else(|| { + UpdatesNotConfigured("Unknown platform, please report this error".to_string()) + })?; + + Ok(Self { + commit, + quality, + platform, + update_service, + }) + } + + /// Gets the current release + pub async fn get_current_release(&self) -> Result { + self.update_service + .get_latest_commit(self.platform, TargetKind::Cli, self.quality) + .await + } + + /// Gets whether the given release is what this CLI is built against + pub fn is_up_to_date_with(&self, release: &Release) -> bool { + release.commit == self.commit + } + + /// Updates the CLI to the given release. + pub async fn do_update( + &self, + release: &Release, + progress: impl ReportCopyProgress, + ) -> Result<(), AnyError> { + // 1. Download the archive into a temporary directory + let tempdir = tempdir().map_err(|e| wrap(e, "Failed to create temp dir"))?; + let stream = self.update_service.get_download_stream(release).await?; + let archive_path = tempdir.path().join(stream.url_path_basename().unwrap()); + http::download_into_file(&archive_path, progress, stream).await?; + + // 2. Unzip the archive and get the binary + let target_path = + std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?; + let staging_path = target_path.with_extension(".update"); + let archive_contents_path = tempdir.path().join("content"); + // unzipping the single binary is pretty small and fast--don't bother with passing progress + unzip_downloaded_release(&archive_path, &archive_contents_path, SilentCopyProgress())?; + copy_updated_cli_to_path(&archive_contents_path, &staging_path)?; + + // 3. Copy file metadata, make sure the new binary is executable\ + copy_file_metadata(&target_path, &staging_path) + .map_err(|e| wrap(e, "failed to set file permissions"))?; + validate_cli_is_good(&staging_path)?; + + // Try to rename the old CLI to the tempdir, where it can get cleaned up by the + // OS later. However, this can fail if the tempdir is on a different drive + // than the installation dir. In this case just rename it to ".old". + if fs::rename(&target_path, tempdir.path().join("old-code-cli")).is_err() { + fs::rename(&target_path, target_path.with_extension(".old")) + .map_err(|e| wrap(e, "failed to rename old CLI"))?; + } + + fs::rename(&staging_path, &target_path) + .map_err(|e| wrap(e, "failed to rename newly installed CLI"))?; + + Ok(()) + } +} + +fn validate_cli_is_good(exe_path: &Path) -> Result<(), AnyError> { + let o = Command::new(exe_path) + .args(["--version"]) + .output() + .map_err(|e| CorruptDownload(format!("could not execute new binary, aborting: {}", e)))?; + + if !o.status.success() { + let msg = format!( + "could not execute new binary, aborting. Stdout:\n\n{}\n\nStderr:\n\n{}", + String::from_utf8_lossy(&o.stdout), + String::from_utf8_lossy(&o.stderr), + ); + + return Err(CorruptDownload(msg).into()); + } + + Ok(()) +} + +fn copy_updated_cli_to_path(unzipped_content: &Path, staging_path: &Path) -> Result<(), AnyError> { + let unzipped_files = fs::read_dir(unzipped_content) + .map_err(|e| wrap(e, "could not read update contents"))? + .collect::>(); + if unzipped_files.len() != 1 { + let msg = format!( + "expected exactly one file in update, got {}", + unzipped_files.len() + ); + return Err(CorruptDownload(msg).into()); + } + + let archive_file = unzipped_files[0] + .as_ref() + .map_err(|e| wrap(e, "error listing update files"))?; + fs::copy(archive_file.path(), staging_path) + .map_err(|e| wrap(e, "error copying to staging file"))?; + Ok(()) +} + +#[cfg(target_os = "windows")] +fn copy_file_metadata(from: &Path, to: &Path) -> Result<(), std::io::Error> { + let permissions = from.metadata()?.permissions(); + fs::set_permissions(to, permissions)?; + Ok(()) +} + +#[cfg(not(target_os = "windows"))] +fn copy_file_metadata(from: &Path, to: &Path) -> Result<(), std::io::Error> { + use std::os::unix::ffi::OsStrExt; + use std::os::unix::fs::MetadataExt; + + let metadata = from.metadata()?; + fs::set_permissions(to, metadata.permissions())?; + + // based on coreutils' chown https://github.com/uutils/coreutils/blob/72b4629916abe0852ad27286f4e307fbca546b6e/src/chown/chown.rs#L266-L281 + let s = std::ffi::CString::new(to.as_os_str().as_bytes()).unwrap(); + let ret = unsafe { libc::chown(s.as_ptr(), metadata.uid(), metadata.gid()) }; + if ret != 0 { + return Err(std::io::Error::last_os_error()); + } + + Ok(()) +} diff --git a/cli/src/singleton.rs b/cli/src/singleton.rs new file mode 100644 index 0000000000..daf2af43d9 --- /dev/null +++ b/cli/src/singleton.rs @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use serde::{Deserialize, Serialize}; +use std::{ + fs::{File, OpenOptions}, + io::{Seek, SeekFrom, Write}, + path::{Path, PathBuf}, + time::Duration, +}; +use sysinfo::{Pid, PidExt}; + +use crate::{ + async_pipe::{ + get_socket_name, get_socket_rw_stream, listen_socket_rw_stream, AsyncPipe, + AsyncPipeListener, + }, + util::{ + errors::CodeError, + file_lock::{FileLock, Lock, PREFIX_LOCKED_BYTES}, + machine::wait_until_process_exits, + }, +}; + +pub struct SingletonServer { + server: AsyncPipeListener, + _lock: FileLock, +} + +impl SingletonServer { + pub async fn accept(&mut self) -> Result { + self.server.accept().await + } +} + +pub enum SingletonConnection { + /// This instance got the singleton lock. It started listening on a socket + /// and has the read/write pair. If this gets dropped, the lock is released. + Singleton(SingletonServer), + /// Another instance is a singleton, and this client connected to it. + Client(AsyncPipe), +} + +/// Contents of the lock file; the listening socket ID and process ID +/// doing the listening. +#[derive(Deserialize, Serialize)] +struct LockFileMatter { + socket_path: String, + pid: u32, +} + +/// Tries to acquire the singleton homed at the given lock file, either starting +/// a new singleton if it doesn't exist, or connecting otherwise. +pub async fn acquire_singleton(lock_file: PathBuf) -> Result { + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&lock_file) + .map_err(CodeError::SingletonLockfileOpenFailed)?; + + match FileLock::acquire(file) { + Ok(Lock::AlreadyLocked(mut file)) => connect_as_client_with_file(&mut file) + .await + .map(SingletonConnection::Client), + Ok(Lock::Acquired(lock)) => start_singleton_server(lock) + .await + .map(SingletonConnection::Singleton), + Err(e) => Err(e), + } +} + +/// Tries to connect to the singleton homed at the given file as a client. +pub async fn connect_as_client(lock_file: &Path) -> Result { + let mut file = OpenOptions::new() + .read(true) + .open(lock_file) + .map_err(CodeError::SingletonLockfileOpenFailed)?; + + connect_as_client_with_file(&mut file).await +} + +async fn start_singleton_server(mut lock: FileLock) -> Result { + let socket_path = get_socket_name(); + + let mut vec = Vec::with_capacity(128); + let _ = vec.write(&[0; PREFIX_LOCKED_BYTES]); + let _ = rmp_serde::encode::write( + &mut vec, + &LockFileMatter { + socket_path: socket_path.to_string_lossy().to_string(), + pid: std::process::id(), + }, + ); + + lock.file_mut() + .write_all(&vec) + .map_err(CodeError::SingletonLockfileOpenFailed)?; + + let server = listen_socket_rw_stream(&socket_path).await?; + Ok(SingletonServer { + server, + _lock: lock, + }) +} + +const MAX_CLIENT_ATTEMPTS: i32 = 10; + +async fn connect_as_client_with_file(mut file: &mut File) -> Result { + // retry, since someone else could get a lock and we could read it before + // the JSON info was finished writing out + let mut attempt = 0; + loop { + let _ = file.seek(SeekFrom::Start(PREFIX_LOCKED_BYTES as u64)); + let r = match rmp_serde::from_read::<_, LockFileMatter>(&mut file) { + Ok(prev) => { + let socket_path = PathBuf::from(prev.socket_path); + + tokio::select! { + p = retry_get_socket_rw_stream(&socket_path, 5, Duration::from_millis(500)) => p, + _ = wait_until_process_exits(Pid::from_u32(prev.pid), 500) => return Err(CodeError::SingletonLockedProcessExited(prev.pid)), + } + } + Err(e) => Err(CodeError::SingletonLockfileReadFailed(e)), + }; + + if r.is_ok() || attempt == MAX_CLIENT_ATTEMPTS { + return r; + } + + attempt += 1; + tokio::time::sleep(Duration::from_millis(500)).await; + } +} + +async fn retry_get_socket_rw_stream( + path: &Path, + max_tries: usize, + interval: Duration, +) -> Result { + for i in 0.. { + match get_socket_rw_stream(path).await { + Ok(s) => return Ok(s), + Err(e) if i == max_tries => return Err(e), + Err(_) => tokio::time::sleep(interval).await, + } + } + + unreachable!() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_acquires_singleton() { + let dir = tempfile::tempdir().expect("expected to make temp dir"); + let s = acquire_singleton(dir.path().join("lock")) + .await + .expect("expected to acquire"); + + match s { + SingletonConnection::Singleton(_) => {} + _ => panic!("expected to be singleton"), + } + } + + #[tokio::test] + async fn test_acquires_client() { + let dir = tempfile::tempdir().expect("expected to make temp dir"); + let lockfile = dir.path().join("lock"); + let s1 = acquire_singleton(lockfile.clone()) + .await + .expect("expected to acquire1"); + match s1 { + SingletonConnection::Singleton(mut l) => tokio::spawn(async move { + l.accept().await.expect("expected to accept"); + }), + _ => panic!("expected to be singleton"), + }; + + let s2 = acquire_singleton(lockfile) + .await + .expect("expected to acquire2"); + match s2 { + SingletonConnection::Client(_) => {} + _ => panic!("expected to be client"), + } + } +} diff --git a/cli/src/state.rs b/cli/src/state.rs new file mode 100644 index 0000000000..4fc10447b7 --- /dev/null +++ b/cli/src/state.rs @@ -0,0 +1,207 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +extern crate dirs; + +use std::{ + fs::{create_dir_all, read_to_string, remove_dir_all, write}, + path::{Path, PathBuf}, + sync::{Arc, Mutex}, +}; + +use serde::{de::DeserializeOwned, Serialize}; + +use crate::{ + constants::{DEFAULT_DATA_PARENT_DIR, VSCODE_CLI_QUALITY}, + download_cache::DownloadCache, + util::errors::{wrap, AnyError, NoHomeForLauncherError, WrappedError}, +}; + +const HOME_DIR_ALTS: [&str; 2] = ["$HOME", "~"]; + +#[derive(Clone)] +pub struct LauncherPaths { + pub server_cache: DownloadCache, + pub cli_cache: DownloadCache, + root: PathBuf, +} + +struct PersistedStateContainer +where + T: Clone + Serialize + DeserializeOwned + Default, +{ + path: PathBuf, + state: Option, +} + +impl PersistedStateContainer +where + T: Clone + Serialize + DeserializeOwned + Default, +{ + fn load_or_get(&mut self) -> T { + if let Some(state) = &self.state { + return state.clone(); + } + + let state = if let Ok(s) = read_to_string(&self.path) { + serde_json::from_str::(&s).unwrap_or_default() + } else { + T::default() + }; + + self.state = Some(state.clone()); + state + } + + fn save(&mut self, state: T) -> Result<(), WrappedError> { + let s = serde_json::to_string(&state).unwrap(); + self.state = Some(state); + write(&self.path, s).map_err(|e| { + wrap( + e, + format!("error saving launcher state into {}", self.path.display()), + ) + }) + } +} + +/// Container that holds some state value that is persisted to disk. +#[derive(Clone)] +pub struct PersistedState +where + T: Clone + Serialize + DeserializeOwned + Default, +{ + container: Arc>>, +} + +impl PersistedState +where + T: Clone + Serialize + DeserializeOwned + Default, +{ + /// Creates a new state container that persists to the given path. + pub fn new(path: PathBuf) -> PersistedState { + PersistedState { + container: Arc::new(Mutex::new(PersistedStateContainer { path, state: None })), + } + } + + /// Loads persisted state. + pub fn load(&self) -> T { + self.container.lock().unwrap().load_or_get() + } + + /// Saves persisted state. + pub fn save(&self, state: T) -> Result<(), WrappedError> { + self.container.lock().unwrap().save(state) + } + + /// Mutates persisted state. + pub fn update(&self, mutator: impl FnOnce(&mut T) -> R) -> Result { + let mut container = self.container.lock().unwrap(); + let mut state = container.load_or_get(); + let r = mutator(&mut state); + container.save(state).map(|_| r) + } +} + +impl LauncherPaths { + /// todo@conno4312: temporary migration from the old CLI data directory + pub fn migrate(root: Option) -> Result { + if root.is_some() { + return Self::new(root); + } + + let home_dir = match dirs::home_dir() { + None => return Self::new(root), + Some(d) => d, + }; + + let old_dir = home_dir.join(".vscode-cli"); + let mut new_dir = home_dir; + new_dir.push(DEFAULT_DATA_PARENT_DIR); + new_dir.push("cli"); + if !old_dir.exists() || new_dir.exists() { + return Self::new_for_path(new_dir); + } + + if let Err(e) = std::fs::rename(&old_dir, &new_dir) { + // no logger exists at this point in the lifecycle, so just log to stderr + eprintln!( + "Failed to migrate old CLI data directory, will create a new one ({})", + e + ); + } + + Self::new_for_path(new_dir) + } + + pub fn new(root: Option) -> Result { + let root = root.unwrap_or_else(|| format!("~/{}/cli", DEFAULT_DATA_PARENT_DIR)); + let mut replaced = root.to_owned(); + for token in HOME_DIR_ALTS { + if root.contains(token) { + if let Some(home) = dirs::home_dir() { + replaced = root.replace(token, &home.to_string_lossy()) + } else { + return Err(AnyError::from(NoHomeForLauncherError())); + } + } + } + + Self::new_for_path(PathBuf::from(replaced)) + } + + fn new_for_path(root: PathBuf) -> Result { + if !root.exists() { + create_dir_all(&root) + .map_err(|e| wrap(e, format!("error creating directory {}", root.display())))?; + } + + Ok(LauncherPaths::new_without_replacements(root)) + } + + pub fn new_without_replacements(root: PathBuf) -> LauncherPaths { + // cleanup folders that existed before the new LRU strategy: + let _ = std::fs::remove_dir_all(root.join("server-insiders")); + let _ = std::fs::remove_dir_all(root.join("server-stable")); + + LauncherPaths { + server_cache: DownloadCache::new(root.join("servers")), + cli_cache: DownloadCache::new(root.join("cli")), + root, + } + } + + /// Root directory for the server launcher + pub fn root(&self) -> &Path { + &self.root + } + + /// Lockfile for the running tunnel + pub fn tunnel_lockfile(&self) -> PathBuf { + self.root.join(format!( + "tunnel-{}.lock", + VSCODE_CLI_QUALITY.unwrap_or("oss") + )) + } + + /// Suggested path for tunnel service logs, when using file logs + pub fn service_log_file(&self) -> PathBuf { + self.root.join("tunnel-service.log") + } + + /// Removes the launcher data directory. + pub fn remove(&self) -> Result<(), WrappedError> { + remove_dir_all(&self.root).map_err(|e| { + wrap( + e, + format!( + "error removing launcher data directory {}", + self.root.display() + ), + ) + }) + } +} diff --git a/cli/src/tunnels.rs b/cli/src/tunnels.rs new file mode 100644 index 0000000000..0eadf3bf7b --- /dev/null +++ b/cli/src/tunnels.rs @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +pub mod code_server; +pub mod dev_tunnels; +pub mod legal; +pub mod paths; +pub mod protocol; +pub mod shutdown_signal; +pub mod singleton_client; +pub mod singleton_server; + +mod challenge; +mod control_server; +mod nosleep; +#[cfg(target_os = "linux")] +mod nosleep_linux; +#[cfg(target_os = "macos")] +mod nosleep_macos; +#[cfg(target_os = "windows")] +mod nosleep_windows; +mod port_forwarder; +mod server_bridge; +mod server_multiplexer; +mod service; +#[cfg(target_os = "linux")] +mod service_linux; +#[cfg(target_os = "macos")] +mod service_macos; +#[cfg(target_os = "windows")] +mod service_windows; +mod socket_signal; + +pub use control_server::{serve, serve_stream, Next, ServeStreamParams}; +pub use nosleep::SleepInhibitor; +pub use service::{ + create_service_manager, ServiceContainer, ServiceManager, SERVICE_LOG_FILE_NAME, +}; diff --git a/cli/src/tunnels/challenge.rs b/cli/src/tunnels/challenge.rs new file mode 100644 index 0000000000..c9d089cead --- /dev/null +++ b/cli/src/tunnels/challenge.rs @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +#[cfg(not(feature = "vsda"))] +pub fn create_challenge() -> String { + use rand::distributions::{Alphanumeric, DistString}; + Alphanumeric.sample_string(&mut rand::thread_rng(), 16) +} + +#[cfg(not(feature = "vsda"))] +pub fn sign_challenge(challenge: &str) -> String { + use sha2::{Digest, Sha256}; + let mut hash = Sha256::new(); + hash.update(challenge.as_bytes()); + let result = hash.finalize(); + base64::encode_config(result, base64::URL_SAFE_NO_PAD) +} + +#[cfg(not(feature = "vsda"))] +pub fn verify_challenge(challenge: &str, response: &str) -> bool { + sign_challenge(challenge) == response +} + +#[cfg(feature = "vsda")] +pub fn create_challenge() -> String { + use rand::distributions::{Alphanumeric, DistString}; + let str = Alphanumeric.sample_string(&mut rand::thread_rng(), 16); + vsda::create_new_message(&str) +} + +#[cfg(feature = "vsda")] +pub fn sign_challenge(challenge: &str) -> String { + vsda::sign(challenge) +} + +#[cfg(feature = "vsda")] +pub fn verify_challenge(challenge: &str, response: &str) -> bool { + vsda::validate(challenge, response) +} diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs new file mode 100644 index 0000000000..66fa4110f4 --- /dev/null +++ b/cli/src/tunnels/code_server.rs @@ -0,0 +1,799 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use super::paths::{InstalledServer, ServerPaths}; +use crate::async_pipe::get_socket_name; +use crate::constants::{ + APPLICATION_NAME, EDITOR_WEB_URL, QUALITYLESS_PRODUCT_NAME, QUALITYLESS_SERVER_NAME, +}; +use crate::download_cache::DownloadCache; +use crate::options::{Quality, TelemetryLevel}; +use crate::state::LauncherPaths; +use crate::tunnels::paths::{get_server_folder_name, SERVER_FOLDER_NAME}; +use crate::update_service::{ + unzip_downloaded_release, Platform, Release, TargetKind, UpdateService, +}; +use crate::util::command::{capture_command, kill_tree}; +use crate::util::errors::{wrap, AnyError, CodeError, ExtensionInstallFailed, WrappedError}; +use crate::util::http::{self, BoxedHttp}; +use crate::util::io::SilentCopyProgress; +use crate::util::machine::process_exists; +use crate::{debug, info, log, spanf, trace, warning}; +use lazy_static::lazy_static; +use opentelemetry::KeyValue; +use regex::Regex; +use serde::Deserialize; +use std::fs; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::time::Duration; +use tokio::fs::remove_file; +use tokio::io::{AsyncBufReadExt, BufReader}; +use tokio::process::{Child, Command}; +use tokio::sync::oneshot::Receiver; +use tokio::time::{interval, timeout}; + +lazy_static! { + static ref LISTENING_PORT_RE: Regex = + Regex::new(r"Extension host agent listening on (.+)").unwrap(); + static ref WEB_UI_RE: Regex = Regex::new(r"Web UI available at (.+)").unwrap(); +} + +#[derive(Clone, Debug, Default)] +pub struct CodeServerArgs { + pub host: Option, + pub port: Option, + pub socket_path: Option, + + // common argument + pub telemetry_level: Option, + pub log: Option, + pub accept_server_license_terms: bool, + pub verbose: bool, + // extension management + pub install_extensions: Vec, + pub uninstall_extensions: Vec, + pub list_extensions: bool, + pub show_versions: bool, + pub category: Option, + pub pre_release: bool, + pub force: bool, + pub start_server: bool, + // connection tokens + pub connection_token: Option, + pub connection_token_file: Option, + pub without_connection_token: bool, +} + +impl CodeServerArgs { + pub fn log_level(&self) -> log::Level { + if self.verbose { + log::Level::Trace + } else { + self.log.unwrap_or(log::Level::Info) + } + } + + pub fn telemetry_disabled(&self) -> bool { + self.telemetry_level == Some(TelemetryLevel::Off) + } + + pub fn command_arguments(&self) -> Vec { + let mut args = Vec::new(); + if let Some(i) = &self.socket_path { + args.push(format!("--socket-path={}", i)); + } else { + if let Some(i) = &self.host { + args.push(format!("--host={}", i)); + } + if let Some(i) = &self.port { + args.push(format!("--port={}", i)); + } + } + + if let Some(i) = &self.connection_token { + args.push(format!("--connection-token={}", i)); + } + if let Some(i) = &self.connection_token_file { + args.push(format!("--connection-token-file={}", i)); + } + if self.without_connection_token { + args.push(String::from("--without-connection-token")); + } + if self.accept_server_license_terms { + args.push(String::from("--accept-server-license-terms")); + } + if let Some(i) = self.telemetry_level { + args.push(format!("--telemetry-level={}", i)); + } + if let Some(i) = self.log { + args.push(format!("--log={}", i)); + } + + for extension in &self.install_extensions { + args.push(format!("--install-extension={}", extension)); + } + if !&self.install_extensions.is_empty() { + if self.pre_release { + args.push(String::from("--pre-release")); + } + if self.force { + args.push(String::from("--force")); + } + } + for extension in &self.uninstall_extensions { + args.push(format!("--uninstall-extension={}", extension)); + } + if self.list_extensions { + args.push(String::from("--list-extensions")); + if self.show_versions { + args.push(String::from("--show-versions")); + } + if let Some(i) = &self.category { + args.push(format!("--category={}", i)); + } + } + if self.start_server { + args.push(String::from("--start-server")); + } + args + } +} + +/// Base server params that can be `resolve()`d to a `ResolvedServerParams`. +/// Doing so fetches additional information like a commit ID if previously +/// unspecified. +pub struct ServerParamsRaw { + pub commit_id: Option, + pub quality: Quality, + pub code_server_args: CodeServerArgs, + pub headless: bool, + pub platform: Platform, +} + +/// Server params that can be used to start a VS Code server. +pub struct ResolvedServerParams { + pub release: Release, + pub code_server_args: CodeServerArgs, +} + +impl ResolvedServerParams { + fn as_installed_server(&self) -> InstalledServer { + InstalledServer { + commit: self.release.commit.clone(), + quality: self.release.quality, + headless: self.release.target == TargetKind::Server, + } + } +} + +impl ServerParamsRaw { + pub async fn resolve( + self, + log: &log::Logger, + http: BoxedHttp, + ) -> Result { + Ok(ResolvedServerParams { + release: self.get_or_fetch_commit_id(log, http).await?, + code_server_args: self.code_server_args, + }) + } + + async fn get_or_fetch_commit_id( + &self, + log: &log::Logger, + http: BoxedHttp, + ) -> Result { + let target = match self.headless { + true => TargetKind::Server, + false => TargetKind::Web, + }; + + if let Some(c) = &self.commit_id { + return Ok(Release { + commit: c.clone(), + quality: self.quality, + target, + name: String::new(), + platform: self.platform, + }); + } + + UpdateService::new(log.clone(), http) + .get_latest_commit(self.platform, target, self.quality) + .await + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(dead_code)] +struct UpdateServerVersion { + pub name: String, + pub version: String, + pub product_version: String, + pub timestamp: i64, +} + +/// Code server listening on a port address. +#[derive(Clone)] +pub struct SocketCodeServer { + pub commit_id: String, + pub socket: PathBuf, + pub origin: Arc, +} + +/// Code server listening on a socket address. +#[derive(Clone)] +pub struct PortCodeServer { + pub commit_id: String, + pub port: u16, + pub origin: Arc, +} + +/// A server listening on any address/location. +pub enum AnyCodeServer { + Socket(SocketCodeServer), + Port(PortCodeServer), +} + +pub enum CodeServerOrigin { + /// A new code server, that opens the barrier when it exits. + New(Box), + /// An existing code server with a PID. + Existing(u32), +} + +impl CodeServerOrigin { + pub async fn wait_for_exit(&mut self) { + match self { + CodeServerOrigin::New(child) => { + child.wait().await.ok(); + } + CodeServerOrigin::Existing(pid) => { + let mut interval = interval(Duration::from_secs(30)); + while process_exists(*pid) { + interval.tick().await; + } + } + } + } + + pub async fn kill(&mut self) { + match self { + CodeServerOrigin::New(child) => { + child.kill().await.ok(); + } + CodeServerOrigin::Existing(pid) => { + kill_tree(*pid).await.ok(); + } + } + } +} + +/// Ensures the given list of extensions are installed on the running server. +async fn do_extension_install_on_running_server( + start_script_path: &Path, + extensions: &[String], + log: &log::Logger, +) -> Result<(), AnyError> { + if extensions.is_empty() { + return Ok(()); + } + + debug!(log, "Installing extensions..."); + let command = format!( + "{} {}", + start_script_path.display(), + extensions + .iter() + .map(|s| get_extensions_flag(s)) + .collect::>() + .join(" ") + ); + + let result = capture_command("bash", &["-c", &command]).await?; + if !result.status.success() { + Err(AnyError::from(ExtensionInstallFailed( + String::from_utf8_lossy(&result.stderr).to_string(), + ))) + } else { + Ok(()) + } +} + +pub struct ServerBuilder<'a> { + logger: &'a log::Logger, + server_params: &'a ResolvedServerParams, + launcher_paths: &'a LauncherPaths, + server_paths: ServerPaths, + http: BoxedHttp, +} + +impl<'a> ServerBuilder<'a> { + pub fn new( + logger: &'a log::Logger, + server_params: &'a ResolvedServerParams, + launcher_paths: &'a LauncherPaths, + http: BoxedHttp, + ) -> Self { + Self { + logger, + server_params, + launcher_paths, + server_paths: server_params + .as_installed_server() + .server_paths(launcher_paths), + http, + } + } + + /// Gets any already-running server from this directory. + pub async fn get_running(&self) -> Result, AnyError> { + info!( + self.logger, + "Checking {} and {} for a running server...", + self.server_paths.logfile.display(), + self.server_paths.pidfile.display() + ); + + let pid = match self.server_paths.get_running_pid() { + Some(pid) => pid, + None => return Ok(None), + }; + info!(self.logger, "Found running server (pid={})", pid); + if !Path::new(&self.server_paths.logfile).exists() { + warning!(self.logger, "{} Server is running but its logfile is missing. Don't delete the {} Server manually, run the command '{} prune'.", QUALITYLESS_PRODUCT_NAME, QUALITYLESS_PRODUCT_NAME, APPLICATION_NAME); + return Ok(None); + } + + do_extension_install_on_running_server( + &self.server_paths.executable, + &self.server_params.code_server_args.install_extensions, + self.logger, + ) + .await?; + + let origin = Arc::new(CodeServerOrigin::Existing(pid)); + let contents = fs::read_to_string(&self.server_paths.logfile) + .expect("Something went wrong reading log file"); + + if let Some(port) = parse_port_from(&contents) { + Ok(Some(AnyCodeServer::Port(PortCodeServer { + commit_id: self.server_params.release.commit.to_owned(), + port, + origin, + }))) + } else if let Some(socket) = parse_socket_from(&contents) { + Ok(Some(AnyCodeServer::Socket(SocketCodeServer { + commit_id: self.server_params.release.commit.to_owned(), + socket, + origin, + }))) + } else { + Ok(None) + } + } + + /// Ensures the server is set up in the configured directory. + pub async fn setup(&self) -> Result<(), AnyError> { + debug!( + self.logger, + "Installing and setting up {}...", QUALITYLESS_SERVER_NAME + ); + + let update_service = UpdateService::new(self.logger.clone(), self.http.clone()); + let name = get_server_folder_name( + self.server_params.release.quality, + &self.server_params.release.commit, + ); + + self.launcher_paths + .server_cache + .create(name, |target_dir| async move { + let tmpdir = + tempfile::tempdir().map_err(|e| wrap(e, "error creating temp download dir"))?; + + let response = update_service + .get_download_stream(&self.server_params.release) + .await?; + let archive_path = tmpdir.path().join(response.url_path_basename().unwrap()); + + info!( + self.logger, + "Downloading {} server -> {}", + QUALITYLESS_PRODUCT_NAME, + archive_path.display() + ); + + http::download_into_file( + &archive_path, + self.logger.get_download_logger("server download progress:"), + response, + ) + .await?; + + unzip_downloaded_release( + &archive_path, + &target_dir.join(SERVER_FOLDER_NAME), + SilentCopyProgress(), + )?; + + Ok(()) + }) + .await?; + + debug!(self.logger, "Server setup complete"); + + Ok(()) + } + + pub async fn listen_on_port(&self, port: u16) -> Result { + let mut cmd = self.get_base_command(); + cmd.arg("--start-server") + .arg("--enable-remote-auto-shutdown") + .arg(format!("--port={}", port)); + + let child = self.spawn_server_process(cmd)?; + let log_file = self.get_logfile()?; + let plog = self.logger.prefixed(&log::new_code_server_prefix()); + + let (mut origin, listen_rx) = + monitor_server::(child, Some(log_file), plog, false); + + let port = match timeout(Duration::from_secs(8), listen_rx).await { + Err(e) => { + origin.kill().await; + Err(wrap(e, "timed out looking for port")) + } + Ok(Err(e)) => { + origin.kill().await; + Err(wrap(e, "server exited without writing port")) + } + Ok(Ok(p)) => Ok(p), + }?; + + info!(self.logger, "Server started"); + + Ok(PortCodeServer { + commit_id: self.server_params.release.commit.to_owned(), + port, + origin: Arc::new(origin), + }) + } + + pub async fn listen_on_default_socket(&self) -> Result { + let requested_file = get_socket_name(); + self.listen_on_socket(&requested_file).await + } + + pub async fn listen_on_socket(&self, socket: &Path) -> Result { + Ok(spanf!( + self.logger, + self.logger.span("server.start").with_attributes(vec! { + KeyValue::new("commit_id", self.server_params.release.commit.to_string()), + KeyValue::new("quality", format!("{}", self.server_params.release.quality)), + }), + self._listen_on_socket(socket) + )?) + } + + async fn _listen_on_socket(&self, socket: &Path) -> Result { + remove_file(&socket).await.ok(); // ignore any error if it doesn't exist + + let mut cmd = self.get_base_command(); + cmd.arg("--start-server") + .arg("--enable-remote-auto-shutdown") + .arg(format!("--socket-path={}", socket.display())); + + let child = self.spawn_server_process(cmd)?; + let log_file = self.get_logfile()?; + let plog = self.logger.prefixed(&log::new_code_server_prefix()); + + let (mut origin, listen_rx) = + monitor_server::(child, Some(log_file), plog, false); + + let socket = match timeout(Duration::from_secs(8), listen_rx).await { + Err(e) => { + origin.kill().await; + Err(wrap(e, "timed out looking for socket")) + } + Ok(Err(e)) => { + origin.kill().await; + Err(wrap(e, "server exited without writing socket")) + } + Ok(Ok(socket)) => Ok(socket), + }?; + + info!(self.logger, "Server started"); + + Ok(SocketCodeServer { + commit_id: self.server_params.release.commit.to_owned(), + socket, + origin: Arc::new(origin), + }) + } + + /// Starts with a given opaque set of args. Does not set up any port or + /// socket, but does return one if present, in the form of a channel. + pub async fn start_opaque_with_args( + &self, + args: &[String], + ) -> Result<(CodeServerOrigin, Receiver), AnyError> + where + M: ServerOutputMatcher, + R: 'static + Send + std::fmt::Debug, + { + let mut cmd = self.get_base_command(); + cmd.args(args); + + let child = self.spawn_server_process(cmd)?; + let plog = self.logger.prefixed(&log::new_code_server_prefix()); + + Ok(monitor_server::(child, None, plog, true)) + } + + fn spawn_server_process(&self, mut cmd: Command) -> Result { + info!(self.logger, "Starting server..."); + + debug!(self.logger, "Starting server with command... {:?}", cmd); + + let child = cmd + .stderr(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .map_err(|e| wrap(e, "error spawning server"))?; + + self.server_paths + .write_pid(child.id().expect("expected server to have pid"))?; + + Ok(child) + } + + fn get_logfile(&self) -> Result { + File::create(&self.server_paths.logfile).map_err(|e| { + wrap( + e, + format!( + "error creating log file {}", + self.server_paths.logfile.display() + ), + ) + }) + } + + fn get_base_command(&self) -> Command { + let mut cmd = Command::new(&self.server_paths.executable); + cmd.stdin(std::process::Stdio::null()) + .args(self.server_params.code_server_args.command_arguments()); + cmd + } +} + +fn monitor_server( + mut child: Child, + log_file: Option, + plog: log::Logger, + write_directly: bool, +) -> (CodeServerOrigin, Receiver) +where + M: ServerOutputMatcher, + R: 'static + Send + std::fmt::Debug, +{ + let stdout = child + .stdout + .take() + .expect("child did not have a handle to stdout"); + + let stderr = child + .stderr + .take() + .expect("child did not have a handle to stdout"); + + let (listen_tx, listen_rx) = tokio::sync::oneshot::channel(); + + // Handle stderr and stdout in a separate task. Initially scan lines looking + // for the listening port. Afterwards, just scan and write out to the file. + tokio::spawn(async move { + let mut stdout_reader = BufReader::new(stdout).lines(); + let mut stderr_reader = BufReader::new(stderr).lines(); + let write_line = |line: &str| -> std::io::Result<()> { + if let Some(mut f) = log_file.as_ref() { + f.write_all(line.as_bytes())?; + f.write_all(&[b'\n'])?; + } + if write_directly { + println!("{}", line); + } else { + trace!(plog, line); + } + Ok(()) + }; + + loop { + let line = tokio::select! { + l = stderr_reader.next_line() => l, + l = stdout_reader.next_line() => l, + }; + + match line { + Err(e) => { + trace!(plog, "error reading from stdout/stderr: {}", e); + return; + } + Ok(None) => break, + Ok(Some(l)) => { + write_line(&l).ok(); + + if let Some(listen_on) = M::match_line(&l) { + trace!(plog, "parsed location: {:?}", listen_on); + listen_tx.send(listen_on).ok(); + break; + } + } + } + } + + loop { + let line = tokio::select! { + l = stderr_reader.next_line() => l, + l = stdout_reader.next_line() => l, + }; + + match line { + Err(e) => { + trace!(plog, "error reading from stdout/stderr: {}", e); + break; + } + Ok(None) => break, + Ok(Some(l)) => { + write_line(&l).ok(); + } + } + } + }); + + let origin = CodeServerOrigin::New(Box::new(child)); + (origin, listen_rx) +} + +fn get_extensions_flag(extension_id: &str) -> String { + format!("--install-extension={}", extension_id) +} + +/// A type that can be used to scan stdout from the VS Code server. Returns +/// some other type that, in turn, is returned from starting the server. +pub trait ServerOutputMatcher +where + R: Send, +{ + fn match_line(line: &str) -> Option; +} + +/// Parses a line like "Extension host agent listening on /tmp/foo.sock" +struct SocketMatcher(); + +impl ServerOutputMatcher for SocketMatcher { + fn match_line(line: &str) -> Option { + parse_socket_from(line) + } +} + +/// Parses a line like "Extension host agent listening on 9000" +pub struct PortMatcher(); + +impl ServerOutputMatcher for PortMatcher { + fn match_line(line: &str) -> Option { + parse_port_from(line) + } +} + +/// Parses a line like "Web UI available at http://localhost:9000/?tkn=..." +pub struct WebUiMatcher(); + +impl ServerOutputMatcher for WebUiMatcher { + fn match_line(line: &str) -> Option { + WEB_UI_RE.captures(line).and_then(|cap| { + cap.get(1) + .and_then(|uri| reqwest::Url::parse(uri.as_str()).ok()) + }) + } +} + +/// Does not do any parsing and just immediately returns an empty result. +pub struct NoOpMatcher(); + +impl ServerOutputMatcher<()> for NoOpMatcher { + fn match_line(_: &str) -> Option<()> { + Some(()) + } +} + +fn parse_socket_from(text: &str) -> Option { + LISTENING_PORT_RE + .captures(text) + .and_then(|cap| cap.get(1).map(|path| PathBuf::from(path.as_str()))) +} + +fn parse_port_from(text: &str) -> Option { + LISTENING_PORT_RE.captures(text).and_then(|cap| { + cap.get(1) + .and_then(|path| path.as_str().parse::().ok()) + }) +} + +pub fn print_listening(log: &log::Logger, tunnel_name: &str) { + debug!( + log, + "{} is listening for incoming connections", QUALITYLESS_SERVER_NAME + ); + + let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("")); + let current_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("")); + + let dir = if home_dir == current_dir { + PathBuf::from("") + } else { + current_dir + }; + + let base_web_url = match EDITOR_WEB_URL { + Some(u) => u, + None => return, + }; + + let mut addr = url::Url::parse(base_web_url).unwrap(); + { + let mut ps = addr.path_segments_mut().unwrap(); + ps.push("tunnel"); + ps.push(tunnel_name); + for segment in &dir { + let as_str = segment.to_string_lossy(); + if !(as_str.len() == 1 && as_str.starts_with(std::path::MAIN_SEPARATOR)) { + ps.push(as_str.as_ref()); + } + } + } + + let message = &format!("\nOpen this link in your browser {}\n", addr); + log.result(message); +} + +pub async fn download_cli_into_cache( + cache: &DownloadCache, + release: &Release, + update_service: &UpdateService, +) -> Result { + let cache_name = format!( + "{}-{}-{}", + release.quality, release.commit, release.platform + ); + let cli_dir = cache + .create(&cache_name, |target_dir| async move { + let tmpdir = + tempfile::tempdir().map_err(|e| wrap(e, "error creating temp download dir"))?; + let response = update_service.get_download_stream(release).await?; + + let name = response.url_path_basename().unwrap(); + let archive_path = tmpdir.path().join(name); + http::download_into_file(&archive_path, SilentCopyProgress(), response).await?; + unzip_downloaded_release(&archive_path, &target_dir, SilentCopyProgress())?; + Ok(()) + }) + .await?; + + let cli = std::fs::read_dir(cli_dir) + .map_err(|_| CodeError::CorruptDownload("could not read cli folder contents"))? + .next(); + + match cli { + Some(Ok(cli)) => Ok(cli.path()), + _ => { + let _ = cache.delete(&cache_name); + Err(CodeError::CorruptDownload("cli directory is empty").into()) + } + } +} diff --git a/cli/src/tunnels/control_server.rs b/cli/src/tunnels/control_server.rs new file mode 100644 index 0000000000..b85446a893 --- /dev/null +++ b/cli/src/tunnels/control_server.rs @@ -0,0 +1,1168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use crate::async_pipe::get_socket_rw_stream; +use crate::constants::{CONTROL_PORT, PRODUCT_NAME_LONG}; +use crate::log; +use crate::msgpack_rpc::{new_msgpack_rpc, start_msgpack_rpc, MsgPackCodec, MsgPackSerializer}; +use crate::rpc::{MaybeSync, RpcBuilder, RpcCaller, RpcDispatcher}; +use crate::self_update::SelfUpdate; +use crate::state::LauncherPaths; +use crate::tunnels::protocol::{HttpRequestParams, METHOD_CHALLENGE_ISSUE}; +use crate::tunnels::socket_signal::CloseReason; +use crate::update_service::{Platform, Release, TargetKind, UpdateService}; +use crate::util::errors::{ + wrap, AnyError, CodeError, MismatchedLaunchModeError, NoAttachedServerError, +}; +use crate::util::http::{ + DelegatedHttpRequest, DelegatedSimpleHttp, FallbackSimpleHttp, ReqwestSimpleHttp, +}; +use crate::util::io::SilentCopyProgress; +use crate::util::is_integrated_cli; +use crate::util::os::os_release; +use crate::util::sync::{new_barrier, Barrier, BarrierOpener}; + +use futures::stream::FuturesUnordered; +use futures::FutureExt; +use opentelemetry::trace::SpanKind; +use opentelemetry::KeyValue; +use std::collections::HashMap; +use std::process::Stdio; +use tokio::pin; +use tokio::process::{ChildStderr, ChildStdin}; +use tokio_util::codec::Decoder; + +use std::sync::atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::Instant; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, BufReader, DuplexStream}; +use tokio::sync::{mpsc, Mutex}; + +use super::challenge::{create_challenge, sign_challenge, verify_challenge}; +use super::code_server::{ + download_cli_into_cache, AnyCodeServer, CodeServerArgs, ServerBuilder, ServerParamsRaw, + SocketCodeServer, +}; +use super::dev_tunnels::ActiveTunnel; +use super::paths::prune_stopped_servers; +use super::port_forwarder::{PortForwarding, PortForwardingProcessor}; +use super::protocol::{ + AcquireCliParams, CallServerHttpParams, CallServerHttpResult, ChallengeIssueResponse, + ChallengeVerifyParams, ClientRequestMethod, EmptyObject, ForwardParams, ForwardResult, + FsStatRequest, FsStatResponse, GetEnvResponse, GetHostnameResponse, HttpBodyParams, + HttpHeadersParams, ServeParams, ServerLog, ServerMessageParams, SpawnParams, SpawnResult, + ToClientRequest, UnforwardParams, UpdateParams, UpdateResult, VersionResponse, + METHOD_CHALLENGE_VERIFY, +}; +use super::server_bridge::ServerBridge; +use super::server_multiplexer::ServerMultiplexer; +use super::shutdown_signal::ShutdownSignal; +use super::socket_signal::{ + ClientMessageDecoder, ServerMessageDestination, ServerMessageSink, SocketSignal, +}; + +type HttpRequestsMap = Arc>>; +type CodeServerCell = Arc>>; + +struct HandlerContext { + /// Log handle for the server + log: log::Logger, + /// Whether the server update during the handler session. + did_update: Arc, + /// Whether authentication is still required on the socket. + auth_state: Arc>, + /// A loopback channel to talk to the socket server task. + socket_tx: mpsc::Sender, + /// Configured launcher paths. + launcher_paths: LauncherPaths, + /// Connected VS Code Server + code_server: CodeServerCell, + /// Potentially many "websocket" connections to client + server_bridges: ServerMultiplexer, + // the cli arguments used to start the code server + code_server_args: CodeServerArgs, + /// port forwarding functionality + port_forwarding: Option, + /// install platform for the VS Code server + platform: Platform, + /// http client to make download/update requests + http: Arc, + /// requests being served by the client + http_requests: HttpRequestsMap, +} + +/// Handler auth state. +enum AuthState { + /// Auth is required, we're waiting for the client to send its challenge. + WaitingForChallenge, + /// A challenge has been issued. Waiting for a verification. + ChallengeIssued(String), + /// Auth is no longer required. + Authenticated, +} + +static MESSAGE_ID_COUNTER: AtomicU32 = AtomicU32::new(0); + +// Gets a next incrementing number that can be used in logs +pub fn next_message_id() -> u32 { + MESSAGE_ID_COUNTER.fetch_add(1, Ordering::SeqCst) +} + +impl HandlerContext { + async fn dispose(&self) { + self.server_bridges.dispose().await; + info!(self.log, "Disposed of connection to running server."); + } +} + +enum ServerSignal { + /// Signalled when the server has been updated and we want to respawn. + /// We'd generally need to stop and then restart the launcher, but the + /// program might be managed by a supervisor like systemd. Instead, we + /// will stop the TCP listener and spawn the launcher again as a subprocess + /// with the same arguments we used. + Respawn, +} + +pub enum Next { + /// Whether the server should be respawned in a new binary (see ServerSignal.Respawn). + Respawn, + /// Whether the tunnel should be restarted + Restart, + /// Whether the process should exit + Exit, +} + +pub struct ServerTermination { + pub next: Next, + pub tunnel: ActiveTunnel, +} + +// Runs the launcher server. Exits on a ctrl+c or when requested by a user. +// Note that client connections may not be closed when this returns; use +// `close_all_clients()` on the ServerTermination to make this happen. +pub async fn serve( + log: &log::Logger, + mut tunnel: ActiveTunnel, + launcher_paths: &LauncherPaths, + code_server_args: &CodeServerArgs, + platform: Platform, + mut shutdown_rx: Barrier, +) -> Result { + let mut port = tunnel.add_port_direct(CONTROL_PORT).await?; + let mut forwarding = PortForwardingProcessor::new(); + let (tx, mut rx) = mpsc::channel::(4); + let (exit_barrier, signal_exit) = new_barrier(); + + loop { + tokio::select! { + Ok(reason) = shutdown_rx.wait() => { + info!(log, "Shutting down: {}", reason); + drop(signal_exit); + return Ok(ServerTermination { + next: match reason { + ShutdownSignal::RpcRestartRequested => Next::Restart, + _ => Next::Exit, + }, + tunnel, + }); + }, + c = rx.recv() => { + if let Some(ServerSignal::Respawn) = c { + drop(signal_exit); + return Ok(ServerTermination { + next: Next::Respawn, + tunnel, + }); + } + }, + Some(w) = forwarding.recv() => { + forwarding.process(w, &mut tunnel).await; + }, + l = port.recv() => { + let socket = match l { + Some(p) => p, + None => { + warning!(log, "ssh tunnel disposed, tearing down"); + return Ok(ServerTermination { + next: Next::Restart, + tunnel, + }); + } + }; + + let own_log = log.prefixed(&log::new_rpc_prefix()); + let own_tx = tx.clone(); + let own_paths = launcher_paths.clone(); + let own_exit = exit_barrier.clone(); + let own_code_server_args = code_server_args.clone(); + let own_forwarding = forwarding.handle(); + + tokio::spawn(async move { + use opentelemetry::trace::{FutureExt, TraceContextExt}; + + let span = own_log.span("server.socket").with_kind(SpanKind::Consumer).start(own_log.tracer()); + let cx = opentelemetry::Context::current_with_span(span); + let serve_at = Instant::now(); + + debug!(own_log, "Serving new connection"); + + let (writehalf, readhalf) = socket.into_split(); + let stats = process_socket(readhalf, writehalf, own_tx, Some(own_forwarding), ServeStreamParams { + log: own_log, + launcher_paths: own_paths, + code_server_args: own_code_server_args, + platform, + exit_barrier: own_exit, + requires_auth: false, + }).with_context(cx.clone()).await; + + cx.span().add_event( + "socket.bandwidth", + vec![ + KeyValue::new("tx", stats.tx as f64), + KeyValue::new("rx", stats.rx as f64), + KeyValue::new("duration_ms", serve_at.elapsed().as_millis() as f64), + ], + ); + cx.span().end(); + }); + } + } + } +} + +pub struct ServeStreamParams { + pub log: log::Logger, + pub launcher_paths: LauncherPaths, + pub code_server_args: CodeServerArgs, + pub platform: Platform, + pub requires_auth: bool, + pub exit_barrier: Barrier, +} + +pub async fn serve_stream( + readhalf: impl AsyncRead + Send + Unpin + 'static, + writehalf: impl AsyncWrite + Unpin, + params: ServeStreamParams, +) -> SocketStats { + // Currently the only server signal is respawn, that doesn't have much meaning + // when serving a stream, so make an ignored channel. + let (server_rx, server_tx) = mpsc::channel(1); + drop(server_tx); + + process_socket(readhalf, writehalf, server_rx, None, params).await +} + +pub struct SocketStats { + rx: usize, + tx: usize, +} + +#[allow(clippy::too_many_arguments)] +fn make_socket_rpc( + log: log::Logger, + socket_tx: mpsc::Sender, + http_delegated: DelegatedSimpleHttp, + launcher_paths: LauncherPaths, + code_server_args: CodeServerArgs, + port_forwarding: Option, + requires_auth: bool, + platform: Platform, +) -> RpcDispatcher { + let http_requests = Arc::new(std::sync::Mutex::new(HashMap::new())); + let server_bridges = ServerMultiplexer::new(); + let mut rpc = RpcBuilder::new(MsgPackSerializer {}).methods(HandlerContext { + did_update: Arc::new(AtomicBool::new(false)), + auth_state: Arc::new(std::sync::Mutex::new(match requires_auth { + true => AuthState::WaitingForChallenge, + false => AuthState::Authenticated, + })), + socket_tx, + log: log.clone(), + launcher_paths, + code_server_args, + code_server: Arc::new(Mutex::new(None)), + server_bridges, + port_forwarding, + platform, + http: Arc::new(FallbackSimpleHttp::new( + ReqwestSimpleHttp::new(), + http_delegated, + )), + http_requests, + }); + + rpc.register_sync("ping", |_: EmptyObject, _| Ok(EmptyObject {})); + rpc.register_sync("gethostname", |_: EmptyObject, _| handle_get_hostname()); + rpc.register_sync("fs_stat", |p: FsStatRequest, c| { + ensure_auth(&c.auth_state)?; + handle_stat(p.path) + }); + rpc.register_sync("get_env", |_: EmptyObject, c| { + ensure_auth(&c.auth_state)?; + handle_get_env() + }); + rpc.register_sync(METHOD_CHALLENGE_ISSUE, |_: EmptyObject, c| { + handle_challenge_issue(&c.auth_state) + }); + rpc.register_sync(METHOD_CHALLENGE_VERIFY, |p: ChallengeVerifyParams, c| { + handle_challenge_verify(p.response, &c.auth_state) + }); + rpc.register_async("serve", move |params: ServeParams, c| async move { + ensure_auth(&c.auth_state)?; + handle_serve(c, params).await + }); + rpc.register_async("update", |p: UpdateParams, c| async move { + handle_update(&c.http, &c.log, &c.did_update, &p).await + }); + rpc.register_sync("servermsg", |m: ServerMessageParams, c| { + if let Err(e) = handle_server_message(&c.log, &c.server_bridges, m) { + warning!(c.log, "error handling call: {:?}", e); + } + Ok(EmptyObject {}) + }); + rpc.register_sync("prune", |_: EmptyObject, c| handle_prune(&c.launcher_paths)); + rpc.register_async("callserverhttp", |p: CallServerHttpParams, c| async move { + let code_server = c.code_server.lock().await.clone(); + handle_call_server_http(code_server, p).await + }); + rpc.register_async("forward", |p: ForwardParams, c| async move { + ensure_auth(&c.auth_state)?; + handle_forward(&c.log, &c.port_forwarding, p).await + }); + rpc.register_async("unforward", |p: UnforwardParams, c| async move { + ensure_auth(&c.auth_state)?; + handle_unforward(&c.log, &c.port_forwarding, p).await + }); + rpc.register_async("acquire_cli", |p: AcquireCliParams, c| async move { + ensure_auth(&c.auth_state)?; + handle_acquire_cli(&c.launcher_paths, &c.http, &c.log, p).await + }); + rpc.register_duplex("spawn", 3, |mut streams, p: SpawnParams, c| async move { + ensure_auth(&c.auth_state)?; + handle_spawn( + &c.log, + p, + Some(streams.remove(0)), + Some(streams.remove(0)), + Some(streams.remove(0)), + ) + .await + }); + rpc.register_duplex( + "spawn_cli", + 3, + |mut streams, p: SpawnParams, c| async move { + ensure_auth(&c.auth_state)?; + handle_spawn_cli( + &c.log, + p, + streams.remove(0), + streams.remove(0), + streams.remove(0), + ) + .await + }, + ); + rpc.register_sync("httpheaders", |p: HttpHeadersParams, c| { + if let Some(req) = c.http_requests.lock().unwrap().get(&p.req_id) { + req.initial_response(p.status_code, p.headers); + } + Ok(EmptyObject {}) + }); + rpc.register_sync("httpbody", move |p: HttpBodyParams, c| { + let mut reqs = c.http_requests.lock().unwrap(); + if let Some(req) = reqs.get(&p.req_id) { + if !p.segment.is_empty() { + req.body(p.segment); + } + if p.complete { + reqs.remove(&p.req_id); + } + } + Ok(EmptyObject {}) + }); + rpc.register_sync( + "version", + |_: EmptyObject, _| Ok(VersionResponse::default()), + ); + + rpc.build(log) +} + +fn ensure_auth(is_authed: &Arc>) -> Result<(), AnyError> { + if let AuthState::Authenticated = &*is_authed.lock().unwrap() { + Ok(()) + } else { + Err(CodeError::ServerAuthRequired.into()) + } +} + +#[allow(clippy::too_many_arguments)] // necessary here +async fn process_socket( + readhalf: impl AsyncRead + Send + Unpin + 'static, + mut writehalf: impl AsyncWrite + Unpin, + server_tx: mpsc::Sender, + port_forwarding: Option, + params: ServeStreamParams, +) -> SocketStats { + let ServeStreamParams { + mut exit_barrier, + log, + launcher_paths, + code_server_args, + platform, + requires_auth, + } = params; + + let (http_delegated, mut http_rx) = DelegatedSimpleHttp::new(log.clone()); + let (socket_tx, mut socket_rx) = mpsc::channel(4); + let rx_counter = Arc::new(AtomicUsize::new(0)); + let http_requests = Arc::new(std::sync::Mutex::new(HashMap::new())); + + let rpc = make_socket_rpc( + log.clone(), + socket_tx.clone(), + http_delegated, + launcher_paths, + code_server_args, + port_forwarding, + requires_auth, + platform, + ); + + { + let log = log.clone(); + let rx_counter = rx_counter.clone(); + let socket_tx = socket_tx.clone(); + let exit_barrier = exit_barrier.clone(); + tokio::spawn(async move { + if !requires_auth { + send_version(&socket_tx).await; + } + + if let Err(e) = + handle_socket_read(&log, readhalf, exit_barrier, &socket_tx, rx_counter, &rpc).await + { + debug!(log, "closing socket reader: {}", e); + socket_tx + .send(SocketSignal::CloseWith(CloseReason(format!("{}", e)))) + .await + .ok(); + } + + let ctx = rpc.context(); + + // The connection is now closed, asked to respawn if needed + if ctx.did_update.load(Ordering::SeqCst) { + server_tx.send(ServerSignal::Respawn).await.ok(); + } + + ctx.dispose().await; + + let _ = socket_tx + .send(SocketSignal::CloseWith(CloseReason("eof".to_string()))) + .await; + }); + } + + let mut tx_counter = 0; + + loop { + tokio::select! { + _ = exit_barrier.wait() => { + writehalf.shutdown().await.ok(); + break; + }, + Some(r) = http_rx.recv() => { + let id = next_message_id(); + let serialized = rmp_serde::to_vec_named(&ToClientRequest { + id: None, + params: ClientRequestMethod::makehttpreq(HttpRequestParams { + url: &r.url, + method: r.method, + req_id: id, + }), + }) + .unwrap(); + http_requests.lock().unwrap().insert(id, r); + + tx_counter += serialized.len(); + if let Err(e) = writehalf.write_all(&serialized).await { + debug!(log, "Closing connection: {}", e); + break; + } + } + recv = socket_rx.recv() => match recv { + None => break, + Some(message) => match message { + SocketSignal::Send(bytes) => { + tx_counter += bytes.len(); + if let Err(e) = writehalf.write_all(&bytes).await { + debug!(log, "Closing connection: {}", e); + break; + } + } + SocketSignal::CloseWith(reason) => { + debug!(log, "Closing connection: {}", reason.0); + break; + } + } + } + } + } + + SocketStats { + tx: tx_counter, + rx: rx_counter.load(Ordering::Acquire), + } +} + +async fn send_version(tx: &mpsc::Sender) { + tx.send(SocketSignal::from_message(&ToClientRequest { + id: None, + params: ClientRequestMethod::version(VersionResponse::default()), + })) + .await + .ok(); +} +async fn handle_socket_read( + _log: &log::Logger, + readhalf: impl AsyncRead + Unpin, + mut closer: Barrier, + socket_tx: &mpsc::Sender, + rx_counter: Arc, + rpc: &RpcDispatcher, +) -> Result<(), std::io::Error> { + let mut readhalf = BufReader::new(readhalf); + let mut decoder = MsgPackCodec::new(); + let mut decoder_buf = bytes::BytesMut::new(); + + loop { + let read_len = tokio::select! { + r = readhalf.read_buf(&mut decoder_buf) => r, + _ = closer.wait() => Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "eof")), + }?; + + if read_len == 0 { + return Ok(()); + } + + rx_counter.fetch_add(read_len, Ordering::Relaxed); + + while let Some(frame) = decoder.decode(&mut decoder_buf)? { + match rpc.dispatch_with_partial(&frame.vec, frame.obj) { + MaybeSync::Sync(Some(v)) => { + if socket_tx.send(SocketSignal::Send(v)).await.is_err() { + return Ok(()); + } + } + MaybeSync::Sync(None) => continue, + MaybeSync::Future(fut) => { + let socket_tx = socket_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + socket_tx.send(SocketSignal::Send(v)).await.ok(); + } + }); + } + MaybeSync::Stream((stream, fut)) => { + if let Some(stream) = stream { + rpc.register_stream(socket_tx.clone(), stream).await; + } + let socket_tx = socket_tx.clone(); + tokio::spawn(async move { + if let Some(v) = fut.await { + socket_tx.send(SocketSignal::Send(v)).await.ok(); + } + }); + } + } + } + } +} + +#[derive(Clone)] +struct ServerOutputSink { + tx: mpsc::Sender, +} + +impl log::LogSink for ServerOutputSink { + fn write_log(&self, level: log::Level, _prefix: &str, message: &str) { + let s = SocketSignal::from_message(&ToClientRequest { + id: None, + params: ClientRequestMethod::serverlog(ServerLog { + line: message, + level: level.to_u8(), + }), + }); + + self.tx.try_send(s).ok(); + } + + fn write_result(&self, _message: &str) {} +} + +async fn handle_serve( + c: Arc, + params: ServeParams, +) -> Result { + // fill params.extensions into code_server_args.install_extensions + let mut csa = c.code_server_args.clone(); + csa.connection_token = params.connection_token.or(csa.connection_token); + csa.install_extensions.extend(params.extensions.into_iter()); + + let params_raw = ServerParamsRaw { + commit_id: params.commit_id, + quality: params.quality, + code_server_args: csa, + headless: true, + platform: c.platform, + }; + + let resolved = if params.use_local_download { + params_raw + .resolve(&c.log, Arc::new(c.http.delegated())) + .await + } else { + params_raw.resolve(&c.log, c.http.clone()).await + }?; + + let mut server_ref = c.code_server.lock().await; + let server = match &*server_ref { + Some(o) => o.clone(), + None => { + let install_log = c.log.tee(ServerOutputSink { + tx: c.socket_tx.clone(), + }); + + macro_rules! do_setup { + ($sb:expr) => { + match $sb.get_running().await? { + Some(AnyCodeServer::Socket(s)) => s, + Some(_) => return Err(AnyError::from(MismatchedLaunchModeError())), + None => { + $sb.setup().await?; + $sb.listen_on_default_socket().await? + } + } + }; + } + + let server = if params.use_local_download { + let sb = ServerBuilder::new( + &install_log, + &resolved, + &c.launcher_paths, + Arc::new(c.http.delegated()), + ); + do_setup!(sb) + } else { + let sb = + ServerBuilder::new(&install_log, &resolved, &c.launcher_paths, c.http.clone()); + do_setup!(sb) + }; + + server_ref.replace(server.clone()); + server + } + }; + + attach_server_bridge( + &c.log, + server, + c.socket_tx.clone(), + c.server_bridges.clone(), + params.socket_id, + params.compress, + ) + .await?; + Ok(EmptyObject {}) +} + +async fn attach_server_bridge( + log: &log::Logger, + code_server: SocketCodeServer, + socket_tx: mpsc::Sender, + multiplexer: ServerMultiplexer, + socket_id: u16, + compress: bool, +) -> Result { + let (server_messages, decoder) = if compress { + ( + ServerMessageSink::new_compressed( + multiplexer.clone(), + socket_id, + ServerMessageDestination::Channel(socket_tx), + ), + ClientMessageDecoder::new_compressed(), + ) + } else { + ( + ServerMessageSink::new_plain( + multiplexer.clone(), + socket_id, + ServerMessageDestination::Channel(socket_tx), + ), + ClientMessageDecoder::new_plain(), + ) + }; + + let attached_fut = ServerBridge::new(&code_server.socket, server_messages, decoder).await; + match attached_fut { + Ok(a) => { + multiplexer.register(socket_id, a); + trace!(log, "Attached to server"); + Ok(socket_id) + } + Err(e) => Err(e), + } +} + +/// Handle an incoming server message. This is synchronous and uses a 'write loop' +/// to ensure message order is preserved exactly, which is necessary for compression. +fn handle_server_message( + log: &log::Logger, + multiplexer: &ServerMultiplexer, + params: ServerMessageParams, +) -> Result { + if multiplexer.write_message(log, params.i, params.body) { + Ok(EmptyObject {}) + } else { + Err(AnyError::from(NoAttachedServerError())) + } +} + +fn handle_prune(paths: &LauncherPaths) -> Result, AnyError> { + prune_stopped_servers(paths).map(|v| { + v.iter() + .map(|p| p.server_dir.display().to_string()) + .collect() + }) +} + +async fn handle_update( + http: &Arc, + log: &log::Logger, + did_update: &AtomicBool, + params: &UpdateParams, +) -> Result { + if matches!(is_integrated_cli(), Ok(true)) || did_update.load(Ordering::SeqCst) { + return Ok(UpdateResult { + up_to_date: true, + did_update: false, + }); + } + + let update_service = UpdateService::new(log.clone(), http.clone()); + let updater = SelfUpdate::new(&update_service)?; + let latest_release = updater.get_current_release().await?; + let up_to_date = updater.is_up_to_date_with(&latest_release); + + if !params.do_update || up_to_date { + return Ok(UpdateResult { + up_to_date, + did_update: false, + }); + } + + if did_update + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_err() + { + return Ok(UpdateResult { + up_to_date: true, + did_update: true, // well, another thread did, but same difference... + }); + } + + info!(log, "Updating CLI to {}", latest_release); + + updater + .do_update(&latest_release, SilentCopyProgress()) + .await?; + + Ok(UpdateResult { + up_to_date: true, + did_update: true, + }) +} + +fn handle_get_hostname() -> Result { + Ok(GetHostnameResponse { + value: gethostname::gethostname().to_string_lossy().into_owned(), + }) +} + +fn handle_stat(path: String) -> Result { + Ok(std::fs::metadata(path) + .map(|m| FsStatResponse { + exists: true, + size: Some(m.len()), + kind: Some(match m.file_type() { + t if t.is_dir() => "dir", + t if t.is_file() => "file", + t if t.is_symlink() => "link", + _ => "unknown", + }), + }) + .unwrap_or_default()) +} + +fn handle_get_env() -> Result { + Ok(GetEnvResponse { + env: std::env::vars().collect(), + os_release: os_release().unwrap_or_else(|_| "unknown".to_string()), + #[cfg(windows)] + os_platform: "win32", + #[cfg(target_os = "linux")] + os_platform: "linux", + #[cfg(target_os = "macos")] + os_platform: "darwin", + }) +} + +fn handle_challenge_issue( + auth_state: &Arc>, +) -> Result { + let challenge = create_challenge(); + + let mut auth_state = auth_state.lock().unwrap(); + *auth_state = AuthState::ChallengeIssued(challenge.clone()); + + Ok(ChallengeIssueResponse { challenge }) +} + +fn handle_challenge_verify( + response: String, + auth_state: &Arc>, +) -> Result { + let mut auth_state = auth_state.lock().unwrap(); + + match &*auth_state { + AuthState::Authenticated => Ok(EmptyObject {}), + AuthState::WaitingForChallenge => Err(CodeError::AuthChallengeNotIssued.into()), + AuthState::ChallengeIssued(c) => match verify_challenge(c, &response) { + false => Err(CodeError::AuthChallengeNotIssued.into()), + true => { + *auth_state = AuthState::Authenticated; + Ok(EmptyObject {}) + } + }, + } +} + +async fn handle_forward( + log: &log::Logger, + port_forwarding: &Option, + params: ForwardParams, +) -> Result { + let port_forwarding = port_forwarding + .as_ref() + .ok_or(CodeError::PortForwardingNotAvailable)?; + info!(log, "Forwarding port {}", params.port); + let uri = port_forwarding.forward(params.port).await?; + Ok(ForwardResult { uri }) +} + +async fn handle_unforward( + log: &log::Logger, + port_forwarding: &Option, + params: UnforwardParams, +) -> Result { + let port_forwarding = port_forwarding + .as_ref() + .ok_or(CodeError::PortForwardingNotAvailable)?; + info!(log, "Unforwarding port {}", params.port); + port_forwarding.unforward(params.port).await?; + Ok(EmptyObject {}) +} + +async fn handle_call_server_http( + code_server: Option, + params: CallServerHttpParams, +) -> Result { + use hyper::{body, client::conn::Builder, Body, Request}; + + // We use Hyper directly here since reqwest doesn't support sockets/pipes. + // See https://github.com/seanmonstar/reqwest/issues/39 + + let socket = match &code_server { + Some(cs) => &cs.socket, + None => return Err(AnyError::from(NoAttachedServerError())), + }; + + let rw = get_socket_rw_stream(socket).await?; + + let (mut request_sender, connection) = Builder::new() + .handshake(rw) + .await + .map_err(|e| wrap(e, "error establishing connection"))?; + + // start the connection processing; it's shut down when the sender is dropped + tokio::spawn(connection); + + let mut request_builder = Request::builder() + .method::<&str>(params.method.as_ref()) + .uri(format!("http://127.0.0.1{}", params.path)) + .header("Host", "127.0.0.1"); + + for (k, v) in params.headers { + request_builder = request_builder.header(k, v); + } + let request = request_builder + .body(Body::from(params.body.unwrap_or_default())) + .map_err(|e| wrap(e, "invalid request"))?; + + let response = request_sender + .send_request(request) + .await + .map_err(|e| wrap(e, "error sending request"))?; + + Ok(CallServerHttpResult { + status: response.status().as_u16(), + headers: response + .headers() + .into_iter() + .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) + .collect(), + body: body::to_bytes(response) + .await + .map_err(|e| wrap(e, "error reading response body"))? + .to_vec(), + }) +} + +async fn handle_acquire_cli( + paths: &LauncherPaths, + http: &Arc, + log: &log::Logger, + params: AcquireCliParams, +) -> Result { + let update_service = UpdateService::new(log.clone(), http.clone()); + + let release = match params.commit_id { + Some(commit) => Release { + name: format!("{} CLI", PRODUCT_NAME_LONG), + commit, + platform: params.platform, + quality: params.quality, + target: TargetKind::Cli, + }, + None => { + update_service + .get_latest_commit(params.platform, TargetKind::Cli, params.quality) + .await? + } + }; + + let cli = download_cli_into_cache(&paths.cli_cache, &release, &update_service).await?; + let file = tokio::fs::File::open(cli) + .await + .map_err(|e| wrap(e, "error opening cli file"))?; + + handle_spawn::<_, DuplexStream>(log, params.spawn, Some(file), None, None).await +} + +async fn handle_spawn( + log: &log::Logger, + params: SpawnParams, + stdin: Option, + stdout: Option, + stderr: Option, +) -> Result +where + Stdin: AsyncRead + Unpin + Send + 'static, + StdoutAndErr: AsyncWrite + Unpin + Send + 'static, +{ + debug!( + log, + "requested to spawn {} with args {:?}", params.command, params.args + ); + + macro_rules! pipe_if { + ($e: expr) => { + if $e { + Stdio::piped() + } else { + Stdio::null() + } + }; + } + + let mut p = tokio::process::Command::new(¶ms.command); + p.args(¶ms.args); + p.envs(¶ms.env); + p.stdin(pipe_if!(stdin.is_some())); + p.stdout(pipe_if!(stdin.is_some())); + p.stderr(pipe_if!(stderr.is_some())); + if let Some(cwd) = ¶ms.cwd { + p.current_dir(cwd); + } + + let mut p = p.spawn().map_err(CodeError::ProcessSpawnFailed)?; + + let futs = FuturesUnordered::new(); + if let (Some(mut a), Some(mut b)) = (p.stdout.take(), stdout) { + futs.push(async move { tokio::io::copy(&mut a, &mut b).await }.boxed()); + } + if let (Some(mut a), Some(mut b)) = (p.stderr.take(), stderr) { + futs.push(async move { tokio::io::copy(&mut a, &mut b).await }.boxed()); + } + if let (Some(mut b), Some(mut a)) = (p.stdin.take(), stdin) { + futs.push(async move { tokio::io::copy(&mut a, &mut b).await }.boxed()); + } + + wait_for_process_exit(log, ¶ms.command, p, futs).await +} + +async fn handle_spawn_cli( + log: &log::Logger, + params: SpawnParams, + mut protocol_in: DuplexStream, + mut protocol_out: DuplexStream, + mut log_out: DuplexStream, +) -> Result { + debug!( + log, + "requested to spawn cli {} with args {:?}", params.command, params.args + ); + + let mut p = tokio::process::Command::new(¶ms.command); + p.args(¶ms.args); + + // CLI args to spawn a server; contracted with clients that they should _not_ provide these. + p.arg("--verbose"); + p.arg("command-shell"); + + p.envs(¶ms.env); + p.stdin(Stdio::piped()); + p.stdout(Stdio::piped()); + p.stderr(Stdio::piped()); + if let Some(cwd) = ¶ms.cwd { + p.current_dir(cwd); + } + + let mut p = p.spawn().map_err(CodeError::ProcessSpawnFailed)?; + + let mut stdin = p.stdin.take().unwrap(); + let mut stdout = p.stdout.take().unwrap(); + let mut stderr = p.stderr.take().unwrap(); + + // Start handling logs while doing the handshake in case there's some kind of error + let log_pump = tokio::spawn(async move { tokio::io::copy(&mut stdout, &mut log_out).await }); + + // note: intentionally do not wrap stdin in a bufreader, since we don't + // want to read anything other than our handshake messages. + if let Err(e) = spawn_do_child_authentication(log, &mut stdin, &mut stderr).await { + warning!(log, "failed to authenticate with child process {}", e); + let _ = p.kill().await; + return Err(e.into()); + } + + debug!(log, "cli authenticated, attaching stdio"); + let futs = FuturesUnordered::new(); + futs.push(async move { tokio::io::copy(&mut protocol_in, &mut stdin).await }.boxed()); + futs.push(async move { tokio::io::copy(&mut stderr, &mut protocol_out).await }.boxed()); + futs.push(async move { log_pump.await.unwrap() }.boxed()); + + wait_for_process_exit(log, ¶ms.command, p, futs).await +} + +type TokioCopyFuture = dyn futures::Future> + Send; + +async fn wait_for_process_exit( + log: &log::Logger, + command: &str, + mut process: tokio::process::Child, + futs: FuturesUnordered>>, +) -> Result { + let closed = process.wait(); + pin!(closed); + + let r = tokio::select! { + _ = futures::future::join_all(futs) => closed.await, + r = &mut closed => r + }; + + let r = match r { + Ok(e) => SpawnResult { + message: e.to_string(), + exit_code: e.code().unwrap_or(-1), + }, + Err(e) => SpawnResult { + message: e.to_string(), + exit_code: -1, + }, + }; + + debug!( + log, + "spawned cli {} exited with code {}", command, r.exit_code + ); + + Ok(r) +} + +async fn spawn_do_child_authentication( + log: &log::Logger, + stdin: &mut ChildStdin, + stdout: &mut ChildStderr, +) -> Result<(), CodeError> { + let (msg_tx, msg_rx) = mpsc::unbounded_channel(); + let (shutdown_rx, shutdown) = new_barrier(); + let mut rpc = new_msgpack_rpc(); + let caller = rpc.get_caller(msg_tx); + + let challenge_response = do_challenge_response_flow(caller, shutdown); + let rpc = start_msgpack_rpc( + rpc.methods(()).build(log.prefixed("client-auth")), + stdout, + stdin, + msg_rx, + shutdown_rx, + ); + pin!(rpc); + + tokio::select! { + r = &mut rpc => { + match r { + // means shutdown happened cleanly already, we're good + Ok(_) => Ok(()), + Err(e) => Err(CodeError::ProcessSpawnHandshakeFailed(e)) + } + }, + r = challenge_response => { + r?; + rpc.await.map(|_| ()).map_err(CodeError::ProcessSpawnFailed) + } + } +} + +async fn do_challenge_response_flow( + caller: RpcCaller, + shutdown: BarrierOpener<()>, +) -> Result<(), CodeError> { + let challenge: ChallengeIssueResponse = caller + .call(METHOD_CHALLENGE_ISSUE, EmptyObject {}) + .await + .unwrap() + .map_err(CodeError::TunnelRpcCallFailed)?; + + let _: EmptyObject = caller + .call( + METHOD_CHALLENGE_VERIFY, + ChallengeVerifyParams { + response: sign_challenge(&challenge.challenge), + }, + ) + .await + .unwrap() + .map_err(CodeError::TunnelRpcCallFailed)?; + + shutdown.open(()); + + Ok(()) +} diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs new file mode 100644 index 0000000000..78811b914b --- /dev/null +++ b/cli/src/tunnels/dev_tunnels.rs @@ -0,0 +1,997 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use crate::auth; +use crate::constants::{ + CONTROL_PORT, IS_INTERACTIVE_CLI, PROTOCOL_VERSION_TAG, PROTOCOL_VERSION_TAG_PREFIX, + TUNNEL_SERVICE_USER_AGENT, +}; +use crate::state::{LauncherPaths, PersistedState}; +use crate::util::errors::{ + wrap, AnyError, DevTunnelError, InvalidTunnelName, TunnelCreationFailed, WrappedError, +}; +use crate::util::input::prompt_placeholder; +use crate::{debug, info, log, spanf, trace, warning}; +use async_trait::async_trait; +use futures::TryFutureExt; +use lazy_static::lazy_static; +use rand::prelude::IteratorRandom; +use regex::Regex; +use reqwest::StatusCode; +use serde::{Deserialize, Serialize}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; +use tokio::sync::{mpsc, watch}; +use tunnels::connections::{ForwardedPortConnection, RelayTunnelHost}; +use tunnels::contracts::{ + Tunnel, TunnelPort, TunnelRelayTunnelEndpoint, PORT_TOKEN, TUNNEL_PROTOCOL_AUTO, +}; +use tunnels::management::{ + new_tunnel_management, HttpError, TunnelLocator, TunnelManagementClient, TunnelRequestOptions, + NO_REQUEST_OPTIONS, +}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct PersistedTunnel { + pub name: String, + pub id: String, + pub cluster: String, +} + +impl PersistedTunnel { + pub fn into_locator(self) -> TunnelLocator { + TunnelLocator::ID { + cluster: self.cluster, + id: self.id, + } + } + pub fn locator(&self) -> TunnelLocator { + TunnelLocator::ID { + cluster: self.cluster.clone(), + id: self.id.clone(), + } + } +} + +#[async_trait] +trait AccessTokenProvider: Send + Sync { + /// Gets the current access token. + async fn refresh_token(&self) -> Result; +} + +/// Access token provider that provides a fixed token without refreshing. +struct StaticAccessTokenProvider(String); + +impl StaticAccessTokenProvider { + pub fn new(token: String) -> Self { + Self(token) + } +} + +#[async_trait] +impl AccessTokenProvider for StaticAccessTokenProvider { + async fn refresh_token(&self) -> Result { + Ok(self.0.clone()) + } +} + +/// Access token provider that looks up the token from the tunnels API. +struct LookupAccessTokenProvider { + client: TunnelManagementClient, + locator: TunnelLocator, + log: log::Logger, + initial_token: Arc>>, +} + +impl LookupAccessTokenProvider { + pub fn new( + client: TunnelManagementClient, + locator: TunnelLocator, + log: log::Logger, + initial_token: Option, + ) -> Self { + Self { + client, + locator, + log, + initial_token: Arc::new(Mutex::new(initial_token)), + } + } +} + +#[async_trait] +impl AccessTokenProvider for LookupAccessTokenProvider { + async fn refresh_token(&self) -> Result { + if let Some(token) = self.initial_token.lock().unwrap().take() { + return Ok(token); + } + + let tunnel_lookup = spanf!( + self.log, + self.log.span("dev-tunnel.tag.get"), + self.client.get_tunnel( + &self.locator, + &TunnelRequestOptions { + token_scopes: vec!["host".to_string()], + ..Default::default() + } + ) + ); + + trace!(self.log, "Successfully refreshed access token"); + + match tunnel_lookup { + Ok(tunnel) => Ok(get_host_token_from_tunnel(&tunnel)), + Err(e) => Err(wrap(e, "failed to lookup tunnel for host token")), + } + } +} + +#[derive(Clone)] +pub struct DevTunnels { + log: log::Logger, + launcher_tunnel: PersistedState>, + client: TunnelManagementClient, +} + +/// Representation of a tunnel returned from the `start` methods. +pub struct ActiveTunnel { + /// Name of the tunnel + pub name: String, + /// Underlying dev tunnels ID + pub id: String, + manager: ActiveTunnelManager, +} + +impl ActiveTunnel { + /// Closes and unregisters the tunnel. + pub async fn close(&mut self) -> Result<(), AnyError> { + self.manager.kill().await?; + Ok(()) + } + + /// Forwards a port to local connections. + pub async fn add_port_direct( + &mut self, + port_number: u16, + ) -> Result, AnyError> { + let port = self.manager.add_port_direct(port_number).await?; + Ok(port) + } + + /// Forwards a port over TCP. + pub async fn add_port_tcp(&mut self, port_number: u16) -> Result<(), AnyError> { + self.manager.add_port_tcp(port_number).await?; + Ok(()) + } + + /// Removes a forwarded port TCP. + pub async fn remove_port(&mut self, port_number: u16) -> Result<(), AnyError> { + self.manager.remove_port(port_number).await?; + Ok(()) + } + + /// Gets the public URI on which a forwarded port can be access in browser. + pub async fn get_port_uri(&mut self, port: u16) -> Result { + let endpoint = self.manager.get_endpoint().await?; + let format = endpoint + .base + .port_uri_format + .expect("expected to have port format"); + + Ok(format.replace(PORT_TOKEN, &port.to_string())) + } +} + +const VSCODE_CLI_TUNNEL_TAG: &str = "vscode-server-launcher"; +const MAX_TUNNEL_NAME_LENGTH: usize = 20; + +fn get_host_token_from_tunnel(tunnel: &Tunnel) -> String { + tunnel + .access_tokens + .as_ref() + .expect("expected to have access tokens") + .get("host") + .expect("expected to have host token") + .to_string() +} + +fn is_valid_name(name: &str) -> Result<(), InvalidTunnelName> { + if name.len() > MAX_TUNNEL_NAME_LENGTH { + return Err(InvalidTunnelName(format!( + "Names cannot be longer than {} characters. Please try a different name.", + MAX_TUNNEL_NAME_LENGTH + ))); + } + + let re = Regex::new(r"^([\w-]+)$").unwrap(); + + if !re.is_match(name) { + return Err(InvalidTunnelName( + "Names can only contain letters, numbers, and '-'. Spaces, commas, and all other special characters are not allowed. Please try a different name.".to_string() + )); + } + + Ok(()) +} + +lazy_static! { + static ref HOST_TUNNEL_REQUEST_OPTIONS: TunnelRequestOptions = TunnelRequestOptions { + include_ports: true, + token_scopes: vec!["host".to_string()], + ..Default::default() + }; +} + +/// Structure optionally passed into `start_existing_tunnel` to forward an existing tunnel. +#[derive(Clone, Debug)] +pub struct ExistingTunnel { + /// Name you'd like to assign preexisting tunnel to use to connect to the VS Code Server + pub tunnel_name: String, + + /// Token to authenticate and use preexisting tunnel + pub host_token: String, + + /// Id of preexisting tunnel to use to connect to the VS Code Server + pub tunnel_id: String, + + /// Cluster of preexisting tunnel to use to connect to the VS Code Server + pub cluster: String, +} + +impl DevTunnels { + pub fn new(log: &log::Logger, auth: auth::Auth, paths: &LauncherPaths) -> DevTunnels { + let mut client = new_tunnel_management(&TUNNEL_SERVICE_USER_AGENT); + client.authorization_provider(auth); + + DevTunnels { + log: log.clone(), + client: client.into(), + launcher_tunnel: PersistedState::new(paths.root().join("code_tunnel.json")), + } + } + + pub async fn remove_tunnel(&mut self) -> Result<(), AnyError> { + let tunnel = match self.launcher_tunnel.load() { + Some(t) => t, + None => { + return Ok(()); + } + }; + + spanf!( + self.log, + self.log.span("dev-tunnel.delete"), + self.client + .delete_tunnel(&tunnel.into_locator(), NO_REQUEST_OPTIONS) + ) + .map_err(|e| wrap(e, "failed to execute `tunnel delete`"))?; + + self.launcher_tunnel.save(None)?; + Ok(()) + } + + /// Renames the current tunnel to the new name. + pub async fn rename_tunnel(&mut self, name: &str) -> Result<(), AnyError> { + self.update_tunnel_name(None, name).await.map(|_| ()) + } + + /// Updates the name of the existing persisted tunnel to the new name. + /// Gracefully creates a new tunnel if the previous one was deleted. + async fn update_tunnel_name( + &mut self, + persisted: Option, + name: &str, + ) -> Result<(Tunnel, PersistedTunnel), AnyError> { + let name = name.to_ascii_lowercase(); + self.check_is_name_free(&name).await?; + + debug!(self.log, "Tunnel name changed, applying updates..."); + + let (mut full_tunnel, mut persisted, is_new) = match persisted { + Some(persisted) => { + self.get_or_create_tunnel(persisted, Some(&name), NO_REQUEST_OPTIONS) + .await + } + None => self + .create_tunnel(&name, NO_REQUEST_OPTIONS) + .await + .map(|(pt, t)| (t, pt, true)), + }?; + + if is_new { + return Ok((full_tunnel, persisted)); + } + + full_tunnel.tags = vec![name.to_string(), VSCODE_CLI_TUNNEL_TAG.to_string()]; + + let new_tunnel = spanf!( + self.log, + self.log.span("dev-tunnel.tag.update"), + self.client.update_tunnel(&full_tunnel, NO_REQUEST_OPTIONS) + ) + .map_err(|e| wrap(e, "failed to rename tunnel"))?; + + persisted.name = name; + self.launcher_tunnel.save(Some(persisted.clone()))?; + + Ok((new_tunnel, persisted)) + } + + /// Gets the persisted tunnel from the service, or creates a new one. + /// If `create_with_new_name` is given, the new tunnel has that name + /// instead of the one previously persisted. + async fn get_or_create_tunnel( + &mut self, + persisted: PersistedTunnel, + create_with_new_name: Option<&str>, + options: &TunnelRequestOptions, + ) -> Result<(Tunnel, PersistedTunnel, /* is_new */ bool), AnyError> { + let tunnel_lookup = spanf!( + self.log, + self.log.span("dev-tunnel.tag.get"), + self.client.get_tunnel(&persisted.locator(), options) + ); + + match tunnel_lookup { + Ok(ft) => Ok((ft, persisted, false)), + Err(HttpError::ResponseError(e)) + if e.status_code == StatusCode::NOT_FOUND + || e.status_code == StatusCode::FORBIDDEN => + { + let (persisted, tunnel) = self + .create_tunnel(create_with_new_name.unwrap_or(&persisted.name), options) + .await?; + Ok((tunnel, persisted, true)) + } + Err(e) => Err(wrap(e, "failed to lookup tunnel").into()), + } + } + + /// Starts a new tunnel for the code server on the port. Unlike `start_new_tunnel`, + /// this attempts to reuse or create a tunnel of a preferred name or of a generated friendly tunnel name. + pub async fn start_new_launcher_tunnel( + &mut self, + preferred_name: Option<&str>, + use_random_name: bool, + ) -> Result { + let (mut tunnel, persisted) = match self.launcher_tunnel.load() { + Some(mut persisted) => { + if let Some(preferred_name) = preferred_name.map(|n| n.to_ascii_lowercase()) { + if persisted.name.to_ascii_lowercase() != preferred_name { + (_, persisted) = self + .update_tunnel_name(Some(persisted), &preferred_name) + .await?; + } + } + + let (tunnel, persisted, _) = self + .get_or_create_tunnel(persisted, None, &HOST_TUNNEL_REQUEST_OPTIONS) + .await?; + (tunnel, persisted) + } + None => { + debug!(self.log, "No code server tunnel found, creating new one"); + let name = self + .get_name_for_tunnel(preferred_name, use_random_name) + .await?; + let (persisted, full_tunnel) = self + .create_tunnel(&name, &HOST_TUNNEL_REQUEST_OPTIONS) + .await?; + (full_tunnel, persisted) + } + }; + + if !tunnel.tags.iter().any(|t| t == PROTOCOL_VERSION_TAG) { + tunnel = self + .update_protocol_version_tag(tunnel, &HOST_TUNNEL_REQUEST_OPTIONS) + .await?; + } + + let locator = TunnelLocator::try_from(&tunnel).unwrap(); + let host_token = get_host_token_from_tunnel(&tunnel); + + for port_to_delete in tunnel + .ports + .iter() + .filter(|p| p.port_number != CONTROL_PORT) + { + let output_fut = self.client.delete_tunnel_port( + &locator, + port_to_delete.port_number, + NO_REQUEST_OPTIONS, + ); + spanf!( + self.log, + self.log.span("dev-tunnel.port.delete"), + output_fut + ) + .map_err(|e| wrap(e, "failed to delete port"))?; + } + + // cleanup any old trailing tunnel endpoints + for endpoint in tunnel.endpoints { + let fut = self.client.delete_tunnel_endpoints( + &locator, + &endpoint.host_id, + None, + NO_REQUEST_OPTIONS, + ); + + spanf!(self.log, self.log.span("dev-tunnel.endpoint.prune"), fut) + .map_err(|e| wrap(e, "failed to prune tunnel endpoint"))?; + } + + self.start_tunnel( + locator.clone(), + &persisted, + self.client.clone(), + LookupAccessTokenProvider::new( + self.client.clone(), + locator, + self.log.clone(), + Some(host_token), + ), + ) + .await + } + + async fn create_tunnel( + &mut self, + name: &str, + options: &TunnelRequestOptions, + ) -> Result<(PersistedTunnel, Tunnel), AnyError> { + info!(self.log, "Creating tunnel with the name: {}", name); + + let mut tried_recycle = false; + + let new_tunnel = Tunnel { + tags: vec![ + name.to_string(), + PROTOCOL_VERSION_TAG.to_string(), + VSCODE_CLI_TUNNEL_TAG.to_string(), + ], + ..Default::default() + }; + + loop { + let result = spanf!( + self.log, + self.log.span("dev-tunnel.create"), + self.client.create_tunnel(&new_tunnel, options) + ); + + match result { + Err(HttpError::ResponseError(e)) + if e.status_code == StatusCode::TOO_MANY_REQUESTS => + { + if !tried_recycle && self.try_recycle_tunnel().await? { + tried_recycle = true; + continue; + } + + return Err(AnyError::from(TunnelCreationFailed( + name.to_string(), + "You've exceeded the 10 machine limit for the port fowarding service. Please remove other machines before trying to add this machine.".to_string(), + ))); + } + Err(e) => { + return Err(AnyError::from(TunnelCreationFailed( + name.to_string(), + format!("{:?}", e), + ))) + } + Ok(t) => { + let pt = PersistedTunnel { + cluster: t.cluster_id.clone().unwrap(), + id: t.tunnel_id.clone().unwrap(), + name: name.to_string(), + }; + + self.launcher_tunnel.save(Some(pt.clone()))?; + return Ok((pt, t)); + } + } + } + } + + /// Ensures the tunnel contains a tag for the current PROTCOL_VERSION, and no + /// other version tags. + async fn update_protocol_version_tag( + &self, + tunnel: Tunnel, + options: &TunnelRequestOptions, + ) -> Result { + debug!( + self.log, + "Updating tunnel protocol version tag to {}", PROTOCOL_VERSION_TAG + ); + let mut new_tags: Vec = tunnel + .tags + .into_iter() + .filter(|t| !t.starts_with(PROTOCOL_VERSION_TAG_PREFIX)) + .collect(); + new_tags.push(PROTOCOL_VERSION_TAG.to_string()); + + let tunnel_update = Tunnel { + tags: new_tags, + tunnel_id: tunnel.tunnel_id.clone(), + cluster_id: tunnel.cluster_id.clone(), + ..Default::default() + }; + + let result = spanf!( + self.log, + self.log.span("dev-tunnel.protocol-tag-update"), + self.client.update_tunnel(&tunnel_update, options) + ); + + result.map_err(|e| wrap(e, "tunnel tag update failed").into()) + } + + /// Tries to delete an unused tunnel, and then creates a tunnel with the + /// given `new_name`. + async fn try_recycle_tunnel(&mut self) -> Result { + trace!( + self.log, + "Tunnel limit hit, trying to recycle an old tunnel" + ); + + let existing_tunnels = self.list_all_server_tunnels().await?; + + let recyclable = existing_tunnels + .iter() + .filter(|t| { + t.status + .as_ref() + .and_then(|s| s.host_connection_count.as_ref()) + .map(|c| c.get_count()) + .unwrap_or(0) == 0 + }) + .choose(&mut rand::thread_rng()); + + match recyclable { + Some(tunnel) => { + trace!(self.log, "Recycling tunnel ID {:?}", tunnel.tunnel_id); + spanf!( + self.log, + self.log.span("dev-tunnel.delete"), + self.client + .delete_tunnel(&tunnel.try_into().unwrap(), NO_REQUEST_OPTIONS) + ) + .map_err(|e| wrap(e, "failed to execute `tunnel delete`"))?; + Ok(true) + } + None => { + trace!(self.log, "No tunnels available to recycle"); + Ok(false) + } + } + } + + async fn list_all_server_tunnels(&mut self) -> Result, AnyError> { + let tunnels = spanf!( + self.log, + self.log.span("dev-tunnel.listall"), + self.client.list_all_tunnels(&TunnelRequestOptions { + tags: vec![VSCODE_CLI_TUNNEL_TAG.to_string()], + require_all_tags: true, + ..Default::default() + }) + ) + .map_err(|e| wrap(e, "error listing current tunnels"))?; + + Ok(tunnels) + } + + async fn check_is_name_free(&mut self, name: &str) -> Result<(), AnyError> { + let existing = spanf!( + self.log, + self.log.span("dev-tunnel.rename.search"), + self.client.list_all_tunnels(&TunnelRequestOptions { + tags: vec![VSCODE_CLI_TUNNEL_TAG.to_string(), name.to_string()], + require_all_tags: true, + ..Default::default() + }) + ) + .map_err(|e| wrap(e, "failed to list existing tunnels"))?; + if !existing.is_empty() { + return Err(AnyError::from(TunnelCreationFailed( + name.to_string(), + "tunnel name already in use".to_string(), + ))); + }; + Ok(()) + } + + async fn get_name_for_tunnel( + &mut self, + preferred_name: Option<&str>, + mut use_random_name: bool, + ) -> Result { + let existing_tunnels = self.list_all_server_tunnels().await?; + let is_name_free = |n: &str| { + !existing_tunnels.iter().any(|v| { + v.status + .as_ref() + .and_then(|s| s.host_connection_count.as_ref().map(|c| c.get_count())) + .unwrap_or(0) > 0 && v.tags.iter().any(|t| t == n) + }) + }; + + if let Some(machine_name) = preferred_name { + let name = machine_name.to_ascii_lowercase(); + if let Err(e) = is_valid_name(&name) { + info!(self.log, "{} is an invalid name", e); + return Err(AnyError::from(wrap(e, "invalid name"))); + } + if is_name_free(&name) { + return Ok(name); + } + info!( + self.log, + "{} is already taken, using a random name instead", &name + ); + use_random_name = true; + } + + let mut placeholder_name = + clean_hostname_for_tunnel(&gethostname::gethostname().to_string_lossy()); + placeholder_name.make_ascii_lowercase(); + + if !is_name_free(&placeholder_name) { + for i in 2.. { + let fixed_name = format!("{}{}", placeholder_name, i); + if is_name_free(&fixed_name) { + placeholder_name = fixed_name; + break; + } + } + } + + if use_random_name || !*IS_INTERACTIVE_CLI { + return Ok(placeholder_name); + } + + loop { + let mut name = prompt_placeholder( + "What would you like to call this machine?", + &placeholder_name, + )?; + + name.make_ascii_lowercase(); + + if let Err(e) = is_valid_name(&name) { + info!(self.log, "{}", e); + continue; + } + + if is_name_free(&name) { + return Ok(name); + } + + info!(self.log, "The name {} is already in use", name); + } + } + + /// Hosts an existing tunnel, where the tunnel ID and host token are given. + pub async fn start_existing_tunnel( + &mut self, + tunnel: ExistingTunnel, + ) -> Result { + let tunnel_details = PersistedTunnel { + name: tunnel.tunnel_name, + id: tunnel.tunnel_id, + cluster: tunnel.cluster, + }; + + let mut mgmt = self.client.build(); + mgmt.authorization(tunnels::management::Authorization::Tunnel( + tunnel.host_token.clone(), + )); + + self.start_tunnel( + tunnel_details.locator(), + &tunnel_details, + mgmt.into(), + StaticAccessTokenProvider::new(tunnel.host_token), + ) + .await + } + + async fn start_tunnel( + &mut self, + locator: TunnelLocator, + tunnel_details: &PersistedTunnel, + client: TunnelManagementClient, + access_token: impl AccessTokenProvider + 'static, + ) -> Result { + let mut manager = ActiveTunnelManager::new(self.log.clone(), client, locator, access_token); + + let endpoint_result = spanf!( + self.log, + self.log.span("dev-tunnel.serve.callback"), + manager.get_endpoint() + ); + + let endpoint = match endpoint_result { + Ok(endpoint) => endpoint, + Err(e) => { + error!(self.log, "Error connecting to tunnel endpoint: {}", e); + manager.kill().await.ok(); + return Err(e); + } + }; + + debug!(self.log, "Connected to tunnel endpoint: {:?}", endpoint); + + Ok(ActiveTunnel { + name: tunnel_details.name.clone(), + id: tunnel_details.id.clone(), + manager, + }) + } +} + +struct ActiveTunnelManager { + close_tx: Option>, + endpoint_rx: watch::Receiver>>, + relay: Arc>, +} + +impl ActiveTunnelManager { + pub fn new( + log: log::Logger, + mgmt: TunnelManagementClient, + locator: TunnelLocator, + access_token: impl AccessTokenProvider + 'static, + ) -> ActiveTunnelManager { + let (endpoint_tx, endpoint_rx) = watch::channel(None); + let (close_tx, close_rx) = mpsc::channel(1); + + let relay = Arc::new(tokio::sync::Mutex::new(RelayTunnelHost::new(locator, mgmt))); + let relay_spawned = relay.clone(); + + tokio::spawn(async move { + ActiveTunnelManager::spawn_tunnel( + log, + relay_spawned, + close_rx, + endpoint_tx, + access_token, + ) + .await; + }); + + ActiveTunnelManager { + endpoint_rx, + relay, + close_tx: Some(close_tx), + } + } + + /// Adds a port for TCP/IP forwarding. + #[allow(dead_code)] // todo: port forwarding + pub async fn add_port_tcp(&self, port_number: u16) -> Result<(), WrappedError> { + self.relay + .lock() + .await + .add_port(&TunnelPort { + port_number, + protocol: Some(TUNNEL_PROTOCOL_AUTO.to_owned()), + ..Default::default() + }) + .await + .map_err(|e| wrap(e, "error adding port to relay"))?; + Ok(()) + } + + /// Adds a port for TCP/IP forwarding. + pub async fn add_port_direct( + &self, + port_number: u16, + ) -> Result, WrappedError> { + self.relay + .lock() + .await + .add_port_raw(&TunnelPort { + port_number, + protocol: Some(TUNNEL_PROTOCOL_AUTO.to_owned()), + ..Default::default() + }) + .await + .map_err(|e| wrap(e, "error adding port to relay")) + } + + /// Removes a port from TCP/IP forwarding. + pub async fn remove_port(&self, port_number: u16) -> Result<(), WrappedError> { + self.relay + .lock() + .await + .remove_port(port_number) + .await + .map_err(|e| wrap(e, "error remove port from relay")) + } + + /// Gets the most recent details from the tunnel process. Returns None if + /// the process exited before providing details. + pub async fn get_endpoint(&mut self) -> Result { + loop { + if let Some(details) = &*self.endpoint_rx.borrow() { + return details.clone().map_err(AnyError::from); + } + + if self.endpoint_rx.changed().await.is_err() { + return Err(DevTunnelError("tunnel creation cancelled".to_string()).into()); + } + } + } + + /// Kills the process, and waits for it to exit. + /// See https://tokio.rs/tokio/topics/shutdown#waiting-for-things-to-finish-shutting-down for how this works + pub async fn kill(&mut self) -> Result<(), AnyError> { + if let Some(tx) = self.close_tx.take() { + drop(tx); + } + + self.relay + .lock() + .await + .unregister() + .await + .map_err(|e| wrap(e, "error unregistering relay"))?; + + while self.endpoint_rx.changed().await.is_ok() {} + + Ok(()) + } + + async fn spawn_tunnel( + log: log::Logger, + relay: Arc>, + mut close_rx: mpsc::Receiver<()>, + endpoint_tx: watch::Sender>>, + access_token_provider: impl AccessTokenProvider + 'static, + ) { + let mut backoff = Backoff::new(Duration::from_secs(5), Duration::from_secs(120)); + + macro_rules! fail { + ($e: expr, $msg: expr) => { + warning!(log, "{}: {}", $msg, $e); + endpoint_tx.send(Some(Err($e))).ok(); + backoff.delay().await; + }; + } + + loop { + debug!(log, "Starting tunnel to server..."); + + let access_token = match access_token_provider.refresh_token().await { + Ok(t) => t, + Err(e) => { + fail!(e, "Error refreshing access token, will retry"); + continue; + } + }; + + // we don't bother making a client that can refresh the token, since + // the tunnel won't be able to host as soon as the access token expires. + let handle_res = { + let mut relay = relay.lock().await; + relay + .connect(&access_token) + .await + .map_err(|e| wrap(e, "error connecting to tunnel")) + }; + + let mut handle = match handle_res { + Ok(handle) => handle, + Err(e) => { + fail!(e, "Error connecting to relay, will retry"); + continue; + } + }; + + backoff.reset(); + endpoint_tx.send(Some(Ok(handle.endpoint().clone()))).ok(); + + tokio::select! { + // error is mapped like this prevent it being used across an await, + // which Rust dislikes since there's a non-sendable dyn Error in there + res = (&mut handle).map_err(|e| wrap(e, "error from tunnel connection")) => { + if let Err(e) = res { + fail!(e, "Tunnel exited unexpectedly, reconnecting"); + } else { + warning!(log, "Tunnel exited unexpectedly but gracefully, reconnecting"); + backoff.delay().await; + } + }, + _ = close_rx.recv() => { + trace!(log, "Tunnel closing gracefully"); + trace!(log, "Tunnel closed with result: {:?}", handle.close().await); + break; + } + } + } + } +} + +struct Backoff { + failures: u32, + base_duration: Duration, + max_duration: Duration, +} + +impl Backoff { + pub fn new(base_duration: Duration, max_duration: Duration) -> Self { + Self { + failures: 0, + base_duration, + max_duration, + } + } + + pub async fn delay(&mut self) { + tokio::time::sleep(self.next()).await + } + + pub fn next(&mut self) -> Duration { + self.failures += 1; + let duration = self + .base_duration + .checked_mul(self.failures) + .unwrap_or(self.max_duration); + std::cmp::min(duration, self.max_duration) + } + + pub fn reset(&mut self) { + self.failures = 0; + } +} + +/// Cleans up the hostname so it can be used as a tunnel name. +/// See TUNNEL_NAME_PATTERN in the tunnels SDK for the rules we try to use. +fn clean_hostname_for_tunnel(hostname: &str) -> String { + let mut out = String::new(); + for char in hostname.chars().take(60) { + match char { + '-' | '_' | ' ' => { + out.push('-'); + } + '0'..='9' | 'a'..='z' | 'A'..='Z' => { + out.push(char); + } + _ => {} + } + } + + let trimmed = out.trim_matches('-'); + if trimmed.len() < 2 { + "remote-machine".to_string() // placeholder if the result was empty + } else { + trimmed.to_owned() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_clean_hostname_for_tunnel() { + assert_eq!( + clean_hostname_for_tunnel("hello123"), + "hello123".to_string() + ); + assert_eq!( + clean_hostname_for_tunnel("-cool-name-"), + "cool-name".to_string() + ); + assert_eq!( + clean_hostname_for_tunnel("cool!name with_chars"), + "coolname-with-chars".to_string() + ); + assert_eq!(clean_hostname_for_tunnel("z"), "remote-machine".to_string()); + } +} diff --git a/cli/src/tunnels/legal.rs b/cli/src/tunnels/legal.rs new file mode 100644 index 0000000000..23aac529d0 --- /dev/null +++ b/cli/src/tunnels/legal.rs @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use crate::constants::{IS_INTERACTIVE_CLI, PRODUCT_NAME_LONG}; +use crate::state::{LauncherPaths, PersistedState}; +use crate::util::errors::{AnyError, MissingLegalConsent}; +use crate::util::input::prompt_yn; +use serde::{Deserialize, Serialize}; + +const LICENSE_TEXT: Option<&'static str> = option_env!("VSCODE_CLI_REMOTE_LICENSE_TEXT"); +const LICENSE_PROMPT: Option<&'static str> = option_env!("VSCODE_CLI_REMOTE_LICENSE_PROMPT"); + +#[derive(Clone, Default, Serialize, Deserialize)] +struct PersistedConsent { + pub consented: Option, +} + +pub fn require_consent( + paths: &LauncherPaths, + accept_server_license_terms: bool, +) -> Result<(), AnyError> { + match LICENSE_TEXT { + Some(t) => println!("{}", t.replace("\\n", "\r\n")), + None => return Ok(()), + } + + let prompt = match LICENSE_PROMPT { + Some(p) => p, + None => return Ok(()), + }; + + let license: PersistedState = + PersistedState::new(paths.root().join("license_consent.json")); + + let mut load = license.load(); + if let Some(true) = load.consented { + return Ok(()); + } + + if accept_server_license_terms { + load.consented = Some(true); + } else if !*IS_INTERACTIVE_CLI { + return Err(MissingLegalConsent( + "Run this command again with --accept-server-license-terms to indicate your agreement." + .to_string(), + ) + .into()); + } else { + match prompt_yn(prompt) { + Ok(true) => { + load.consented = Some(true); + } + Ok(false) => { + return Err(AnyError::from(MissingLegalConsent(format!( + "Sorry you cannot use {} CLI without accepting the terms.", + PRODUCT_NAME_LONG + )))) + } + Err(e) => return Err(AnyError::from(MissingLegalConsent(e.to_string()))), + } + } + + license.save(load)?; + Ok(()) +} diff --git a/cli/src/tunnels/nosleep.rs b/cli/src/tunnels/nosleep.rs new file mode 100644 index 0000000000..7a15debf4b --- /dev/null +++ b/cli/src/tunnels/nosleep.rs @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +#[cfg(target_os = "windows")] +pub type SleepInhibitor = super::nosleep_windows::SleepInhibitor; + +#[cfg(target_os = "linux")] +pub type SleepInhibitor = super::nosleep_linux::SleepInhibitor; + +#[cfg(target_os = "macos")] +pub type SleepInhibitor = super::nosleep_macos::SleepInhibitor; diff --git a/cli/src/tunnels/nosleep_linux.rs b/cli/src/tunnels/nosleep_linux.rs new file mode 100644 index 0000000000..eb778eb31d --- /dev/null +++ b/cli/src/tunnels/nosleep_linux.rs @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use zbus::{dbus_proxy, Connection}; + +use crate::{ + constants::APPLICATION_NAME, + util::errors::{wrap, AnyError}, +}; + +/// An basically undocumented API, but seems widely implemented, and is what +/// browsers use for sleep inhibition. The downside is that it also *may* +/// disable the screensaver. A much better and more granular API is available +/// on `org.freedesktop.login1.Manager`, but this requires administrative +/// permission to request inhibition, which is not possible here. +/// +/// See https://source.chromium.org/chromium/chromium/src/+/main:services/device/wake_lock/power_save_blocker/power_save_blocker_linux.cc;l=54;drc=2e85357a8b76996981cc6f783853a49df2cedc3a +#[dbus_proxy( + interface = "org.freedesktop.PowerManagement.Inhibit", + gen_blocking = false, + default_service = "org.freedesktop.PowerManagement.Inhibit", + default_path = "/org/freedesktop/PowerManagement/Inhibit" +)] +trait PMInhibitor { + #[dbus_proxy(name = "Inhibit")] + fn inhibit(&self, what: &str, why: &str) -> zbus::Result; +} + +/// A slightly better documented version which seems commonly used. +#[dbus_proxy( + interface = "org.freedesktop.ScreenSaver", + gen_blocking = false, + default_service = "org.freedesktop.ScreenSaver", + default_path = "/org/freedesktop/ScreenSaver" +)] +trait ScreenSaver { + #[dbus_proxy(name = "Inhibit")] + fn inhibit(&self, what: &str, why: &str) -> zbus::Result; +} + +pub struct SleepInhibitor { + _connection: Connection, // Inhibition is released when the connection is closed +} + +impl SleepInhibitor { + pub async fn new() -> Result { + let connection = Connection::session() + .await + .map_err(|e| wrap(e, "error creating dbus session"))?; + + macro_rules! try_inhibit { + ($proxy:ident) => { + match $proxy::new(&connection).await { + Ok(proxy) => proxy.inhibit(APPLICATION_NAME, "running tunnel").await, + Err(e) => Err(e), + } + }; + } + + if let Err(e1) = try_inhibit!(PMInhibitorProxy) { + if let Err(e2) = try_inhibit!(ScreenSaverProxy) { + return Err(wrap( + e2, + format!( + "error requesting sleep inhibition, pminhibitor gave {}, screensaver gave", + e1 + ), + ) + .into()); + } + } + + Ok(SleepInhibitor { + _connection: connection, + }) + } +} diff --git a/cli/src/tunnels/nosleep_macos.rs b/cli/src/tunnels/nosleep_macos.rs new file mode 100644 index 0000000000..55966acd1a --- /dev/null +++ b/cli/src/tunnels/nosleep_macos.rs @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::io; + +use core_foundation::base::TCFType; +use core_foundation::string::{CFString, CFStringRef}; +use libc::c_int; + +use crate::constants::TUNNEL_ACTIVITY_NAME; + +extern "C" { + pub fn IOPMAssertionCreateWithName( + assertion_type: CFStringRef, + assertion_level: u32, + assertion_name: CFStringRef, + assertion_id: &mut u32, + ) -> c_int; + + pub fn IOPMAssertionRelease(assertion_id: u32) -> c_int; +} + +const NUM_ASSERTIONS: usize = 2; + +const ASSERTIONS: [&str; NUM_ASSERTIONS] = ["PreventUserIdleSystemSleep", "PreventSystemSleep"]; + +struct Assertion(u32); + +impl Assertion { + pub fn make(typ: &CFString, name: &CFString) -> io::Result { + let mut assertion_id = 0; + let result = unsafe { + IOPMAssertionCreateWithName( + typ.as_concrete_TypeRef(), + 255, + name.as_concrete_TypeRef(), + &mut assertion_id, + ) + }; + + if result != 0 { + Err(io::Error::last_os_error()) + } else { + Ok(Self(assertion_id)) + } + } +} + +impl Drop for Assertion { + fn drop(&mut self) { + unsafe { + IOPMAssertionRelease(self.0); + } + } +} + +pub struct SleepInhibitor { + _assertions: Vec, +} + +impl SleepInhibitor { + pub async fn new() -> io::Result { + let mut assertions = Vec::with_capacity(NUM_ASSERTIONS); + let assertion_name = CFString::from_static_string(TUNNEL_ACTIVITY_NAME); + for typ in ASSERTIONS { + assertions.push(Assertion::make( + &CFString::from_static_string(typ), + &assertion_name, + )?); + } + + Ok(Self { + _assertions: assertions, + }) + } +} diff --git a/cli/src/tunnels/nosleep_windows.rs b/cli/src/tunnels/nosleep_windows.rs new file mode 100644 index 0000000000..6d61c8c25d --- /dev/null +++ b/cli/src/tunnels/nosleep_windows.rs @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::io; + +use winapi::{ + ctypes::c_void, + um::{ + handleapi::CloseHandle, + minwinbase::REASON_CONTEXT, + winbase::{PowerClearRequest, PowerCreateRequest, PowerSetRequest}, + winnt::{ + PowerRequestSystemRequired, POWER_REQUEST_CONTEXT_SIMPLE_STRING, + POWER_REQUEST_CONTEXT_VERSION, POWER_REQUEST_TYPE, + }, + }, +}; + +use crate::constants::TUNNEL_ACTIVITY_NAME; + +struct Request(*mut c_void); + +impl Request { + pub fn new() -> io::Result { + let mut reason: Vec = TUNNEL_ACTIVITY_NAME.encode_utf16().collect(); + let mut context = REASON_CONTEXT { + Version: POWER_REQUEST_CONTEXT_VERSION, + Flags: POWER_REQUEST_CONTEXT_SIMPLE_STRING, + ..Default::default() + }; + unsafe { *context.Reason.SimpleReasonString_mut() = reason.as_mut_ptr() }; + + let request = unsafe { PowerCreateRequest(&mut context) }; + if request.is_null() { + return Err(io::Error::last_os_error()); + } + + Ok(Self(request)) + } + + pub fn set(&self, request_type: POWER_REQUEST_TYPE) -> io::Result<()> { + let result = unsafe { PowerSetRequest(self.0, request_type) }; + if result == 0 { + return Err(io::Error::last_os_error()); + } + + Ok(()) + } +} + +impl Drop for Request { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } +} + +pub struct SleepInhibitor { + request: Request, +} + +impl SleepInhibitor { + pub async fn new() -> io::Result { + let request = Request::new()?; + request.set(PowerRequestSystemRequired)?; + Ok(Self { request }) + } +} + +impl Drop for SleepInhibitor { + fn drop(&mut self) { + unsafe { + PowerClearRequest(self.request.0, PowerRequestSystemRequired); + } + } +} diff --git a/cli/src/tunnels/paths.rs b/cli/src/tunnels/paths.rs new file mode 100644 index 0000000000..6b374e041a --- /dev/null +++ b/cli/src/tunnels/paths.rs @@ -0,0 +1,154 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + fs::{read_dir, read_to_string, remove_dir_all, write}, + path::PathBuf, +}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + options::{self, Quality}, + state::LauncherPaths, + util::{ + errors::{wrap, AnyError, WrappedError}, + machine, + }, +}; + +pub const SERVER_FOLDER_NAME: &str = "server"; + +pub struct ServerPaths { + // Directory into which the server is downloaded + pub server_dir: PathBuf, + // Executable path, within the server_id + pub executable: PathBuf, + // File where logs for the server should be written. + pub logfile: PathBuf, + // File where the process ID for the server should be written. + pub pidfile: PathBuf, +} + +impl ServerPaths { + // Queries the system to determine the process ID of the running server. + // Returns the process ID, if the server is running. + pub fn get_running_pid(&self) -> Option { + if let Some(pid) = self.read_pid() { + return match machine::process_at_path_exists(pid, &self.executable) { + true => Some(pid), + false => None, + }; + } + + if let Some(pid) = machine::find_running_process(&self.executable) { + // attempt to backfill process ID: + self.write_pid(pid).ok(); + return Some(pid); + } + + None + } + + /// Delete the server directory + pub fn delete(&self) -> Result<(), WrappedError> { + remove_dir_all(&self.server_dir).map_err(|e| { + wrap( + e, + format!("error deleting server dir {}", self.server_dir.display()), + ) + }) + } + + // VS Code Server pid + pub fn write_pid(&self, pid: u32) -> Result<(), WrappedError> { + write(&self.pidfile, format!("{}", pid)).map_err(|e| { + wrap( + e, + format!("error writing process id into {}", self.pidfile.display()), + ) + }) + } + + fn read_pid(&self) -> Option { + read_to_string(&self.pidfile) + .ok() + .and_then(|s| s.parse::().ok()) + } +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub struct InstalledServer { + pub quality: options::Quality, + pub commit: String, + pub headless: bool, +} + +impl InstalledServer { + /// Gets path information about where a specific server should be stored. + pub fn server_paths(&self, p: &LauncherPaths) -> ServerPaths { + let server_dir = self.get_install_folder(p); + ServerPaths { + executable: server_dir + .join(SERVER_FOLDER_NAME) + .join("bin") + .join(self.quality.server_entrypoint()), + logfile: server_dir.join("log.txt"), + pidfile: server_dir.join("pid.txt"), + server_dir, + } + } + + fn get_install_folder(&self, p: &LauncherPaths) -> PathBuf { + p.server_cache.path().join(if !self.headless { + format!("{}-web", get_server_folder_name(self.quality, &self.commit)) + } else { + get_server_folder_name(self.quality, &self.commit) + }) + } +} + +/// Prunes servers not currently running, and returns the deleted servers. +pub fn prune_stopped_servers(launcher_paths: &LauncherPaths) -> Result, AnyError> { + get_all_servers(launcher_paths) + .into_iter() + .map(|s| s.server_paths(launcher_paths)) + .filter(|s| s.get_running_pid().is_none()) + .map(|s| s.delete().map(|_| s)) + .collect::>() + .map_err(AnyError::from) +} + +// Gets a list of all servers which look like they might be running. +pub fn get_all_servers(lp: &LauncherPaths) -> Vec { + let mut servers: Vec = vec![]; + if let Ok(children) = read_dir(lp.server_cache.path()) { + for child in children.flatten() { + let fname = child.file_name(); + let fname = fname.to_string_lossy(); + let (quality, commit) = match fname.split_once('-') { + Some(r) => r, + None => continue, + }; + + let quality = match options::Quality::try_from(quality) { + Ok(q) => q, + Err(_) => continue, + }; + + servers.push(InstalledServer { + quality, + commit: commit.to_string(), + headless: true, + }); + } + } + + servers +} + +pub fn get_server_folder_name(quality: Quality, commit: &str) -> String { + format!("{}-{}", quality, commit) +} diff --git a/cli/src/tunnels/port_forwarder.rs b/cli/src/tunnels/port_forwarder.rs new file mode 100644 index 0000000000..409d11a2b8 --- /dev/null +++ b/cli/src/tunnels/port_forwarder.rs @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::collections::HashSet; + +use tokio::sync::{mpsc, oneshot}; + +use crate::{ + constants::CONTROL_PORT, + util::errors::{AnyError, CannotForwardControlPort, ServerHasClosed}, +}; + +use super::dev_tunnels::ActiveTunnel; + +pub enum PortForwardingRec { + Forward(u16, oneshot::Sender>), + Unforward(u16, oneshot::Sender>), +} + +/// Provides a port forwarding service for connected clients. Clients can make +/// requests on it, which are (and *must be*) processed by calling the `.process()` +/// method on the forwarder. +pub struct PortForwardingProcessor { + tx: mpsc::Sender, + rx: mpsc::Receiver, + forwarded: HashSet, +} + +impl PortForwardingProcessor { + pub fn new() -> Self { + let (tx, rx) = mpsc::channel(8); + Self { + tx, + rx, + forwarded: HashSet::new(), + } + } + + /// Gets a handle that can be passed off to consumers of port forwarding. + pub fn handle(&self) -> PortForwarding { + PortForwarding { + tx: self.tx.clone(), + } + } + + /// Receives port forwarding requests. Consumers MUST call `process()` + /// with the received requests. + pub async fn recv(&mut self) -> Option { + self.rx.recv().await + } + + /// Processes the incoming forwarding request. + pub async fn process(&mut self, req: PortForwardingRec, tunnel: &mut ActiveTunnel) { + match req { + PortForwardingRec::Forward(port, tx) => { + tx.send(self.process_forward(port, tunnel).await).ok(); + } + PortForwardingRec::Unforward(port, tx) => { + tx.send(self.process_unforward(port, tunnel).await).ok(); + } + } + } + + async fn process_unforward( + &mut self, + port: u16, + tunnel: &mut ActiveTunnel, + ) -> Result<(), AnyError> { + if port == CONTROL_PORT { + return Err(CannotForwardControlPort().into()); + } + + tunnel.remove_port(port).await?; + self.forwarded.remove(&port); + Ok(()) + } + + async fn process_forward( + &mut self, + port: u16, + tunnel: &mut ActiveTunnel, + ) -> Result { + if port == CONTROL_PORT { + return Err(CannotForwardControlPort().into()); + } + + if !self.forwarded.contains(&port) { + tunnel.add_port_tcp(port).await?; + self.forwarded.insert(port); + } + + tunnel.get_port_uri(port).await + } +} + +#[derive(Clone)] +pub struct PortForwarding { + tx: mpsc::Sender, +} + +impl PortForwarding { + pub async fn forward(&self, port: u16) -> Result { + let (tx, rx) = oneshot::channel(); + let req = PortForwardingRec::Forward(port, tx); + + if self.tx.send(req).await.is_err() { + return Err(ServerHasClosed().into()); + } + + match rx.await { + Ok(r) => r, + Err(_) => Err(ServerHasClosed().into()), + } + } + + pub async fn unforward(&self, port: u16) -> Result<(), AnyError> { + let (tx, rx) = oneshot::channel(); + let req = PortForwardingRec::Unforward(port, tx); + + if self.tx.send(req).await.is_err() { + return Err(ServerHasClosed().into()); + } + + match rx.await { + Ok(r) => r, + Err(_) => Err(ServerHasClosed().into()), + } + } +} diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs new file mode 100644 index 0000000000..8dd4012ed3 --- /dev/null +++ b/cli/src/tunnels/protocol.rs @@ -0,0 +1,249 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use std::collections::HashMap; + +use crate::{ + constants::{PROTOCOL_VERSION, VSCODE_CLI_VERSION}, + options::Quality, + update_service::Platform, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Debug)] +#[serde(tag = "method", content = "params", rename_all = "camelCase")] +#[allow(non_camel_case_types)] +pub enum ClientRequestMethod<'a> { + servermsg(RefServerMessageParams<'a>), + serverlog(ServerLog<'a>), + makehttpreq(HttpRequestParams<'a>), + version(VersionResponse), +} + +#[derive(Deserialize, Debug)] +pub struct HttpBodyParams { + #[serde(with = "serde_bytes")] + pub segment: Vec, + pub complete: bool, + pub req_id: u32, +} + +#[derive(Serialize, Debug)] +pub struct HttpRequestParams<'a> { + pub url: &'a str, + pub method: &'static str, + pub req_id: u32, +} + +#[derive(Deserialize, Debug)] +pub struct HttpHeadersParams { + pub status_code: u16, + pub headers: Vec<(String, String)>, + pub req_id: u32, +} + +#[derive(Deserialize, Debug)] +pub struct ForwardParams { + pub port: u16, +} + +#[derive(Deserialize, Debug)] +pub struct UnforwardParams { + pub port: u16, +} + +#[derive(Serialize)] +pub struct ForwardResult { + pub uri: String, +} + +#[derive(Deserialize, Debug)] +pub struct ServeParams { + pub socket_id: u16, + pub commit_id: Option, + pub quality: Quality, + pub extensions: Vec, + /// Optional preferred connection token. + #[serde(default)] + pub connection_token: Option, + #[serde(default)] + pub use_local_download: bool, + /// If true, the client and server should gzip servermsg's sent in either direction. + #[serde(default)] + pub compress: bool, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct EmptyObject {} + +#[derive(Serialize, Deserialize, Debug)] +pub struct UpdateParams { + pub do_update: bool, +} + +#[derive(Deserialize, Debug)] +pub struct ServerMessageParams { + pub i: u16, + #[serde(with = "serde_bytes")] + pub body: Vec, +} + +#[derive(Serialize, Debug)] +pub struct RefServerMessageParams<'a> { + pub i: u16, + #[serde(with = "serde_bytes")] + pub body: &'a [u8], +} + +#[derive(Serialize)] +pub struct UpdateResult { + pub up_to_date: bool, + pub did_update: bool, +} + +#[derive(Serialize, Debug)] +pub struct ToClientRequest<'a> { + pub id: Option, + #[serde(flatten)] + pub params: ClientRequestMethod<'a>, +} + +#[derive(Debug, Default, Serialize)] +pub struct ServerLog<'a> { + pub line: &'a str, + pub level: u8, +} + +#[derive(Serialize)] +pub struct GetHostnameResponse { + pub value: String, +} + +#[derive(Serialize)] +pub struct GetEnvResponse { + pub env: HashMap, + pub os_platform: &'static str, + pub os_release: String, +} + +#[derive(Deserialize)] +pub struct FsStatRequest { + pub path: String, +} + +#[derive(Serialize, Default)] +pub struct FsStatResponse { + pub exists: bool, + pub size: Option, + #[serde(rename = "type")] + pub kind: Option<&'static str>, +} + +#[derive(Deserialize, Debug)] +pub struct CallServerHttpParams { + pub path: String, + pub method: String, + pub headers: HashMap, + pub body: Option>, +} + +#[derive(Serialize)] +pub struct CallServerHttpResult { + pub status: u16, + #[serde(with = "serde_bytes")] + pub body: Vec, + pub headers: HashMap, +} + +#[derive(Serialize, Debug)] +pub struct VersionResponse { + pub version: &'static str, + pub protocol_version: u32, +} + +impl Default for VersionResponse { + fn default() -> Self { + Self { + version: VSCODE_CLI_VERSION.unwrap_or("dev"), + protocol_version: PROTOCOL_VERSION, + } + } +} + +#[derive(Deserialize)] +pub struct SpawnParams { + pub command: String, + pub args: Vec, + #[serde(default)] + pub cwd: Option, + #[serde(default)] + pub env: HashMap, +} + +#[derive(Deserialize)] +pub struct AcquireCliParams { + pub platform: Platform, + pub quality: Quality, + pub commit_id: Option, + #[serde(flatten)] + pub spawn: SpawnParams, +} + +#[derive(Serialize)] +pub struct SpawnResult { + pub message: String, + pub exit_code: i32, +} + +pub const METHOD_CHALLENGE_ISSUE: &str = "challenge_issue"; +pub const METHOD_CHALLENGE_VERIFY: &str = "challenge_verify"; + +#[derive(Serialize, Deserialize)] +pub struct ChallengeIssueResponse { + pub challenge: String, +} + +#[derive(Deserialize, Serialize)] +pub struct ChallengeVerifyParams { + pub response: String, +} + +pub mod singleton { + use crate::log; + use serde::{Deserialize, Serialize}; + + pub const METHOD_RESTART: &str = "restart"; + pub const METHOD_SHUTDOWN: &str = "shutdown"; + pub const METHOD_STATUS: &str = "status"; + pub const METHOD_LOG: &str = "log"; + pub const METHOD_LOG_REPLY_DONE: &str = "log_done"; + + #[derive(Serialize)] + pub struct LogMessage<'a> { + pub level: Option, + pub prefix: &'a str, + pub message: &'a str, + } + + #[derive(Deserialize)] + pub struct LogMessageOwned { + pub level: Option, + pub prefix: String, + pub message: String, + } + + #[derive(Serialize, Deserialize)] + pub struct Status { + pub tunnel: TunnelState, + } + + #[derive(Deserialize, Serialize, Debug)] + pub struct LogReplayFinished {} + + #[derive(Deserialize, Serialize, Debug)] + pub enum TunnelState { + Disconnected, + Connected { name: String }, + } +} diff --git a/cli/src/tunnels/server_bridge.rs b/cli/src/tunnels/server_bridge.rs new file mode 100644 index 0000000000..26215cd3b4 --- /dev/null +++ b/cli/src/tunnels/server_bridge.rs @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use super::socket_signal::{ClientMessageDecoder, ServerMessageSink}; +use crate::{ + async_pipe::{get_socket_rw_stream, socket_stream_split, AsyncPipeWriteHalf}, + util::errors::AnyError, +}; +use std::path::Path; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +pub struct ServerBridge { + write: AsyncPipeWriteHalf, + decoder: ClientMessageDecoder, +} + +const BUFFER_SIZE: usize = 65536; + +impl ServerBridge { + pub async fn new( + path: &Path, + mut target: ServerMessageSink, + decoder: ClientMessageDecoder, + ) -> Result { + let stream = get_socket_rw_stream(path).await?; + let (mut read, write) = socket_stream_split(stream); + + tokio::spawn(async move { + let mut read_buf = vec![0; BUFFER_SIZE]; + loop { + match read.read(&mut read_buf).await { + Err(_) => return, + Ok(0) => { + return; // EOF + } + Ok(s) => { + let send = target.server_message(&read_buf[..s]).await; + if send.is_err() { + return; + } + } + } + } + }); + + Ok(ServerBridge { write, decoder }) + } + + pub async fn write(&mut self, b: Vec) -> std::io::Result<()> { + let dec = self.decoder.decode(&b)?; + if !dec.is_empty() { + self.write.write_all(dec).await?; + } + Ok(()) + } + + pub async fn close(mut self) -> std::io::Result<()> { + self.write.shutdown().await?; + Ok(()) + } +} diff --git a/cli/src/tunnels/server_multiplexer.rs b/cli/src/tunnels/server_multiplexer.rs new file mode 100644 index 0000000000..83d4b8a95c --- /dev/null +++ b/cli/src/tunnels/server_multiplexer.rs @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::sync::Arc; + +use futures::future::join_all; + +use crate::log; + +use super::server_bridge::ServerBridge; + +type Inner = Arc>>>; + +struct ServerBridgeRec { + id: u16, + // bridge is removed when there's a write loop currently active + bridge: Option, + write_queue: Vec>, +} + +/// The ServerMultiplexer manages multiple server bridges and allows writing +/// to them in a thread-safe way. It is copy, sync, and clone. +#[derive(Clone)] +pub struct ServerMultiplexer { + inner: Inner, +} + +impl ServerMultiplexer { + pub fn new() -> Self { + Self { + inner: Arc::new(std::sync::Mutex::new(Some(Vec::new()))), + } + } + + /// Adds a new bridge to the multiplexer. + pub fn register(&self, id: u16, bridge: ServerBridge) { + let bridge_rec = ServerBridgeRec { + id, + bridge: Some(bridge), + write_queue: vec![], + }; + + let mut lock = self.inner.lock().unwrap(); + match &mut *lock { + Some(server_bridges) => (*server_bridges).push(bridge_rec), + None => *lock = Some(vec![bridge_rec]), + } + } + + /// Removes a server bridge by ID. + pub fn remove(&self, id: u16) { + let mut lock = self.inner.lock().unwrap(); + if let Some(bridges) = &mut *lock { + bridges.retain(|sb| sb.id != id); + } + } + + /// Handle an incoming server message. This is synchronous and uses a 'write loop' + /// to ensure message order is preserved exactly, which is necessary for compression. + /// Returns false if there was no server with the given bridge_id. + pub fn write_message(&self, log: &log::Logger, bridge_id: u16, message: Vec) -> bool { + let mut lock = self.inner.lock().unwrap(); + + let bridges = match &mut *lock { + Some(sb) => sb, + None => return false, + }; + + let record = match bridges.iter_mut().find(|b| b.id == bridge_id) { + Some(sb) => sb, + None => return false, + }; + + record.write_queue.push(message); + if let Some(bridge) = record.bridge.take() { + let bridges_lock = self.inner.clone(); + let log = log.clone(); + tokio::spawn(write_loop(log, record.id, bridge, bridges_lock)); + } + + true + } + + /// Disposes all running server bridges. + pub async fn dispose(&self) { + let bridges = { + let mut lock = self.inner.lock().unwrap(); + lock.take() + }; + + let bridges = match bridges { + Some(b) => b, + None => return, + }; + + join_all( + bridges + .into_iter() + .filter_map(|b| b.bridge) + .map(|b| b.close()), + ) + .await; + } +} + +/// Write loop started by `handle_server_message`. It takes the ServerBridge, and +/// runs until there's no more items in the 'write queue'. At that point, if the +/// record still exists in the bridges_lock (i.e. we haven't shut down), it'll +/// return the ServerBridge so that the next handle_server_message call starts +/// the loop again. Otherwise, it'll close the bridge. +async fn write_loop(log: log::Logger, id: u16, mut bridge: ServerBridge, bridges_lock: Inner) { + let mut items_vec = vec![]; + loop { + { + let mut lock = bridges_lock.lock().unwrap(); + let server_bridges = match &mut *lock { + Some(sb) => sb, + None => break, + }; + + let bridge_rec = match server_bridges.iter_mut().find(|b| id == b.id) { + Some(b) => b, + None => break, + }; + + if bridge_rec.write_queue.is_empty() { + bridge_rec.bridge = Some(bridge); + return; + } + + std::mem::swap(&mut bridge_rec.write_queue, &mut items_vec); + } + + for item in items_vec.drain(..) { + if let Err(e) = bridge.write(item).await { + warning!(log, "Error writing to server: {:?}", e); + break; + } + } + } + + bridge.close().await.ok(); // got here from `break` above, meaning our record got cleared. Close the bridge if so +} diff --git a/cli/src/tunnels/service.rs b/cli/src/tunnels/service.rs new file mode 100644 index 0000000000..864ae7b9c5 --- /dev/null +++ b/cli/src/tunnels/service.rs @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::path::{Path, PathBuf}; + +use async_trait::async_trait; + +use crate::log; +use crate::state::LauncherPaths; +use crate::util::errors::{wrap, AnyError}; +use crate::util::io::{tailf, TailEvent}; + +pub const SERVICE_LOG_FILE_NAME: &str = "tunnel-service.log"; + +#[async_trait] +pub trait ServiceContainer: Send { + async fn run_service( + &mut self, + log: log::Logger, + launcher_paths: LauncherPaths, + ) -> Result<(), AnyError>; +} + +#[async_trait] +pub trait ServiceManager { + /// Registers the current executable as a service to run with the given set + /// of arguments. + async fn register(&self, exe: PathBuf, args: &[&str]) -> Result<(), AnyError>; + + /// Runs the service using the given handle. The executable *must not* take + /// any action which may fail prior to calling this to ensure service + /// states may update. + async fn run( + self, + launcher_paths: LauncherPaths, + handle: impl 'static + ServiceContainer, + ) -> Result<(), AnyError>; + + /// Show logs from the running service to standard out. + async fn show_logs(&self) -> Result<(), AnyError>; + + /// Unregisters the current executable as a service. + async fn unregister(&self) -> Result<(), AnyError>; +} + +#[cfg(target_os = "windows")] +pub type ServiceManagerImpl = super::service_windows::WindowsService; + +#[cfg(target_os = "linux")] +pub type ServiceManagerImpl = super::service_linux::SystemdService; + +#[cfg(target_os = "macos")] +pub type ServiceManagerImpl = super::service_macos::LaunchdService; + +#[allow(unreachable_code)] +#[allow(unused_variables)] +pub fn create_service_manager(log: log::Logger, paths: &LauncherPaths) -> ServiceManagerImpl { + #[cfg(target_os = "macos")] + { + super::service_macos::LaunchdService::new(log, paths) + } + #[cfg(target_os = "windows")] + { + super::service_windows::WindowsService::new(log, paths) + } + #[cfg(target_os = "linux")] + { + super::service_linux::SystemdService::new(log, paths.clone()) + } +} + +#[allow(dead_code)] // unused on Linux +pub(crate) async fn tail_log_file(log_file: &Path) -> Result<(), AnyError> { + if !log_file.exists() { + println!("The tunnel service has not started yet."); + return Ok(()); + } + + let file = std::fs::File::open(log_file).map_err(|e| wrap(e, "error opening log file"))?; + let mut rx = tailf(file, 20); + while let Some(line) = rx.recv().await { + match line { + TailEvent::Line(l) => print!("{}", l), + TailEvent::Reset => println!("== Tunnel service restarted =="), + TailEvent::Err(e) => return Err(wrap(e, "error reading log file").into()), + } + } + + Ok(()) +} diff --git a/cli/src/tunnels/service_linux.rs b/cli/src/tunnels/service_linux.rs new file mode 100644 index 0000000000..4d4a937933 --- /dev/null +++ b/cli/src/tunnels/service_linux.rs @@ -0,0 +1,240 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + fs::File, + io::{self, Write}, + path::PathBuf, + process::Command, +}; + +use async_trait::async_trait; +use zbus::{dbus_proxy, zvariant, Connection}; + +use crate::{ + constants::{APPLICATION_NAME, PRODUCT_NAME_LONG}, + log, + state::LauncherPaths, + util::errors::{wrap, AnyError}, +}; + +use super::ServiceManager; + +pub struct SystemdService { + log: log::Logger, + service_file: PathBuf, +} + +impl SystemdService { + pub fn new(log: log::Logger, paths: LauncherPaths) -> Self { + Self { + log, + service_file: paths.root().join(SystemdService::service_name_string()), + } + } +} + +impl SystemdService { + async fn connect() -> Result { + let connection = Connection::session() + .await + .map_err(|e| wrap(e, "error creating dbus session"))?; + Ok(connection) + } + + async fn proxy(connection: &Connection) -> Result, AnyError> { + let proxy = SystemdManagerDbusProxy::new(connection) + .await + .map_err(|e| { + wrap( + e, + "error connecting to systemd, you may need to re-run with sudo:", + ) + })?; + + Ok(proxy) + } + + fn service_path_string(&self) -> String { + self.service_file.as_os_str().to_string_lossy().to_string() + } + + fn service_name_string() -> String { + format!("{}-tunnel.service", APPLICATION_NAME) + } +} + +#[async_trait] +impl ServiceManager for SystemdService { + async fn register( + &self, + exe: std::path::PathBuf, + args: &[&str], + ) -> Result<(), crate::util::errors::AnyError> { + let connection = SystemdService::connect().await?; + let proxy = SystemdService::proxy(&connection).await?; + + write_systemd_service_file(&self.service_file, exe, args) + .map_err(|e| wrap(e, "error creating service file"))?; + + proxy + .link_unit_files( + vec![self.service_path_string()], + /* 'runtime only'= */ false, + /* replace existing = */ true, + ) + .await + .map_err(|e| wrap(e, "error registering service"))?; + + info!(self.log, "Successfully registered service..."); + + // note: enablement is implicit in recent systemd version, but required for older systems + // https://github.com/microsoft/vscode/issues/167489#issuecomment-1331222826 + proxy + .enable_unit_files( + vec![SystemdService::service_name_string()], + /* 'runtime only'= */ false, + /* replace existing = */ true, + ) + .await + .map_err(|e| wrap(e, "error enabling unit files for service"))?; + + info!(self.log, "Successfully enabled unit files..."); + + proxy + .start_unit(SystemdService::service_name_string(), "replace".to_string()) + .await + .map_err(|e| wrap(e, "error starting service"))?; + + info!(self.log, "Tunnel service successfully started"); + + Ok(()) + } + + async fn run( + self, + launcher_paths: crate::state::LauncherPaths, + mut handle: impl 'static + super::ServiceContainer, + ) -> Result<(), crate::util::errors::AnyError> { + handle.run_service(self.log, launcher_paths).await + } + + async fn show_logs(&self) -> Result<(), AnyError> { + // show the systemctl status header... + Command::new("systemctl") + .args([ + "--user", + "status", + "-n", + "0", + &SystemdService::service_name_string(), + ]) + .status() + .map(|s| s.code().unwrap_or(1)) + .map_err(|e| wrap(e, "error running systemctl"))?; + + // then follow log files + Command::new("journalctl") + .args(["--user", "-f", "-u", &SystemdService::service_name_string()]) + .status() + .map(|s| s.code().unwrap_or(1)) + .map_err(|e| wrap(e, "error running journalctl"))?; + Ok(()) + } + + async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> { + let connection = SystemdService::connect().await?; + let proxy = SystemdService::proxy(&connection).await?; + + proxy + .stop_unit(SystemdService::service_name_string(), "replace".to_string()) + .await + .map_err(|e| wrap(e, "error unregistering service"))?; + + info!(self.log, "Successfully stopped service..."); + + proxy + .disable_unit_files( + vec![SystemdService::service_name_string()], + /* 'runtime only'= */ false, + ) + .await + .map_err(|e| wrap(e, "error unregistering service"))?; + + info!(self.log, "Tunnel service uninstalled"); + + Ok(()) + } +} + +fn write_systemd_service_file( + path: &PathBuf, + exe: std::path::PathBuf, + args: &[&str], +) -> io::Result<()> { + let mut f = File::create(path)?; + write!( + &mut f, + "[Unit]\n\ + Description={} Tunnel\n\ + After=network.target\n\ + StartLimitIntervalSec=0\n\ + \n\ + [Service]\n\ + Type=simple\n\ + Restart=always\n\ + RestartSec=10\n\ + ExecStart={} \"{}\"\n\ + \n\ + [Install]\n\ + WantedBy=default.target\n\ + ", + PRODUCT_NAME_LONG, + exe.into_os_string().to_string_lossy(), + args.join("\" \"") + )?; + Ok(()) +} + +/// Minimal implementation of systemd types for the services we need. The full +/// definition can be found on any systemd machine with the command: +/// +/// gdbus introspect --system --dest org.freedesktop.systemd1 --object-path /org/freedesktop/systemd1 +/// +/// See docs here: https://www.freedesktop.org/software/systemd/man/org.freedesktop.systemd1.html +#[dbus_proxy( + interface = "org.freedesktop.systemd1.Manager", + gen_blocking = false, + default_service = "org.freedesktop.systemd1", + default_path = "/org/freedesktop/systemd1" +)] +trait SystemdManagerDbus { + #[dbus_proxy(name = "EnableUnitFiles")] + fn enable_unit_files( + &self, + files: Vec, + runtime: bool, + force: bool, + ) -> zbus::Result<(bool, Vec<(String, String, String)>)>; + + fn link_unit_files( + &self, + files: Vec, + runtime: bool, + force: bool, + ) -> zbus::Result>; + + fn disable_unit_files( + &self, + files: Vec, + runtime: bool, + ) -> zbus::Result>; + + #[dbus_proxy(name = "StartUnit")] + fn start_unit(&self, name: String, mode: String) -> zbus::Result; + + #[dbus_proxy(name = "StopUnit")] + fn stop_unit(&self, name: String, mode: String) -> zbus::Result; +} diff --git a/cli/src/tunnels/service_macos.rs b/cli/src/tunnels/service_macos.rs new file mode 100644 index 0000000000..9bb6d308e1 --- /dev/null +++ b/cli/src/tunnels/service_macos.rs @@ -0,0 +1,163 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + fs::{remove_file, File}, + io::{self, Write}, + path::{Path, PathBuf}, +}; + +use async_trait::async_trait; + +use crate::{ + constants::APPLICATION_NAME, + log, + state::LauncherPaths, + util::{ + command::capture_command_and_check_status, + errors::{wrap, AnyError, CodeError, MissingHomeDirectory}, + }, +}; + +use super::{service::tail_log_file, ServiceManager}; + +pub struct LaunchdService { + log: log::Logger, + log_file: PathBuf, +} + +impl LaunchdService { + pub fn new(log: log::Logger, paths: &LauncherPaths) -> Self { + Self { + log, + log_file: paths.service_log_file(), + } + } +} + +#[async_trait] +impl ServiceManager for LaunchdService { + async fn register( + &self, + exe: std::path::PathBuf, + args: &[&str], + ) -> Result<(), crate::util::errors::AnyError> { + let service_file = get_service_file_path()?; + write_service_file(&service_file, &self.log_file, exe, args) + .map_err(|e| wrap(e, "error creating service file"))?; + + info!(self.log, "Successfully registered service..."); + + capture_command_and_check_status( + "launchctl", + &["load", service_file.as_os_str().to_string_lossy().as_ref()], + ) + .await?; + + capture_command_and_check_status("launchctl", &["start", &get_service_label()]).await?; + + info!(self.log, "Tunnel service successfully started"); + + Ok(()) + } + + async fn show_logs(&self) -> Result<(), AnyError> { + tail_log_file(&self.log_file).await + } + + async fn run( + self, + launcher_paths: crate::state::LauncherPaths, + mut handle: impl 'static + super::ServiceContainer, + ) -> Result<(), crate::util::errors::AnyError> { + handle.run_service(self.log, launcher_paths).await + } + + async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> { + let service_file = get_service_file_path()?; + + match capture_command_and_check_status("launchctl", &["stop", &get_service_label()]).await { + Ok(_) => {} + // status 3 == "no such process" + Err(CodeError::CommandFailed { code, .. }) if code == 3 => {} + Err(e) => return Err(wrap(e, "error stopping service").into()), + }; + + info!(self.log, "Successfully stopped service..."); + + capture_command_and_check_status( + "launchctl", + &[ + "unload", + service_file.as_os_str().to_string_lossy().as_ref(), + ], + ) + .await?; + + info!(self.log, "Tunnel service uninstalled"); + + if let Ok(f) = get_service_file_path() { + remove_file(f).ok(); + } + + Ok(()) + } +} + +fn get_service_label() -> String { + format!("com.visualstudio.{}.tunnel", APPLICATION_NAME) +} + +fn get_service_file_path() -> Result { + match dirs::home_dir() { + Some(mut d) => { + d.push(format!("{}.plist", get_service_label())); + Ok(d) + } + None => Err(MissingHomeDirectory()), + } +} + +fn write_service_file( + path: &PathBuf, + log_file: &Path, + exe: std::path::PathBuf, + args: &[&str], +) -> io::Result<()> { + let mut f = File::create(path)?; + let log_file = log_file.as_os_str().to_string_lossy(); + // todo: we may be able to skip file logging and use the ASL instead + // if/when we no longer need to support older macOS versions. + write!( + &mut f, + "\n\ + \n\ + \n\ + \n\ + Label\n\ + {}\n\ + LimitLoadToSessionType\n\ + Aqua\n\ + ProgramArguments\n\ + \n\ + {}\n\ + {}\n\ + \n\ + KeepAlive\n\ + \n\ + StandardErrorPath\n\ + {}\n\ + StandardOutPath\n\ + {}\n\ + \n\ + ", + get_service_label(), + exe.into_os_string().to_string_lossy(), + args.join(""), + log_file, + log_file + )?; + Ok(()) +} diff --git a/cli/src/tunnels/service_windows.rs b/cli/src/tunnels/service_windows.rs new file mode 100644 index 0000000000..f71492b79c --- /dev/null +++ b/cli/src/tunnels/service_windows.rs @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use async_trait::async_trait; +use shell_escape::windows::escape as shell_escape; +use std::{ + path::PathBuf, + process::{Command, Stdio}, +}; +use winreg::{enums::HKEY_CURRENT_USER, RegKey}; + +use crate::{ + constants::TUNNEL_ACTIVITY_NAME, + log, + state::LauncherPaths, + tunnels::{protocol, singleton_client::do_single_rpc_call}, + util::errors::{wrap, wrapdbg, AnyError}, +}; + +use super::service::{tail_log_file, ServiceContainer, ServiceManager as CliServiceManager}; + +pub struct WindowsService { + log: log::Logger, + tunnel_lock: PathBuf, + log_file: PathBuf, +} + +impl WindowsService { + pub fn new(log: log::Logger, paths: &LauncherPaths) -> Self { + Self { + log, + tunnel_lock: paths.tunnel_lockfile(), + log_file: paths.service_log_file(), + } + } + + fn open_key() -> Result { + RegKey::predef(HKEY_CURRENT_USER) + .create_subkey(r"Software\Microsoft\Windows\CurrentVersion\Run") + .map_err(|e| wrap(e, "error opening run registry key").into()) + .map(|(key, _)| key) + } +} + +#[async_trait] +impl CliServiceManager for WindowsService { + async fn register(&self, exe: std::path::PathBuf, args: &[&str]) -> Result<(), AnyError> { + let key = WindowsService::open_key()?; + + let mut reg_str = String::new(); + let mut cmd = Command::new(&exe); + reg_str.push_str(shell_escape(exe.to_string_lossy()).as_ref()); + + let mut add_arg = |arg: &str| { + reg_str.push(' '); + reg_str.push_str(shell_escape((*arg).into()).as_ref()); + cmd.arg(arg); + }; + + for arg in args { + add_arg(arg); + } + + add_arg("--log-to-file"); + add_arg(self.log_file.to_string_lossy().as_ref()); + + key.set_value(TUNNEL_ACTIVITY_NAME, ®_str) + .map_err(|e| AnyError::from(wrapdbg(e, "error setting registry key")))?; + + info!(self.log, "Successfully registered service..."); + + cmd.stderr(Stdio::null()); + cmd.stdout(Stdio::null()); + cmd.stdin(Stdio::null()); + cmd.spawn() + .map_err(|e| wrapdbg(e, "error starting service"))?; + + info!(self.log, "Tunnel service successfully started"); + Ok(()) + } + + async fn show_logs(&self) -> Result<(), AnyError> { + tail_log_file(&self.log_file).await + } + + async fn run( + self, + launcher_paths: LauncherPaths, + mut handle: impl 'static + ServiceContainer, + ) -> Result<(), AnyError> { + handle.run_service(self.log, launcher_paths).await + } + + async fn unregister(&self) -> Result<(), AnyError> { + let key = WindowsService::open_key()?; + key.delete_value(TUNNEL_ACTIVITY_NAME) + .map_err(|e| AnyError::from(wrap(e, "error deleting registry key")))?; + info!(self.log, "Tunnel service uninstalled"); + + let r = do_single_rpc_call::<_, ()>( + &self.tunnel_lock, + self.log.clone(), + protocol::singleton::METHOD_SHUTDOWN, + protocol::EmptyObject {}, + ) + .await; + + if r.is_err() { + warning!(self.log, "The tunnel service has been unregistered, but we couldn't find a running tunnel process. You may need to restart or log out and back in to fully stop the tunnel."); + } else { + info!(self.log, "Successfully shut down running tunnel."); + } + + Ok(()) + } +} diff --git a/cli/src/tunnels/shutdown_signal.rs b/cli/src/tunnels/shutdown_signal.rs new file mode 100644 index 0000000000..e537d38081 --- /dev/null +++ b/cli/src/tunnels/shutdown_signal.rs @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use futures::{stream::FuturesUnordered, StreamExt}; +use std::{fmt, path::PathBuf}; +use sysinfo::Pid; + +use crate::util::{ + machine::{wait_until_exe_deleted, wait_until_process_exits}, + sync::{new_barrier, Barrier, Receivable}, +}; + +/// Describes the signal to manully stop the server +#[derive(Copy, Clone)] +pub enum ShutdownSignal { + CtrlC, + ParentProcessKilled(Pid), + ExeUninstalled, + ServiceStopped, + RpcShutdownRequested, + RpcRestartRequested, +} + +impl fmt::Display for ShutdownSignal { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ShutdownSignal::CtrlC => write!(f, "Ctrl-C received"), + ShutdownSignal::ParentProcessKilled(p) => { + write!(f, "Parent process {} no longer exists", p) + } + ShutdownSignal::ExeUninstalled => { + write!(f, "Executable no longer exists") + } + ShutdownSignal::ServiceStopped => write!(f, "Service stopped"), + ShutdownSignal::RpcShutdownRequested => write!(f, "RPC client requested shutdown"), + ShutdownSignal::RpcRestartRequested => { + write!(f, "RPC client requested a tunnel restart") + } + } + } +} + +pub enum ShutdownRequest { + CtrlC, + ParentProcessKilled(Pid), + ExeUninstalled(PathBuf), + Derived(Box + Send>), +} + +impl ShutdownRequest { + async fn wait(self) -> Option { + match self { + ShutdownRequest::CtrlC => { + let ctrl_c = tokio::signal::ctrl_c(); + ctrl_c.await.ok(); + Some(ShutdownSignal::CtrlC) + } + ShutdownRequest::ParentProcessKilled(pid) => { + wait_until_process_exits(pid, 2000).await; + Some(ShutdownSignal::ParentProcessKilled(pid)) + } + ShutdownRequest::ExeUninstalled(exe_path) => { + wait_until_exe_deleted(&exe_path, 2000).await; + Some(ShutdownSignal::ExeUninstalled) + } + ShutdownRequest::Derived(mut rx) => rx.recv_msg().await, + } + } + /// Creates a receiver channel sent to once any of the signals are received. + /// Note: does not handle ServiceStopped + pub fn create_rx( + signals: impl IntoIterator, + ) -> Barrier { + let (barrier, opener) = new_barrier(); + let futures = signals + .into_iter() + .map(|s| s.wait()) + .collect::>(); + + tokio::spawn(async move { + if let Some(s) = futures.filter_map(futures::future::ready).next().await { + opener.open(s); + } + }); + + barrier + } +} diff --git a/cli/src/tunnels/singleton_client.rs b/cli/src/tunnels/singleton_client.rs new file mode 100644 index 0000000000..fc1bb9ab20 --- /dev/null +++ b/cli/src/tunnels/singleton_client.rs @@ -0,0 +1,190 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + path::Path, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, +}; + +use const_format::concatcp; +use tokio::sync::mpsc; + +use crate::{ + async_pipe::{socket_stream_split, AsyncPipe}, + constants::IS_INTERACTIVE_CLI, + json_rpc::{new_json_rpc, start_json_rpc, JsonRpcSerializer}, + log, + rpc::RpcCaller, + singleton::connect_as_client, + tunnels::{code_server::print_listening, protocol::EmptyObject}, + util::{errors::CodeError, sync::Barrier}, +}; + +use super::{ + protocol, + shutdown_signal::{ShutdownRequest, ShutdownSignal}, +}; + +pub struct SingletonClientArgs { + pub log: log::Logger, + pub stream: AsyncPipe, + pub shutdown: Barrier, +} + +struct SingletonServerContext { + log: log::Logger, + exit_entirely: Arc, + caller: RpcCaller, +} + +const CONTROL_INSTRUCTIONS_COMMON: &str = + "Connected to an existing tunnel process running on this machine."; + +const CONTROL_INSTRUCTIONS_INTERACTIVE: &str = concatcp!( + CONTROL_INSTRUCTIONS_COMMON, + " You can press: + +- \"x\" + Enter to stop the tunnel and exit +- \"r\" + Enter to restart the tunnel +- Ctrl+C to detach +" +); + +/// Serves a client singleton. Returns true if the process should exit after +/// this returns, instead of trying to start a tunnel. +pub async fn start_singleton_client(args: SingletonClientArgs) -> bool { + let mut rpc = new_json_rpc(); + let (msg_tx, msg_rx) = mpsc::unbounded_channel(); + let exit_entirely = Arc::new(AtomicBool::new(false)); + + debug!( + args.log, + "An existing tunnel is running on this machine, connecting to it..." + ); + + if *IS_INTERACTIVE_CLI { + let stdin_handle = rpc.get_caller(msg_tx.clone()); + thread::spawn(move || { + let mut input = String::new(); + loop { + input.truncate(0); + match std::io::stdin().read_line(&mut input) { + Err(_) | Ok(0) => return, // EOF or not a tty + _ => {} + }; + + match input.chars().next().map(|c| c.to_ascii_lowercase()) { + Some('x') => { + stdin_handle.notify(protocol::singleton::METHOD_SHUTDOWN, EmptyObject {}); + return; + } + Some('r') => { + stdin_handle.notify(protocol::singleton::METHOD_RESTART, EmptyObject {}); + } + Some(_) | None => {} + } + } + }); + } + + let caller = rpc.get_caller(msg_tx); + let mut rpc = rpc.methods(SingletonServerContext { + log: args.log.clone(), + exit_entirely: exit_entirely.clone(), + caller, + }); + + rpc.register_sync(protocol::singleton::METHOD_SHUTDOWN, |_: EmptyObject, c| { + c.exit_entirely.store(true, Ordering::SeqCst); + Ok(()) + }); + + rpc.register_async( + protocol::singleton::METHOD_LOG_REPLY_DONE, + |_: EmptyObject, c| async move { + c.log.result(if *IS_INTERACTIVE_CLI { + CONTROL_INSTRUCTIONS_INTERACTIVE + } else { + CONTROL_INSTRUCTIONS_COMMON + }); + + let res = c.caller.call::<_, _, protocol::singleton::Status>( + protocol::singleton::METHOD_STATUS, + protocol::EmptyObject {}, + ); + + // we want to ensure the "listening" string always gets printed for + // consumers (i.e. VS Code). Ask for it. If the tunnel is not currently + // connected though, it will be soon, and that'll be in the log replays. + if let Ok(Ok(s)) = res.await { + if let protocol::singleton::TunnelState::Connected { name } = s.tunnel { + print_listening(&c.log, &name); + } + } + + Ok(()) + }, + ); + + rpc.register_sync( + protocol::singleton::METHOD_LOG, + |log: protocol::singleton::LogMessageOwned, c| { + match log.level { + Some(level) => c.log.emit(level, &format!("{}{}", log.prefix, log.message)), + None => c.log.result(format!("{}{}", log.prefix, log.message)), + } + Ok(()) + }, + ); + + let (read, write) = socket_stream_split(args.stream); + let _ = start_json_rpc(rpc.build(args.log), read, write, msg_rx, args.shutdown).await; + + exit_entirely.load(Ordering::SeqCst) +} + +pub async fn do_single_rpc_call< + P: serde::Serialize + 'static, + R: serde::de::DeserializeOwned + Send + 'static, +>( + lock_file: &Path, + log: log::Logger, + method: &'static str, + params: P, +) -> Result { + let client = match connect_as_client(lock_file).await { + Ok(p) => p, + Err(CodeError::SingletonLockfileOpenFailed(_)) + | Err(CodeError::SingletonLockedProcessExited(_)) => { + return Err(CodeError::NoRunningTunnel); + } + Err(e) => return Err(e), + }; + + let (msg_tx, msg_rx) = mpsc::unbounded_channel(); + let mut rpc = new_json_rpc(); + let caller = rpc.get_caller(msg_tx); + let (read, write) = socket_stream_split(client); + + let rpc = tokio::spawn(async move { + start_json_rpc( + rpc.methods(()).build(log), + read, + write, + msg_rx, + ShutdownRequest::create_rx([ShutdownRequest::CtrlC]), + ) + .await + .unwrap(); + }); + + let r = caller.call(method, params).await.unwrap(); + rpc.abort(); + r.map_err(CodeError::TunnelRpcCallFailed) +} diff --git a/cli/src/tunnels/singleton_server.rs b/cli/src/tunnels/singleton_server.rs new file mode 100644 index 0000000000..e7ed4b0917 --- /dev/null +++ b/cli/src/tunnels/singleton_server.rs @@ -0,0 +1,263 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + pin::Pin, + sync::{Arc, Mutex}, +}; + +use super::{ + code_server::CodeServerArgs, + control_server::ServerTermination, + dev_tunnels::ActiveTunnel, + protocol, + shutdown_signal::{ShutdownRequest, ShutdownSignal}, +}; +use crate::{ + async_pipe::socket_stream_split, + json_rpc::{new_json_rpc, start_json_rpc, JsonRpcSerializer}, + log, + rpc::{RpcCaller, RpcDispatcher}, + singleton::SingletonServer, + state::LauncherPaths, + tunnels::code_server::print_listening, + update_service::Platform, + util::{ + errors::{AnyError, CodeError}, + ring_buffer::RingBuffer, + sync::{Barrier, ConcatReceivable}, + }, +}; +use futures::future::Either; +use tokio::{ + pin, + sync::{broadcast, mpsc}, + task::JoinHandle, +}; + +pub struct SingletonServerArgs<'a> { + pub server: &'a mut RpcServer, + pub log: log::Logger, + pub tunnel: ActiveTunnel, + pub paths: &'a LauncherPaths, + pub code_server_args: &'a CodeServerArgs, + pub platform: Platform, + pub shutdown: Barrier, + pub log_broadcast: &'a BroadcastLogSink, +} + +#[derive(Clone)] +struct SingletonServerContext { + log: log::Logger, + shutdown_tx: broadcast::Sender, + broadcast_tx: broadcast::Sender>, + current_name: Arc>>, +} + +pub struct RpcServer { + fut: JoinHandle>, + shutdown_broadcast: broadcast::Sender, + current_name: Arc>>, +} + +pub fn make_singleton_server( + log_broadcast: BroadcastLogSink, + log: log::Logger, + server: SingletonServer, + shutdown_rx: Barrier, +) -> RpcServer { + let (shutdown_broadcast, _) = broadcast::channel(4); + let rpc = new_json_rpc(); + + let current_name = Arc::new(Mutex::new(None)); + let mut rpc = rpc.methods(SingletonServerContext { + log: log.clone(), + shutdown_tx: shutdown_broadcast.clone(), + broadcast_tx: log_broadcast.get_brocaster(), + current_name: current_name.clone(), + }); + + rpc.register_sync( + protocol::singleton::METHOD_RESTART, + |_: protocol::EmptyObject, ctx| { + info!(ctx.log, "restarting tunnel after client request"); + let _ = ctx.shutdown_tx.send(ShutdownSignal::RpcRestartRequested); + Ok(()) + }, + ); + + rpc.register_sync( + protocol::singleton::METHOD_STATUS, + |_: protocol::EmptyObject, c| { + Ok(protocol::singleton::Status { + tunnel: match c.current_name.lock().unwrap().clone() { + Some(name) => protocol::singleton::TunnelState::Connected { name }, + None => protocol::singleton::TunnelState::Disconnected, + }, + }) + }, + ); + + rpc.register_sync( + protocol::singleton::METHOD_SHUTDOWN, + |_: protocol::EmptyObject, ctx| { + info!( + ctx.log, + "closing tunnel and all clients after a shutdown request" + ); + let _ = ctx.broadcast_tx.send(RpcCaller::serialize_notify( + &JsonRpcSerializer {}, + protocol::singleton::METHOD_SHUTDOWN, + protocol::EmptyObject {}, + )); + let _ = ctx.shutdown_tx.send(ShutdownSignal::RpcShutdownRequested); + Ok(()) + }, + ); + + // we tokio spawn instead of keeping a future, since we want it to progress + // even outside of the start_singleton_server loop (i.e. while the tunnel restarts) + let fut = tokio::spawn(async move { + serve_singleton_rpc(log_broadcast, server, rpc.build(log), shutdown_rx).await + }); + RpcServer { + shutdown_broadcast, + current_name, + fut, + } +} + +pub async fn start_singleton_server<'a>( + args: SingletonServerArgs<'_>, +) -> Result { + let shutdown_rx = ShutdownRequest::create_rx([ + ShutdownRequest::Derived(Box::new(args.server.shutdown_broadcast.subscribe())), + ShutdownRequest::Derived(Box::new(args.shutdown.clone())), + ]); + + { + print_listening(&args.log, &args.tunnel.name); + let mut name = args.server.current_name.lock().unwrap(); + *name = Some(args.tunnel.name.clone()) + } + + let serve_fut = super::serve( + &args.log, + args.tunnel, + args.paths, + args.code_server_args, + args.platform, + shutdown_rx, + ); + + pin!(serve_fut); + + match futures::future::select(Pin::new(&mut args.server.fut), &mut serve_fut).await { + Either::Left((rpc_result, fut)) => { + // the rpc server will only end as a result of a graceful shutdown, or + // with an error. Return the result of the eventual shutdown of the + // control server. + rpc_result.unwrap()?; + fut.await + } + Either::Right((ctrl_result, _)) => ctrl_result, + } +} + +async fn serve_singleton_rpc( + log_broadcast: BroadcastLogSink, + mut server: SingletonServer, + dispatcher: RpcDispatcher, + shutdown_rx: Barrier, +) -> Result<(), CodeError> { + let mut own_shutdown = shutdown_rx.clone(); + let shutdown_fut = own_shutdown.wait(); + pin!(shutdown_fut); + + loop { + let cnx = tokio::select! { + c = server.accept() => c?, + _ = &mut shutdown_fut => return Ok(()), + }; + + let (read, write) = socket_stream_split(cnx); + let dispatcher = dispatcher.clone(); + let msg_rx = log_broadcast.replay_and_subscribe(); + let shutdown_rx = shutdown_rx.clone(); + tokio::spawn(async move { + let _ = start_json_rpc(dispatcher.clone(), read, write, msg_rx, shutdown_rx).await; + }); + } +} + +/// Log sink that can broadcast and replay log events. Used for transmitting +/// logs from the singleton to all clients. This should be created and injected +/// into other services, like the tunnel, before `start_singleton_server` +/// is called. +#[derive(Clone)] +pub struct BroadcastLogSink { + recent: Arc>>>, + tx: broadcast::Sender>, +} + +impl Default for BroadcastLogSink { + fn default() -> Self { + Self::new() + } +} + +impl BroadcastLogSink { + pub fn new() -> Self { + let (tx, _) = broadcast::channel(64); + Self { + tx, + recent: Arc::new(Mutex::new(RingBuffer::new(50))), + } + } + + fn get_brocaster(&self) -> broadcast::Sender> { + self.tx.clone() + } + + fn replay_and_subscribe( + &self, + ) -> ConcatReceivable, mpsc::UnboundedReceiver>, broadcast::Receiver>> { + let (log_replay_tx, log_replay_rx) = mpsc::unbounded_channel(); + + for log in self.recent.lock().unwrap().iter() { + let _ = log_replay_tx.send(log.clone()); + } + + let _ = log_replay_tx.send(RpcCaller::serialize_notify( + &JsonRpcSerializer {}, + protocol::singleton::METHOD_LOG_REPLY_DONE, + protocol::EmptyObject {}, + )); + + ConcatReceivable::new(log_replay_rx, self.tx.subscribe()) + } +} + +impl log::LogSink for BroadcastLogSink { + fn write_log(&self, level: log::Level, prefix: &str, message: &str) { + let s = JsonRpcSerializer {}; + let serialized = RpcCaller::serialize_notify( + &s, + protocol::singleton::METHOD_LOG, + protocol::singleton::LogMessage { + level: Some(level), + prefix, + message, + }, + ); + + let _ = self.tx.send(serialized.clone()); + self.recent.lock().unwrap().push(serialized); + } + + fn write_result(&self, message: &str) { + self.write_log(log::Level::Info, "", message); + } +} diff --git a/cli/src/tunnels/socket_signal.rs b/cli/src/tunnels/socket_signal.rs new file mode 100644 index 0000000000..2817d2dc46 --- /dev/null +++ b/cli/src/tunnels/socket_signal.rs @@ -0,0 +1,290 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use serde::Serialize; +use tokio::sync::mpsc; + +use crate::msgpack_rpc::MsgPackCaller; + +use super::{ + protocol::{ClientRequestMethod, RefServerMessageParams, ToClientRequest}, + server_multiplexer::ServerMultiplexer, +}; + +pub struct CloseReason(pub String); + +pub enum SocketSignal { + /// Signals bytes to send to the socket. + Send(Vec), + /// Closes the socket (e.g. as a result of an error) + CloseWith(CloseReason), +} + +impl From> for SocketSignal { + fn from(v: Vec) -> Self { + SocketSignal::Send(v) + } +} + +impl SocketSignal { + pub fn from_message(msg: &T) -> Self + where + T: Serialize + ?Sized, + { + SocketSignal::Send(rmp_serde::to_vec_named(msg).unwrap()) + } +} + +/// todo@connor4312: cleanup once everything is moved to rpc standard interfaces +#[allow(dead_code)] +pub enum ServerMessageDestination { + Channel(mpsc::Sender), + Rpc(MsgPackCaller), +} + +/// Struct that handling sending or closing a connected server socket. +pub struct ServerMessageSink { + id: u16, + tx: Option, + multiplexer: ServerMultiplexer, + flate: Option>, +} + +impl ServerMessageSink { + pub fn new_plain( + multiplexer: ServerMultiplexer, + id: u16, + tx: ServerMessageDestination, + ) -> Self { + Self { + tx: Some(tx), + id, + multiplexer, + flate: None, + } + } + + pub fn new_compressed( + multiplexer: ServerMultiplexer, + id: u16, + tx: ServerMessageDestination, + ) -> Self { + Self { + tx: Some(tx), + id, + multiplexer, + flate: Some(FlateStream::new(CompressFlateAlgorithm( + flate2::Compress::new(flate2::Compression::new(2), false), + ))), + } + } + + pub async fn server_message( + &mut self, + body: &[u8], + ) -> Result<(), mpsc::error::SendError> { + let id = self.id; + let mut tx = self.tx.take().unwrap(); + let body = self.get_server_msg_content(body); + let msg = RefServerMessageParams { i: id, body }; + + let r = match &mut tx { + ServerMessageDestination::Channel(tx) => { + tx.send(SocketSignal::from_message(&ToClientRequest { + id: None, + params: ClientRequestMethod::servermsg(msg), + })) + .await + } + ServerMessageDestination::Rpc(caller) => { + caller.notify("servermsg", msg); + Ok(()) + } + }; + + self.tx = Some(tx); + r + } + + pub(crate) fn get_server_msg_content<'a: 'b, 'b>(&'a mut self, body: &'b [u8]) -> &'b [u8] { + if let Some(flate) = &mut self.flate { + if let Ok(compressed) = flate.process(body) { + return compressed; + } + } + + body + } +} + +impl Drop for ServerMessageSink { + fn drop(&mut self) { + self.multiplexer.remove(self.id); + } +} + +pub struct ClientMessageDecoder { + dec: Option>, +} + +impl ClientMessageDecoder { + pub fn new_plain() -> Self { + ClientMessageDecoder { dec: None } + } + + pub fn new_compressed() -> Self { + ClientMessageDecoder { + dec: Some(FlateStream::new(DecompressFlateAlgorithm( + flate2::Decompress::new(false), + ))), + } + } + + pub fn decode<'a: 'b, 'b>(&'a mut self, message: &'b [u8]) -> std::io::Result<&'b [u8]> { + match &mut self.dec { + Some(d) => d.process(message), + None => Ok(message), + } + } +} + +trait FlateAlgorithm { + fn total_in(&self) -> u64; + fn total_out(&self) -> u64; + fn process( + &mut self, + contents: &[u8], + output: &mut [u8], + ) -> Result; +} + +struct DecompressFlateAlgorithm(flate2::Decompress); + +impl FlateAlgorithm for DecompressFlateAlgorithm { + fn total_in(&self) -> u64 { + self.0.total_in() + } + + fn total_out(&self) -> u64 { + self.0.total_out() + } + + fn process( + &mut self, + contents: &[u8], + output: &mut [u8], + ) -> Result { + self.0 + .decompress(contents, output, flate2::FlushDecompress::None) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) + } +} + +struct CompressFlateAlgorithm(flate2::Compress); + +impl FlateAlgorithm for CompressFlateAlgorithm { + fn total_in(&self) -> u64 { + self.0.total_in() + } + + fn total_out(&self) -> u64 { + self.0.total_out() + } + + fn process( + &mut self, + contents: &[u8], + output: &mut [u8], + ) -> Result { + self.0 + .compress(contents, output, flate2::FlushCompress::Sync) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) + } +} + +struct FlateStream +where + A: FlateAlgorithm, +{ + flate: A, + output: Vec, +} + +impl FlateStream +where + A: FlateAlgorithm, +{ + pub fn new(alg: A) -> Self { + Self { + flate: alg, + output: vec![0; 4096], + } + } + + pub fn process(&mut self, contents: &[u8]) -> std::io::Result<&[u8]> { + let mut out_offset = 0; + let mut in_offset = 0; + loop { + let in_before = self.flate.total_in(); + let out_before = self.flate.total_out(); + + match self + .flate + .process(&contents[in_offset..], &mut self.output[out_offset..]) + { + Ok(flate2::Status::Ok | flate2::Status::BufError) => { + let processed_len = in_offset + (self.flate.total_in() - in_before) as usize; + let output_len = out_offset + (self.flate.total_out() - out_before) as usize; + if processed_len < contents.len() { + // If we filled the output buffer but there's more data to compress, + // extend the output buffer and keep compressing. + out_offset = output_len; + in_offset = processed_len; + if output_len == self.output.len() { + self.output.resize(self.output.len() * 2, 0); + } + continue; + } + + return Ok(&self.output[..output_len]); + } + Ok(flate2::Status::StreamEnd) => { + return Err(std::io::Error::new( + std::io::ErrorKind::UnexpectedEof, + "unexpected stream end", + )) + } + Err(e) => return Err(e), + } + } + } +} + +#[cfg(test)] +mod tests { + // Note this useful idiom: importing names from outer (for mod tests) scope. + use super::*; + + #[test] + fn test_round_trips_compression() { + let (tx, _) = mpsc::channel(1); + let mut sink = ServerMessageSink::new_compressed( + ServerMultiplexer::new(), + 0, + ServerMessageDestination::Channel(tx), + ); + let mut decompress = ClientMessageDecoder::new_compressed(); + + // 3000 and 30000 test resizing the buffer + for msg_len in [3, 30, 300, 3000, 30000] { + let vals = (0..msg_len).map(|v| v as u8).collect::>(); + let compressed = sink.get_server_msg_content(&vals); + assert_ne!(compressed, vals); + let decompressed = decompress.decode(compressed).unwrap(); + assert_eq!(decompressed.len(), vals.len()); + assert_eq!(decompressed, vals); + } + } +} diff --git a/cli/src/update_service.rs b/cli/src/update_service.rs new file mode 100644 index 0000000000..caf6a0ca53 --- /dev/null +++ b/cli/src/update_service.rs @@ -0,0 +1,319 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ffi::OsStr, fmt, path::Path}; + +use serde::{Deserialize, Serialize}; + +use crate::{ + constants::VSCODE_CLI_UPDATE_ENDPOINT, + debug, log, options, spanf, + util::{ + errors::{AnyError, CodeError, UpdatesNotConfigured, WrappedError}, + http::{BoxedHttp, SimpleResponse}, + io::ReportCopyProgress, + tar, zipper, + }, +}; + +/// Implementation of the VS Code Update service for use in the CLI. +pub struct UpdateService { + client: BoxedHttp, + log: log::Logger, +} + +/// Describes a specific release, can be created manually or returned from the update service. +#[derive(Clone, Eq, PartialEq)] +pub struct Release { + pub name: String, + pub platform: Platform, + pub target: TargetKind, + pub quality: options::Quality, + pub commit: String, +} + +impl std::fmt::Display for Release { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} (commit {})", self.name, self.commit) + } +} + +#[derive(Deserialize)] +struct UpdateServerVersion { + pub version: String, + pub name: String, +} + +fn quality_download_segment(quality: options::Quality) -> &'static str { + match quality { + options::Quality::Stable => "stable", + options::Quality::Insiders => "insider", + options::Quality::Exploration => "exploration", + } +} + +impl UpdateService { + pub fn new(log: log::Logger, http: BoxedHttp) -> Self { + UpdateService { client: http, log } + } + + pub async fn get_release_by_semver_version( + &self, + platform: Platform, + target: TargetKind, + quality: options::Quality, + version: &str, + ) -> Result { + let update_endpoint = + VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?; + let download_segment = target + .download_segment(platform) + .ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?; + let download_url = format!( + "{}/api/versions/{}/{}/{}", + update_endpoint, + version, + download_segment, + quality_download_segment(quality), + ); + + let mut response = spanf!( + self.log, + self.log.span("server.version.resolve"), + self.client.make_request("GET", download_url) + )?; + + if !response.status_code.is_success() { + return Err(response.into_err().await.into()); + } + + let res = response.json::().await?; + debug!(self.log, "Resolved version {} to {}", version, res.version); + + Ok(Release { + target, + platform, + quality, + name: res.name, + commit: res.version, + }) + } + + /// Gets the latest commit for the target of the given quality. + pub async fn get_latest_commit( + &self, + platform: Platform, + target: TargetKind, + quality: options::Quality, + ) -> Result { + let update_endpoint = + VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?; + let download_segment = target + .download_segment(platform) + .ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?; + let download_url = format!( + "{}/api/latest/{}/{}", + update_endpoint, + download_segment, + quality_download_segment(quality), + ); + + let mut response = spanf!( + self.log, + self.log.span("server.version.resolve"), + self.client.make_request("GET", download_url) + )?; + + if !response.status_code.is_success() { + return Err(response.into_err().await.into()); + } + + let res = response.json::().await?; + debug!(self.log, "Resolved quality {} to {}", quality, res.version); + + Ok(Release { + target, + platform, + quality, + name: res.name, + commit: res.version, + }) + } + + /// Gets the download stream for the release. + pub async fn get_download_stream(&self, release: &Release) -> Result { + let update_endpoint = + VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(UpdatesNotConfigured::no_url)?; + let download_segment = release + .target + .download_segment(release.platform) + .ok_or_else(|| CodeError::UnsupportedPlatform(release.platform.to_string()))?; + + let download_url = format!( + "{}/commit:{}/{}/{}", + update_endpoint, + release.commit, + download_segment, + quality_download_segment(release.quality), + ); + + let response = self.client.make_request("GET", download_url).await?; + if !response.status_code.is_success() { + return Err(response.into_err().await.into()); + } + + Ok(response) + } +} + +pub fn unzip_downloaded_release( + compressed_file: &Path, + target_dir: &Path, + reporter: T, +) -> Result<(), WrappedError> +where + T: ReportCopyProgress, +{ + if compressed_file.extension() == Some(OsStr::new("zip")) { + zipper::unzip_file(compressed_file, target_dir, reporter) + } else { + tar::decompress_tarball(compressed_file, target_dir, reporter) + } +} + +#[derive(Eq, PartialEq, Copy, Clone)] +pub enum TargetKind { + Server, + Archive, + Web, + Cli, +} + +impl TargetKind { + fn download_segment(&self, platform: Platform) -> Option { + match *self { + TargetKind::Server => Some(platform.headless()), + TargetKind::Archive => platform.archive(), + TargetKind::Web => Some(platform.web()), + TargetKind::Cli => Some(platform.cli()), + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum Platform { + LinuxAlpineX64, + LinuxAlpineARM64, + LinuxX64, + LinuxARM64, + LinuxARM32, + DarwinX64, + DarwinARM64, + WindowsX64, + WindowsX86, + WindowsARM64, +} + +impl Platform { + pub fn archive(&self) -> Option { + match self { + Platform::LinuxX64 => Some("linux-x64".to_owned()), + Platform::LinuxARM64 => Some("linux-arm64".to_owned()), + Platform::LinuxARM32 => Some("linux-armhf".to_owned()), + Platform::DarwinX64 => Some("darwin".to_owned()), + Platform::DarwinARM64 => Some("darwin-arm64".to_owned()), + Platform::WindowsX64 => Some("win32-x64-archive".to_owned()), + Platform::WindowsX86 => Some("win32-archive".to_owned()), + Platform::WindowsARM64 => Some("win32-arm64-archive".to_owned()), + _ => None, + } + } + pub fn headless(&self) -> String { + match self { + Platform::LinuxAlpineARM64 => "server-alpine-arm64", + Platform::LinuxAlpineX64 => "server-linux-alpine", + Platform::LinuxX64 => "server-linux-x64", + Platform::LinuxARM64 => "server-linux-arm64", + Platform::LinuxARM32 => "server-linux-armhf", + Platform::DarwinX64 => "server-darwin", + Platform::DarwinARM64 => "server-darwin-arm64", + Platform::WindowsX64 => "server-win32-x64", + Platform::WindowsX86 => "server-win32", + Platform::WindowsARM64 => "server-win32-x64", // we don't publish an arm64 server build yet + } + .to_owned() + } + + pub fn cli(&self) -> String { + match self { + Platform::LinuxAlpineARM64 => "cli-alpine-arm64", + Platform::LinuxAlpineX64 => "cli-alpine-x64", + Platform::LinuxX64 => "cli-linux-x64", + Platform::LinuxARM64 => "cli-linux-arm64", + Platform::LinuxARM32 => "cli-linux-armhf", + Platform::DarwinX64 => "cli-darwin-x64", + Platform::DarwinARM64 => "cli-darwin-arm64", + Platform::WindowsARM64 => "cli-win32-arm64", + Platform::WindowsX64 => "cli-win32-x64", + Platform::WindowsX86 => "cli-win32", + } + .to_owned() + } + + pub fn web(&self) -> String { + format!("{}-web", self.headless()) + } + + pub fn env_default() -> Option { + if cfg!(all( + target_os = "linux", + target_arch = "x86_64", + target_env = "musl" + )) { + Some(Platform::LinuxAlpineX64) + } else if cfg!(all( + target_os = "linux", + target_arch = "aarch64", + target_env = "musl" + )) { + Some(Platform::LinuxAlpineARM64) + } else if cfg!(all(target_os = "linux", target_arch = "x86_64")) { + Some(Platform::LinuxX64) + } else if cfg!(all(target_os = "linux", target_arch = "arm")) { + Some(Platform::LinuxARM32) + } else if cfg!(all(target_os = "linux", target_arch = "aarch64")) { + Some(Platform::LinuxARM64) + } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) { + Some(Platform::DarwinX64) + } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) { + Some(Platform::DarwinARM64) + } else if cfg!(all(target_os = "windows", target_arch = "x86_64")) { + Some(Platform::WindowsX64) + } else if cfg!(all(target_os = "windows", target_arch = "x86")) { + Some(Platform::WindowsX86) + } else if cfg!(all(target_os = "windows", target_arch = "aarch64")) { + Some(Platform::WindowsARM64) + } else { + None + } + } +} + +impl fmt::Display for Platform { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match self { + Platform::LinuxAlpineARM64 => "LinuxAlpineARM64", + Platform::LinuxAlpineX64 => "LinuxAlpineX64", + Platform::LinuxX64 => "LinuxX64", + Platform::LinuxARM64 => "LinuxARM64", + Platform::LinuxARM32 => "LinuxARM32", + Platform::DarwinX64 => "DarwinX64", + Platform::DarwinARM64 => "DarwinARM64", + Platform::WindowsX64 => "WindowsX64", + Platform::WindowsX86 => "WindowsX86", + Platform::WindowsARM64 => "WindowsARM64", + }) + } +} diff --git a/cli/src/util.rs b/cli/src/util.rs new file mode 100644 index 0000000000..364090276e --- /dev/null +++ b/cli/src/util.rs @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +mod is_integrated; + +pub mod command; +pub mod errors; +pub mod http; +pub mod input; +pub mod io; +pub mod machine; +pub mod prereqs; +pub mod ring_buffer; +pub mod sync; +pub use is_integrated::*; +pub mod app_lock; +pub mod file_lock; +pub mod os; +pub mod tar; +pub mod zipper; diff --git a/cli/src/util/app_lock.rs b/cli/src/util/app_lock.rs new file mode 100644 index 0000000000..1e4c741fd9 --- /dev/null +++ b/cli/src/util/app_lock.rs @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +#[cfg(windows)] +use std::{io, ptr}; + +#[cfg(windows)] +use winapi::{ + shared::winerror::ERROR_ALREADY_EXISTS, + um::{handleapi::CloseHandle, synchapi::CreateMutexA, winnt::HANDLE}, +}; + +use super::errors::CodeError; + +pub struct AppMutex { + #[cfg(windows)] + handle: HANDLE, +} + +#[cfg(windows)] // handle is thread-safe, mark it so with this +unsafe impl Send for AppMutex {} + +impl AppMutex { + #[cfg(unix)] + pub fn new(_name: &str) -> Result { + Ok(Self {}) + } + + #[cfg(windows)] + pub fn new(name: &str) -> Result { + use std::ffi::CString; + + let cname = CString::new(name).unwrap(); + let handle = unsafe { CreateMutexA(ptr::null_mut(), 0, cname.as_ptr() as _) }; + + if !handle.is_null() { + return Ok(Self { handle }); + } + + let err = io::Error::last_os_error(); + let raw = err.raw_os_error(); + // docs report it should return ERROR_IO_PENDING, but in my testing it actually + // returns ERROR_LOCK_VIOLATION. Or maybe winapi is wrong? + if raw == Some(ERROR_ALREADY_EXISTS as i32) { + return Err(CodeError::AppAlreadyLocked(name.to_string())); + } + + Err(CodeError::AppLockFailed(err)) + } +} + +impl Drop for AppMutex { + fn drop(&mut self) { + #[cfg(windows)] + unsafe { + CloseHandle(self.handle) + }; + } +} diff --git a/cli/src/util/command.rs b/cli/src/util/command.rs new file mode 100644 index 0000000000..b8762e6723 --- /dev/null +++ b/cli/src/util/command.rs @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use super::errors::CodeError; +use std::{ + borrow::Cow, + ffi::OsStr, + process::{Output, Stdio}, +}; +use tokio::process::Command; + +pub async fn capture_command_and_check_status( + command_str: impl AsRef, + args: &[impl AsRef], +) -> Result { + let output = capture_command(&command_str, args).await?; + + check_output_status(output, || { + format!( + "{} {}", + command_str.as_ref().to_string_lossy(), + args.iter() + .map(|a| a.as_ref().to_string_lossy()) + .collect::>>() + .join(" ") + ) + }) +} + +pub fn check_output_status( + output: Output, + cmd_str: impl FnOnce() -> String, +) -> Result { + if !output.status.success() { + return Err(CodeError::CommandFailed { + command: cmd_str(), + code: output.status.code().unwrap_or(-1), + output: String::from_utf8_lossy(if output.stderr.is_empty() { + &output.stdout + } else { + &output.stderr + }) + .into(), + }); + } + + Ok(output) +} + +pub async fn capture_command( + command_str: A, + args: I, +) -> Result +where + A: AsRef, + I: IntoIterator, + S: AsRef, +{ + Command::new(&command_str) + .args(args) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .output() + .await + .map_err(|e| CodeError::CommandFailed { + command: command_str.as_ref().to_string_lossy().to_string(), + code: -1, + output: e.to_string(), + }) +} + +/// Kills and processes and all of its children. +#[cfg(target_os = "windows")] +pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> { + capture_command("taskkill", &["/t", "/pid", &process_id.to_string()]).await?; + Ok(()) +} + +/// Kills and processes and all of its children. +#[cfg(not(target_os = "windows"))] +pub async fn kill_tree(process_id: u32) -> Result<(), CodeError> { + use futures::future::join_all; + use tokio::io::{AsyncBufReadExt, BufReader}; + + async fn kill_single_pid(process_id_str: String) { + capture_command("kill", &[&process_id_str]).await.ok(); + } + + // Rusty version of https://github.com/microsoft/vscode-js-debug/blob/main/src/targets/node/terminateProcess.sh + + let parent_id = process_id.to_string(); + let mut prgrep_cmd = Command::new("pgrep") + .arg("-P") + .arg(&parent_id) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .spawn() + .map_err(|e| CodeError::CommandFailed { + command: format!("pgrep -P {}", parent_id), + code: -1, + output: e.to_string(), + })?; + + let mut kill_futures = vec![tokio::spawn( + async move { kill_single_pid(parent_id).await }, + )]; + + if let Some(stdout) = prgrep_cmd.stdout.take() { + let mut reader = BufReader::new(stdout).lines(); + while let Some(line) = reader.next_line().await.unwrap_or(None) { + kill_futures.push(tokio::spawn(async move { kill_single_pid(line).await })) + } + } + + join_all(kill_futures).await; + prgrep_cmd.kill().await.ok(); + Ok(()) +} diff --git a/cli/src/util/errors.rs b/cli/src/util/errors.rs new file mode 100644 index 0000000000..50c4c5305c --- /dev/null +++ b/cli/src/util/errors.rs @@ -0,0 +1,532 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use crate::{ + constants::{APPLICATION_NAME, CONTROL_PORT, DOCUMENTATION_URL, QUALITYLESS_PRODUCT_NAME}, + rpc::ResponseError, +}; +use std::fmt::Display; +use thiserror::Error; + +// Wraps another error with additional info. +#[derive(Debug, Clone)] +pub struct WrappedError { + message: String, + original: String, +} + +impl std::fmt::Display for WrappedError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}: {}", self.message, self.original) + } +} + +impl std::error::Error for WrappedError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +impl WrappedError { + // fn new(original: Box, message: String) -> WrappedError { + // WrappedError { message, original } + // } +} + +impl From for WrappedError { + fn from(e: reqwest::Error) -> WrappedError { + WrappedError { + message: format!( + "error requesting {}", + e.url().map_or("", |u| u.as_str()) + ), + original: format!("{}", e), + } + } +} + +pub fn wrapdbg(original: T, message: S) -> WrappedError +where + T: std::fmt::Debug, + S: Into, +{ + WrappedError { + message: message.into(), + original: format!("{:?}", original), + } +} + +pub fn wrap(original: T, message: S) -> WrappedError +where + T: Display, + S: Into, +{ + WrappedError { + message: message.into(), + original: format!("{}", original), + } +} + +// Error generated by an unsuccessful HTTP response +#[derive(Debug)] +pub struct StatusError { + pub url: String, + pub status_code: u16, + pub body: String, +} + +impl std::fmt::Display for StatusError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "error requesting {}: {} {}", + self.url, self.status_code, self.body + ) + } +} + +impl StatusError { + pub async fn from_res(res: reqwest::Response) -> Result { + let status_code = res.status().as_u16(); + let url = res.url().to_string(); + let body = res.text().await.map_err(|e| { + wrap( + e, + format!( + "failed to read response body on {} code from {}", + status_code, url + ), + ) + })?; + + Ok(StatusError { + url, + status_code, + body, + }) + } +} + +// When the user has not consented to the licensing terms in using the Launcher +#[derive(Debug)] +pub struct MissingLegalConsent(pub String); + +impl std::fmt::Display for MissingLegalConsent { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +// When the provided connection token doesn't match the one used to set up the original VS Code Server +// This is most likely due to a new user joining. +#[derive(Debug)] +pub struct MismatchConnectionToken(pub String); + +impl std::fmt::Display for MismatchConnectionToken { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +// When the VS Code server has an unrecognized extension (rather than zip or gz) +#[derive(Debug)] +pub struct InvalidServerExtensionError(pub String); + +impl std::fmt::Display for InvalidServerExtensionError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "invalid server extension '{}'", self.0) + } +} + +// When the tunnel fails to open +#[derive(Debug, Clone)] +pub struct DevTunnelError(pub String); + +impl std::fmt::Display for DevTunnelError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "could not open tunnel: {}", self.0) + } +} + +impl std::error::Error for DevTunnelError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } +} + +// When the server was downloaded, but the entrypoint scripts don't exist. +#[derive(Debug)] +pub struct MissingEntrypointError(); + +impl std::fmt::Display for MissingEntrypointError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Missing entrypoints in server download. Most likely this is a corrupted download. Please retry") + } +} + +#[derive(Debug)] +pub struct SetupError(pub String); + +impl std::fmt::Display for SetupError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{}\n\nMore info at {}/remote/linux", + DOCUMENTATION_URL.unwrap_or(""), + self.0 + ) + } +} + +#[derive(Debug)] +pub struct NoHomeForLauncherError(); + +impl std::fmt::Display for NoHomeForLauncherError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "No $HOME variable was found in your environment. Either set it, or specify a `--data-dir` manually when invoking the launcher.", + ) + } +} + +#[derive(Debug)] +pub struct InvalidTunnelName(pub String); + +impl std::fmt::Display for InvalidTunnelName { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} + +#[derive(Debug)] +pub struct TunnelCreationFailed(pub String, pub String); + +impl std::fmt::Display for TunnelCreationFailed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Could not create tunnel with name: {}\nReason: {}", + &self.0, &self.1 + ) + } +} + +#[derive(Debug)] +pub struct TunnelHostFailed(pub String); + +impl std::fmt::Display for TunnelHostFailed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &self.0) + } +} + +#[derive(Debug)] +pub struct ExtensionInstallFailed(pub String); + +impl std::fmt::Display for ExtensionInstallFailed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Extension install failed: {}", &self.0) + } +} + +#[derive(Debug)] +pub struct MismatchedLaunchModeError(); + +impl std::fmt::Display for MismatchedLaunchModeError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "A server is already running, but it was not launched in the same listening mode (port vs. socket) as this request") + } +} + +#[derive(Debug)] +pub struct NoAttachedServerError(); + +impl std::fmt::Display for NoAttachedServerError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "No server is running") + } +} + +#[derive(Debug)] +pub struct RefreshTokenNotAvailableError(); + +impl std::fmt::Display for RefreshTokenNotAvailableError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Refresh token not available, authentication is required") + } +} + +#[derive(Debug)] +pub struct NoInstallInUserProvidedPath(pub String); + +impl std::fmt::Display for NoInstallInUserProvidedPath { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "No {} installation could be found in {}. You can run `{} --use-quality=stable` to switch to the latest stable version of {}.", + QUALITYLESS_PRODUCT_NAME, + self.0, + APPLICATION_NAME, + QUALITYLESS_PRODUCT_NAME + ) + } +} + +#[derive(Debug)] +pub struct InvalidRequestedVersion(); + +impl std::fmt::Display for InvalidRequestedVersion { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "The reqested version is invalid, expected one of 'stable', 'insiders', version number (x.y.z), or absolute path.", + ) + } +} + +#[derive(Debug)] +pub struct UserCancelledInstallation(); + +impl std::fmt::Display for UserCancelledInstallation { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Installation aborted.") + } +} + +#[derive(Debug)] +pub struct CannotForwardControlPort(); + +impl std::fmt::Display for CannotForwardControlPort { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Cannot forward or unforward port {}.", CONTROL_PORT) + } +} + +#[derive(Debug)] +pub struct ServerHasClosed(); + +impl std::fmt::Display for ServerHasClosed { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Request cancelled because the server has closed") + } +} + +#[derive(Debug)] +pub struct UpdatesNotConfigured(pub String); + +impl UpdatesNotConfigured { + pub fn no_url() -> Self { + UpdatesNotConfigured("no service url".to_owned()) + } +} + +impl std::fmt::Display for UpdatesNotConfigured { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Update service is not configured: {}", self.0) + } +} +#[derive(Debug)] +pub struct ServiceAlreadyRegistered(); + +impl std::fmt::Display for ServiceAlreadyRegistered { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Already registered the service. Run `{} tunnel service uninstall` to unregister it first", APPLICATION_NAME) + } +} + +#[derive(Debug)] +pub struct WindowsNeedsElevation(pub String); + +impl std::fmt::Display for WindowsNeedsElevation { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + writeln!(f, "{}", self.0)?; + writeln!(f)?; + writeln!(f, "You may need to run this command as an administrator:")?; + writeln!(f, " 1. Open the start menu and search for Powershell")?; + writeln!(f, " 2. Right click and 'Run as administrator'")?; + if let Ok(exe) = std::env::current_exe() { + writeln!( + f, + " 3. Run &'{}' '{}'", + exe.display(), + std::env::args().skip(1).collect::>().join("' '") + ) + } else { + writeln!(f, " 3. Run the same command again",) + } + } +} + +#[derive(Debug)] +pub struct InvalidRpcDataError(pub String); + +impl std::fmt::Display for InvalidRpcDataError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "parse error: {}", self.0) + } +} + +#[derive(Debug)] +pub struct CorruptDownload(pub String); + +impl std::fmt::Display for CorruptDownload { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Error updating the {} CLI: {}", + QUALITYLESS_PRODUCT_NAME, self.0 + ) + } +} + +#[derive(Debug)] +pub struct MissingHomeDirectory(); + +impl std::fmt::Display for MissingHomeDirectory { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Could not find your home directory. Please ensure this command is running in the context of an normal user.") + } +} + +#[derive(Debug)] +pub struct OAuthError { + pub error: String, + pub error_description: Option, +} + +impl std::fmt::Display for OAuthError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Error getting authorization: {} {}", + self.error, + self.error_description.as_deref().unwrap_or("") + ) + } +} + +// Makes an "AnyError" enum that contains any of the given errors, in the form +// `enum AnyError { FooError(FooError) }` (when given `makeAnyError!(FooError)`). +// Useful to easily deal with application error types without making tons of "From" +// clauses. +macro_rules! makeAnyError { + ($($e:ident),*) => { + + #[derive(Debug)] + #[allow(clippy::enum_variant_names)] + pub enum AnyError { + $($e($e),)* + } + + impl std::fmt::Display for AnyError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + $(AnyError::$e(ref e) => e.fmt(f),)* + } + } + } + + impl std::error::Error for AnyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + None + } + } + + $(impl From<$e> for AnyError { + fn from(e: $e) -> AnyError { + AnyError::$e(e) + } + })* + }; +} + +/// Internal errors in the VS Code CLI. +/// Note: other error should be migrated to this type gradually +#[derive(Error, Debug)] +pub enum CodeError { + #[error("could not connect to socket/pipe: {0:?}")] + AsyncPipeFailed(std::io::Error), + #[error("could not listen on socket/pipe: {0:?}")] + AsyncPipeListenerFailed(std::io::Error), + #[error("could not create singleton lock file: {0:?}")] + SingletonLockfileOpenFailed(std::io::Error), + #[error("could not read singleton lock file: {0:?}")] + SingletonLockfileReadFailed(rmp_serde::decode::Error), + #[error("the process holding the singleton lock file (pid={0}) exited")] + SingletonLockedProcessExited(u32), + #[error("no tunnel process is currently running")] + NoRunningTunnel, + #[error("rpc call failed: {0:?}")] + TunnelRpcCallFailed(ResponseError), + #[cfg(windows)] + #[error("the windows app lock {0} already exists")] + AppAlreadyLocked(String), + #[cfg(windows)] + #[error("could not get windows app lock: {0:?}")] + AppLockFailed(std::io::Error), + #[error("failed to run command \"{command}\" (code {code}): {output}")] + CommandFailed { + command: String, + code: i32, + output: String, + }, + + #[error("platform not currently supported: {0}")] + UnsupportedPlatform(String), + #[error("This machine not meet {name}'s prerequisites, expected either...: {bullets}")] + PrerequisitesFailed { name: &'static str, bullets: String }, + #[error("failed to spawn process: {0:?}")] + ProcessSpawnFailed(std::io::Error), + #[error("failed to handshake spawned process: {0:?}")] + ProcessSpawnHandshakeFailed(std::io::Error), + #[error("download appears corrupted, please retry ({0})")] + CorruptDownload(&'static str), + #[error("port forwarding is not available in this context")] + PortForwardingNotAvailable, + #[error("'auth' call required")] + ServerAuthRequired, + #[error("challenge not yet issued")] + AuthChallengeNotIssued, + #[error("unauthorized client refused")] + AuthMismatch, +} + +makeAnyError!( + MissingLegalConsent, + MismatchConnectionToken, + DevTunnelError, + StatusError, + WrappedError, + InvalidServerExtensionError, + MissingEntrypointError, + SetupError, + NoHomeForLauncherError, + TunnelCreationFailed, + TunnelHostFailed, + InvalidTunnelName, + ExtensionInstallFailed, + MismatchedLaunchModeError, + NoAttachedServerError, + RefreshTokenNotAvailableError, + NoInstallInUserProvidedPath, + UserCancelledInstallation, + InvalidRequestedVersion, + CannotForwardControlPort, + ServerHasClosed, + ServiceAlreadyRegistered, + WindowsNeedsElevation, + UpdatesNotConfigured, + CorruptDownload, + MissingHomeDirectory, + OAuthError, + InvalidRpcDataError, + CodeError +); + +impl From for AnyError { + fn from(e: reqwest::Error) -> AnyError { + AnyError::WrappedError(WrappedError::from(e)) + } +} diff --git a/cli/src/util/file_lock.rs b/cli/src/util/file_lock.rs new file mode 100644 index 0000000000..a93f17ccda --- /dev/null +++ b/cli/src/util/file_lock.rs @@ -0,0 +1,125 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use crate::util::errors::CodeError; +use std::{fs::File, io}; + +pub struct FileLock { + file: File, + #[cfg(windows)] + overlapped: winapi::um::minwinbase::OVERLAPPED, +} + +#[cfg(windows)] // overlapped is thread-safe, mark it so with this +unsafe impl Send for FileLock {} + +pub enum Lock { + Acquired(FileLock), + AlreadyLocked(File), +} + +/// Number of locked bytes in the file. On Windows, locking prevents reads, +/// but consumers of the lock may still want to read what the locking file +/// as written. Thus, only PREFIX_LOCKED_BYTES are locked, and any globally- +/// readable content should be written after the prefix. +#[cfg(windows)] +pub const PREFIX_LOCKED_BYTES: usize = 1; + +#[cfg(unix)] +pub const PREFIX_LOCKED_BYTES: usize = 0; + +impl FileLock { + #[cfg(windows)] + pub fn acquire(file: File) -> Result { + use std::os::windows::prelude::AsRawHandle; + use winapi::{ + shared::winerror::{ERROR_IO_PENDING, ERROR_LOCK_VIOLATION}, + um::{ + fileapi::LockFileEx, + minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY}, + }, + }; + + let handle = file.as_raw_handle(); + let (overlapped, ok) = unsafe { + let mut overlapped = std::mem::zeroed(); + let ok = LockFileEx( + handle, + LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, + 0, + PREFIX_LOCKED_BYTES as u32, + 0, + &mut overlapped, + ); + + (overlapped, ok) + }; + + if ok != 0 { + return Ok(Lock::Acquired(Self { file, overlapped })); + } + + let err = io::Error::last_os_error(); + let raw = err.raw_os_error(); + // docs report it should return ERROR_IO_PENDING, but in my testing it actually + // returns ERROR_LOCK_VIOLATION. Or maybe winapi is wrong? + if raw == Some(ERROR_IO_PENDING as i32) || raw == Some(ERROR_LOCK_VIOLATION as i32) { + return Ok(Lock::AlreadyLocked(file)); + } + + Err(CodeError::SingletonLockfileOpenFailed(err)) + } + + #[cfg(unix)] + pub fn acquire(file: File) -> Result { + use std::os::unix::io::AsRawFd; + + let fd = file.as_raw_fd(); + let res = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) }; + if res == 0 { + return Ok(Lock::Acquired(Self { file })); + } + + let err = io::Error::last_os_error(); + if err.kind() == io::ErrorKind::WouldBlock { + return Ok(Lock::AlreadyLocked(file)); + } + + Err(CodeError::SingletonLockfileOpenFailed(err)) + } + + pub fn file(&self) -> &File { + &self.file + } + + pub fn file_mut(&mut self) -> &mut File { + &mut self.file + } +} + +impl Drop for FileLock { + #[cfg(windows)] + fn drop(&mut self) { + use std::os::windows::prelude::AsRawHandle; + use winapi::um::fileapi::UnlockFileEx; + + unsafe { + UnlockFileEx( + self.file.as_raw_handle(), + 0, + u32::MAX, + u32::MAX, + &mut self.overlapped, + ) + }; + } + + #[cfg(unix)] + fn drop(&mut self) { + use std::os::unix::io::AsRawFd; + + unsafe { libc::flock(self.file.as_raw_fd(), libc::LOCK_UN) }; + } +} diff --git a/cli/src/util/http.rs b/cli/src/util/http.rs new file mode 100644 index 0000000000..af4fe0b221 --- /dev/null +++ b/cli/src/util/http.rs @@ -0,0 +1,376 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use crate::{ + constants::get_default_user_agent, + log, + util::errors::{self, WrappedError}, +}; +use async_trait::async_trait; +use core::panic; +use futures::stream::TryStreamExt; +use hyper::{ + header::{HeaderName, CONTENT_LENGTH}, + http::HeaderValue, + HeaderMap, StatusCode, +}; +use serde::de::DeserializeOwned; +use std::{io, pin::Pin, str::FromStr, sync::Arc, task::Poll}; +use tokio::{ + fs, + io::{AsyncRead, AsyncReadExt}, + sync::mpsc, +}; +use tokio_util::compat::FuturesAsyncReadCompatExt; + +use super::{ + errors::{wrap, AnyError, StatusError}, + io::{copy_async_progress, ReadBuffer, ReportCopyProgress}, +}; + +pub async fn download_into_file( + filename: &std::path::Path, + progress: T, + mut res: SimpleResponse, +) -> Result +where + T: ReportCopyProgress, +{ + let mut file = fs::File::create(filename) + .await + .map_err(|e| errors::wrap(e, "failed to create file"))?; + + let content_length = res + .headers + .get(CONTENT_LENGTH) + .and_then(|h| h.to_str().ok()) + .and_then(|s| s.parse::().ok()) + .unwrap_or(0); + + copy_async_progress(progress, &mut res.read, &mut file, content_length) + .await + .map_err(|e| errors::wrap(e, "failed to download file"))?; + + Ok(file) +} + +pub struct SimpleResponse { + pub status_code: StatusCode, + pub headers: HeaderMap, + pub read: Pin>, + pub url: Option, +} + +impl SimpleResponse { + pub fn url_path_basename(&self) -> Option { + self.url.as_ref().and_then(|u| { + u.path_segments() + .and_then(|s| s.last().map(|s| s.to_owned())) + }) + } +} + +impl SimpleResponse { + pub fn generic_error(url: &str) -> Self { + let (_, rx) = mpsc::unbounded_channel(); + SimpleResponse { + url: url::Url::parse(url).ok(), + status_code: StatusCode::INTERNAL_SERVER_ERROR, + headers: HeaderMap::new(), + read: Box::pin(DelegatedReader::new(rx)), + } + } + + /// Converts the response into a StatusError + pub async fn into_err(mut self) -> StatusError { + let mut body = String::new(); + self.read.read_to_string(&mut body).await.ok(); + + StatusError { + url: self + .url + .map(|u| u.to_string()) + .unwrap_or_else(|| "".to_owned()), + status_code: self.status_code.as_u16(), + body, + } + } + + /// Deserializes the response body as JSON + pub async fn json(&mut self) -> Result { + let mut buf = vec![]; + + // ideally serde would deserialize a stream, but it does not appear that + // is supported. reqwest itself reads and decodes separately like we do here: + self.read + .read_to_end(&mut buf) + .await + .map_err(|e| wrap(e, "error reading response"))?; + + let t = serde_json::from_slice(&buf) + .map_err(|e| wrap(e, format!("error decoding json from {:?}", self.url)))?; + + Ok(t) + } +} + +/// *Very* simple HTTP implementation. In most cases, this will just delegate to +/// the request library on the server (i.e. `reqwest`) but it can also be used +/// to make update/download requests on the client rather than the server, +/// similar to SSH's `remote.SSH.localServerDownload` setting. +#[async_trait] +pub trait SimpleHttp { + async fn make_request( + &self, + method: &'static str, + url: String, + ) -> Result; +} + +pub type BoxedHttp = Arc; + +// Implementation of SimpleHttp that uses a reqwest client. +#[derive(Clone)] +pub struct ReqwestSimpleHttp { + client: reqwest::Client, +} + +impl ReqwestSimpleHttp { + pub fn new() -> Self { + Self { + client: reqwest::ClientBuilder::new() + .user_agent(get_default_user_agent()) + .build() + .unwrap(), + } + } + + pub fn with_client(client: reqwest::Client) -> Self { + Self { client } + } +} + +impl Default for ReqwestSimpleHttp { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl SimpleHttp for ReqwestSimpleHttp { + async fn make_request( + &self, + method: &'static str, + url: String, + ) -> Result { + let res = self + .client + .request(reqwest::Method::try_from(method).unwrap(), &url) + .send() + .await?; + + Ok(SimpleResponse { + status_code: res.status(), + headers: res.headers().clone(), + url: Some(res.url().clone()), + read: Box::pin( + res.bytes_stream() + .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) + .into_async_read() + .compat(), + ), + }) + } +} + +enum DelegatedHttpEvent { + InitResponse { + status_code: u16, + headers: Vec<(String, String)>, + }, + Body(Vec), + End, +} + +// Handle for a delegated request that allows manually issuing and response. +pub struct DelegatedHttpRequest { + pub method: &'static str, + pub url: String, + ch: mpsc::UnboundedSender, +} + +impl DelegatedHttpRequest { + pub fn initial_response(&self, status_code: u16, headers: Vec<(String, String)>) { + self.ch + .send(DelegatedHttpEvent::InitResponse { + status_code, + headers, + }) + .ok(); + } + + pub fn body(&self, chunk: Vec) { + self.ch.send(DelegatedHttpEvent::Body(chunk)).ok(); + } + + pub fn end(self) {} +} + +impl Drop for DelegatedHttpRequest { + fn drop(&mut self) { + self.ch.send(DelegatedHttpEvent::End).ok(); + } +} + +/// Implementation of SimpleHttp that allows manually controlling responses. +#[derive(Clone)] +pub struct DelegatedSimpleHttp { + start_request: mpsc::Sender, + log: log::Logger, +} + +impl DelegatedSimpleHttp { + pub fn new(log: log::Logger) -> (Self, mpsc::Receiver) { + let (tx, rx) = mpsc::channel(4); + ( + DelegatedSimpleHttp { + log, + start_request: tx, + }, + rx, + ) + } +} + +#[async_trait] +impl SimpleHttp for DelegatedSimpleHttp { + async fn make_request( + &self, + method: &'static str, + url: String, + ) -> Result { + trace!(self.log, "making delegated request to {}", url); + let (tx, mut rx) = mpsc::unbounded_channel(); + let sent = self + .start_request + .send(DelegatedHttpRequest { + method, + url: url.clone(), + ch: tx, + }) + .await; + + if sent.is_err() { + return Ok(SimpleResponse::generic_error(&url)); // sender shut down + } + + match rx.recv().await { + Some(DelegatedHttpEvent::InitResponse { + status_code, + headers, + }) => { + trace!( + self.log, + "delegated request to {} resulted in status = {}", + url, + status_code + ); + let mut headers_map = HeaderMap::with_capacity(headers.len()); + for (k, v) in &headers { + if let (Ok(key), Ok(value)) = ( + HeaderName::from_str(&k.to_lowercase()), + HeaderValue::from_str(v), + ) { + headers_map.insert(key, value); + } + } + + Ok(SimpleResponse { + url: url::Url::parse(&url).ok(), + status_code: StatusCode::from_u16(status_code) + .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR), + headers: headers_map, + read: Box::pin(DelegatedReader::new(rx)), + }) + } + Some(DelegatedHttpEvent::End) => Ok(SimpleResponse::generic_error(&url)), + Some(_) => panic!("expected initresponse as first message from delegated http"), + None => Ok(SimpleResponse::generic_error(&url)), // sender shut down + } + } +} + +struct DelegatedReader { + receiver: mpsc::UnboundedReceiver, + readbuf: ReadBuffer, +} + +impl DelegatedReader { + pub fn new(rx: mpsc::UnboundedReceiver) -> Self { + DelegatedReader { + readbuf: ReadBuffer::default(), + receiver: rx, + } + } +} + +impl AsyncRead for DelegatedReader { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + if let Some((v, s)) = self.readbuf.take_data() { + return self.readbuf.put_data(buf, v, s); + } + + match self.receiver.poll_recv(cx) { + Poll::Ready(Some(DelegatedHttpEvent::Body(msg))) => self.readbuf.put_data(buf, msg, 0), + Poll::Ready(Some(_)) => Poll::Ready(Ok(())), // EOF + Poll::Ready(None) => { + Poll::Ready(Err(io::Error::new(io::ErrorKind::UnexpectedEof, "EOF"))) + } + Poll::Pending => Poll::Pending, + } + } +} + +/// Simple http implementation that falls back to delegated http if +/// making a direct reqwest fails. +pub struct FallbackSimpleHttp { + native: ReqwestSimpleHttp, + delegated: DelegatedSimpleHttp, +} + +impl FallbackSimpleHttp { + pub fn new(native: ReqwestSimpleHttp, delegated: DelegatedSimpleHttp) -> Self { + FallbackSimpleHttp { native, delegated } + } + + pub fn native(&self) -> ReqwestSimpleHttp { + self.native.clone() + } + + pub fn delegated(&self) -> DelegatedSimpleHttp { + self.delegated.clone() + } +} + +#[async_trait] +impl SimpleHttp for FallbackSimpleHttp { + async fn make_request( + &self, + method: &'static str, + url: String, + ) -> Result { + let r1 = self.native.make_request(method, url.clone()).await; + if let Ok(res) = r1 { + if !res.status_code.is_server_error() { + return Ok(res); + } + } + + self.delegated.make_request(method, url).await + } +} diff --git a/cli/src/util/input.rs b/cli/src/util/input.rs new file mode 100644 index 0000000000..709a216fb6 --- /dev/null +++ b/cli/src/util/input.rs @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use crate::util::errors::wrap; +use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; +use indicatif::ProgressBar; +use std::fmt::Display; + +use super::{errors::WrappedError, io::ReportCopyProgress}; + +/// Wrapper around indicatif::ProgressBar that implements ReportCopyProgress. +pub struct ProgressBarReporter { + bar: ProgressBar, + has_set_total: bool, +} + +impl From for ProgressBarReporter { + fn from(bar: ProgressBar) -> Self { + ProgressBarReporter { + bar, + has_set_total: false, + } + } +} + +impl ReportCopyProgress for ProgressBarReporter { + fn report_progress(&mut self, bytes_so_far: u64, total_bytes: u64) { + if !self.has_set_total { + self.bar.set_length(total_bytes); + } + + if bytes_so_far == total_bytes { + self.bar.finish_and_clear(); + } else { + self.bar.set_position(bytes_so_far); + } + } +} + +pub fn prompt_yn(text: &str) -> Result { + Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(text) + .default(true) + .interact() + .map_err(|e| wrap(e, "Failed to read confirm input")) +} + +pub fn prompt_options(text: impl Into, options: &[T]) -> Result +where + T: Display + Copy, +{ + let chosen = Select::with_theme(&ColorfulTheme::default()) + .with_prompt(text) + .items(options) + .default(0) + .interact() + .map_err(|e| wrap(e, "Failed to read select input"))?; + + Ok(options[chosen]) +} + +pub fn prompt_placeholder(question: &str, placeholder: &str) -> Result { + Input::with_theme(&ColorfulTheme::default()) + .with_prompt(question) + .default(placeholder.to_string()) + .interact_text() + .map_err(|e| wrap(e, "Failed to read confirm input")) +} diff --git a/cli/src/util/io.rs b/cli/src/util/io.rs new file mode 100644 index 0000000000..58a7510f71 --- /dev/null +++ b/cli/src/util/io.rs @@ -0,0 +1,355 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use std::{ + fs::File, + io::{self, BufRead, Seek}, + task::Poll, + time::Duration, +}; + +use tokio::{ + io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, + sync::mpsc, + time::sleep, +}; + +use super::ring_buffer::RingBuffer; + +pub trait ReportCopyProgress { + fn report_progress(&mut self, bytes_so_far: u64, total_bytes: u64); +} + +/// Type that doesn't emit anything for download progress. +pub struct SilentCopyProgress(); + +impl ReportCopyProgress for SilentCopyProgress { + fn report_progress(&mut self, _bytes_so_far: u64, _total_bytes: u64) {} +} + +/// Copies from the reader to the writer, reporting progress to the provided +/// reporter every so often. +pub async fn copy_async_progress( + mut reporter: T, + reader: &mut R, + writer: &mut W, + total_bytes: u64, +) -> io::Result +where + R: AsyncRead + Unpin, + W: AsyncWrite + Unpin, + T: ReportCopyProgress, +{ + let mut buf = vec![0; 8 * 1024]; + let mut bytes_so_far = 0; + let mut bytes_last_reported = 0; + let report_granularity = std::cmp::min(total_bytes / 10, 2 * 1024 * 1024); + + reporter.report_progress(0, total_bytes); + + loop { + let read_buf = match reader.read(&mut buf).await { + Ok(0) => break, + Ok(n) => &buf[..n], + Err(e) => return Err(e), + }; + + writer.write_all(read_buf).await?; + + bytes_so_far += read_buf.len() as u64; + if bytes_so_far - bytes_last_reported > report_granularity { + bytes_last_reported = bytes_so_far; + reporter.report_progress(bytes_so_far, total_bytes); + } + } + + reporter.report_progress(bytes_so_far, total_bytes); + + Ok(bytes_so_far) +} + +/// Helper used when converting Future interfaces to poll-based interfaces. +/// Stores excess data that can be reused on future polls. +#[derive(Default)] +pub(crate) struct ReadBuffer(Option<(Vec, usize)>); + +impl ReadBuffer { + /// Removes any data stored in the read buffer + pub fn take_data(&mut self) -> Option<(Vec, usize)> { + self.0.take() + } + + /// Writes as many bytes as possible to the readbuf, stashing any extra. + pub fn put_data( + &mut self, + target: &mut tokio::io::ReadBuf<'_>, + bytes: Vec, + start: usize, + ) -> Poll> { + if bytes.is_empty() { + self.0 = None; + // should not return Ok(), since if nothing is written to the target + // it signals EOF. Instead wait for more data from the source. + return Poll::Pending; + } + + if target.remaining() >= bytes.len() - start { + target.put_slice(&bytes[start..]); + self.0 = None; + } else { + let end = start + target.remaining(); + target.put_slice(&bytes[start..end]); + self.0 = Some((bytes, end)); + } + + Poll::Ready(Ok(())) + } +} + +#[derive(Debug)] +pub enum TailEvent { + /// A new line was read from the file. The line includes its trailing newline character. + Line(String), + /// The file appears to have been rewritten (size shrunk) + Reset, + /// An error was encountered with the file. + Err(io::Error), +} + +/// Simple, naive implementation of `tail -f -n `. Uses polling, so +/// it's not the fastest, but simple and working for easy cases. +pub fn tailf(file: File, n: usize) -> mpsc::UnboundedReceiver { + let (tx, rx) = mpsc::unbounded_channel(); + let mut last_len = match file.metadata() { + Ok(m) => m.len(), + Err(e) => { + tx.send(TailEvent::Err(e)).ok(); + return rx; + } + }; + + let mut reader = io::BufReader::new(file); + let mut pos = 0; + + // Read the initial "n" lines back from the request. initial_lines + // is a small ring buffer. + let mut initial_lines = RingBuffer::new(n); + loop { + let mut line = String::new(); + let bytes_read = match reader.read_line(&mut line) { + Ok(0) => break, + Ok(n) => n, + Err(e) => { + tx.send(TailEvent::Err(e)).ok(); + return rx; + } + }; + + if !line.ends_with('\n') { + // EOF + break; + } + + pos += bytes_read as u64; + initial_lines.push(line); + } + + for line in initial_lines.into_iter() { + tx.send(TailEvent::Line(line)).ok(); + } + + // now spawn the poll process to keep reading new lines + tokio::spawn(async move { + let poll_interval = Duration::from_millis(500); + + loop { + tokio::select! { + _ = sleep(poll_interval) => {}, + _ = tx.closed() => return + } + + match reader.get_ref().metadata() { + Err(e) => { + tx.send(TailEvent::Err(e)).ok(); + return; + } + Ok(m) => { + if m.len() == last_len { + continue; + } + + if m.len() < last_len { + tx.send(TailEvent::Reset).ok(); + pos = 0; + } + + last_len = m.len(); + } + } + + if let Err(e) = reader.seek(io::SeekFrom::Start(pos)) { + tx.send(TailEvent::Err(e)).ok(); + return; + } + + loop { + let mut line = String::new(); + let n = match reader.read_line(&mut line) { + Ok(0) => break, + Ok(n) => n, + Err(e) => { + tx.send(TailEvent::Err(e)).ok(); + return; + } + }; + + if n == 0 || !line.ends_with('\n') { + break; + } + + pos += n as u64; + if tx.send(TailEvent::Line(line)).is_err() { + return; + } + } + } + }); + + rx +} + +#[cfg(test)] +mod tests { + use rand::Rng; + use std::{fs::OpenOptions, io::Write}; + + use super::*; + + #[tokio::test] + async fn test_tailf_empty() { + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("tmp"); + + let read_file = OpenOptions::new() + .write(true) + .read(true) + .create(true) + .open(&file_path) + .unwrap(); + + let mut rx = tailf(read_file, 32); + assert!(rx.try_recv().is_err()); + + let mut append_file = OpenOptions::new() + .write(true) + .append(true) + .open(&file_path) + .unwrap(); + writeln!(&mut append_file, "some line").unwrap(); + + let recv = rx.recv().await; + if let Some(TailEvent::Line(l)) = recv { + assert_eq!("some line\n".to_string(), l); + } else { + unreachable!("expect a line event, got {:?}", recv) + } + + write!(&mut append_file, "partial ").unwrap(); + writeln!(&mut append_file, "line").unwrap(); + + let recv = rx.recv().await; + if let Some(TailEvent::Line(l)) = recv { + assert_eq!("partial line\n".to_string(), l); + } else { + unreachable!("expect a line event, got {:?}", recv) + } + } + + #[tokio::test] + async fn test_tailf_resets() { + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("tmp"); + + let mut read_file = OpenOptions::new() + .write(true) + .read(true) + .create(true) + .open(&file_path) + .unwrap(); + + writeln!(&mut read_file, "some existing content").unwrap(); + let mut rx = tailf(read_file, 0); + assert!(rx.try_recv().is_err()); + + let mut append_file = File::create(&file_path).unwrap(); // truncates + writeln!(&mut append_file, "some line").unwrap(); + + let recv = rx.recv().await; + if let Some(TailEvent::Reset) = recv { + // ok + } else { + unreachable!("expect a reset event, got {:?}", recv) + } + + let recv = rx.recv().await; + if let Some(TailEvent::Line(l)) = recv { + assert_eq!("some line\n".to_string(), l); + } else { + unreachable!("expect a line event, got {:?}", recv) + } + } + + #[tokio::test] + async fn test_tailf_with_data() { + let dir = tempfile::tempdir().unwrap(); + let file_path = dir.path().join("tmp"); + + let mut read_file = OpenOptions::new() + .write(true) + .read(true) + .create(true) + .open(&file_path) + .unwrap(); + let mut rng = rand::thread_rng(); + + let mut written = vec![]; + let base_line = "Elit ipsum cillum ex cillum. Adipisicing consequat cupidatat do proident ut in sunt Lorem ipsum tempor. Eiusmod ipsum Lorem labore exercitation sunt pariatur excepteur fugiat cillum velit cillum enim. Nisi Lorem cupidatat ad enim velit officia eiusmod esse tempor aliquip. Deserunt pariatur tempor in duis culpa esse sit nulla irure ullamco ipsum voluptate non laboris. Occaecat officia nulla officia mollit do aliquip reprehenderit ad incididunt."; + for i in 0..100 { + let line = format!("{}: {}", i, &base_line[..rng.gen_range(0..base_line.len())]); + writeln!(&mut read_file, "{}", line).unwrap(); + written.push(line); + } + write!(&mut read_file, "partial line").unwrap(); + read_file.seek(io::SeekFrom::Start(0)).unwrap(); + + let last_n = 32; + let mut rx = tailf(read_file, last_n); + for i in 0..last_n { + let recv = rx.try_recv().unwrap(); + if let TailEvent::Line(l) = recv { + let mut expected = written[written.len() - last_n + i].to_string(); + expected.push('\n'); + assert_eq!(expected, l); + } else { + unreachable!("expect a line event, got {:?}", recv) + } + } + + assert!(rx.try_recv().is_err()); + + let mut append_file = OpenOptions::new() + .write(true) + .append(true) + .open(&file_path) + .unwrap(); + writeln!(append_file, " is now complete").unwrap(); + + let recv = rx.recv().await; + if let Some(TailEvent::Line(l)) = recv { + assert_eq!("partial line is now complete\n".to_string(), l); + } else { + unreachable!("expect a line event, got {:?}", recv) + } + } +} diff --git a/cli/src/util/is_integrated.rs b/cli/src/util/is_integrated.rs new file mode 100644 index 0000000000..65cefbe27d --- /dev/null +++ b/cli/src/util/is_integrated.rs @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{env, io}; + +/// Gets whether the current CLI seems like it's running in integrated mode, +/// by looking at the location of the exe and known VS Code files. +pub fn is_integrated_cli() -> io::Result { + let exe = env::current_exe()?; + + let parent = match exe.parent() { + Some(parent) if parent.file_name().and_then(|n| n.to_str()) == Some("bin") => parent, + _ => return Ok(false), + }; + + let parent = match parent.parent() { + Some(p) => p, + None => return Ok(false), + }; + + let expected_file = if cfg!(target_os = "macos") { + "node_modules.asar" + } else { + "resources.pak" + }; + + Ok(parent.join(expected_file).exists()) +} diff --git a/cli/src/util/machine.rs b/cli/src/util/machine.rs new file mode 100644 index 0000000000..6db4d13af3 --- /dev/null +++ b/cli/src/util/machine.rs @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{path::Path, time::Duration}; +use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; + +pub fn process_at_path_exists(pid: u32, name: &Path) -> bool { + let mut sys = System::new(); + let pid = Pid::from_u32(pid); + if !sys.refresh_process(pid) { + return false; + } + + let name_str = format!("{}", name.display()); + if let Some(process) = sys.process(pid) { + for cmd in process.cmd() { + if cmd.contains(&name_str) { + return true; + } + } + } + + false +} +pub fn process_exists(pid: u32) -> bool { + let mut sys = System::new(); + sys.refresh_process(Pid::from_u32(pid)) +} + +pub async fn wait_until_process_exits(pid: Pid, poll_ms: u64) { + let mut s = System::new(); + let duration = Duration::from_millis(poll_ms); + while s.refresh_process(pid) { + tokio::time::sleep(duration).await; + } +} + +pub fn find_running_process(name: &Path) -> Option { + let mut sys = System::new(); + sys.refresh_processes(); + + let name_str = format!("{}", name.display()); + + for (pid, process) in sys.processes() { + for cmd in process.cmd() { + if cmd.contains(&name_str) { + return Some(pid.as_u32()); + } + } + } + None +} + +pub async fn wait_until_exe_deleted(current_exe: &Path, poll_ms: u64) { + let duration = Duration::from_millis(poll_ms); + while current_exe.exists() { + tokio::time::sleep(duration).await; + } +} diff --git a/cli/src/util/os.rs b/cli/src/util/os.rs new file mode 100644 index 0000000000..4ac76713d6 --- /dev/null +++ b/cli/src/util/os.rs @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +#[cfg(windows)] +pub fn os_release() -> Result { + // The windows API *had* nice GetVersionEx/A APIs, but these were deprecated + // in Winodws 8 and there's no newer win API to get version numbers. So + // instead read the registry. + + use winreg::{enums::HKEY_LOCAL_MACHINE, RegKey}; + + let key = RegKey::predef(HKEY_LOCAL_MACHINE) + .open_subkey(r"SOFTWARE\Microsoft\Windows NT\CurrentVersion")?; + + let major: u32 = key.get_value("CurrentMajorVersionNumber")?; + let minor: u32 = key.get_value("CurrentMinorVersionNumber")?; + let build: String = key.get_value("CurrentBuild")?; + + Ok(format!("{}.{}.{}", major, minor, build)) +} + +#[cfg(unix)] +pub fn os_release() -> Result { + use std::{ffi::CStr, mem}; + + unsafe { + let mut ret = mem::MaybeUninit::zeroed(); + + if libc::uname(ret.as_mut_ptr()) != 0 { + return Err(std::io::Error::last_os_error()); + } + + let ret = ret.assume_init(); + let c_str: &CStr = CStr::from_ptr(ret.release.as_ptr()); + Ok(c_str.to_string_lossy().into_owned()) + } +} diff --git a/cli/src/util/prereqs.rs b/cli/src/util/prereqs.rs new file mode 100644 index 0000000000..a2701d257a --- /dev/null +++ b/cli/src/util/prereqs.rs @@ -0,0 +1,349 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use std::cmp::Ordering; + +use super::command::capture_command; +use crate::constants::QUALITYLESS_SERVER_NAME; +use crate::update_service::Platform; +use lazy_static::lazy_static; +use regex::bytes::Regex as BinRegex; +use regex::Regex; +use tokio::fs; + +use super::errors::CodeError; + +lazy_static! { + static ref LDCONFIG_STDC_RE: Regex = Regex::new(r"libstdc\+\+.* => (.+)").unwrap(); + static ref LDD_VERSION_RE: BinRegex = BinRegex::new(r"^ldd.*(.+)\.(.+)\s").unwrap(); + static ref GENERIC_VERSION_RE: Regex = Regex::new(r"^([0-9]+)\.([0-9]+)$").unwrap(); + static ref LIBSTD_CXX_VERSION_RE: BinRegex = + BinRegex::new(r"GLIBCXX_([0-9]+)\.([0-9]+)(?:\.([0-9]+))?").unwrap(); + static ref MIN_CXX_VERSION: SimpleSemver = SimpleSemver::new(3, 4, 18); + static ref MIN_LDD_VERSION: SimpleSemver = SimpleSemver::new(2, 17, 0); +} + +const NIXOS_TEST_PATH: &str = "/etc/NIXOS"; + +pub struct PreReqChecker {} + +impl Default for PreReqChecker { + fn default() -> Self { + Self::new() + } +} + +impl PreReqChecker { + pub fn new() -> PreReqChecker { + PreReqChecker {} + } + + #[cfg(not(target_os = "linux"))] + pub async fn verify(&self) -> Result { + Platform::env_default().ok_or_else(|| { + CodeError::UnsupportedPlatform(format!( + "{} {}", + std::env::consts::OS, + std::env::consts::ARCH + )) + }) + } + + #[cfg(target_os = "linux")] + pub async fn verify(&self) -> Result { + let (is_nixos, gnu_a, gnu_b, or_musl) = tokio::join!( + check_is_nixos(), + check_glibc_version(), + check_glibcxx_version(), + check_musl_interpreter() + ); + + if (gnu_a.is_ok() && gnu_b.is_ok()) || is_nixos { + return Ok(if cfg!(target_arch = "x86_64") { + Platform::LinuxX64 + } else if cfg!(target_arch = "arm") { + Platform::LinuxARM32 + } else { + Platform::LinuxARM64 + }); + } + + if or_musl.is_ok() { + return Ok(if cfg!(target_arch = "x86_64") { + Platform::LinuxAlpineX64 + } else { + Platform::LinuxAlpineARM64 + }); + } + + let mut errors: Vec = vec![]; + if let Err(e) = gnu_a { + errors.push(e); + } else if let Err(e) = gnu_b { + errors.push(e); + } + + if let Err(e) = or_musl { + errors.push(e); + } + + let bullets = errors + .iter() + .map(|e| format!(" - {}", e)) + .collect::>() + .join("\n"); + + Err(CodeError::PrerequisitesFailed { + bullets, + name: QUALITYLESS_SERVER_NAME, + }) + } +} + +#[allow(dead_code)] +async fn check_musl_interpreter() -> Result<(), String> { + const MUSL_PATH: &str = if cfg!(target_arch = "aarch64") { + "/lib/ld-musl-aarch64.so.1" + } else { + "/lib/ld-musl-x86_64.so.1" + }; + + if fs::metadata(MUSL_PATH).await.is_err() { + return Err(format!( + "find {}, which is required to run the {} in musl environments", + MUSL_PATH, QUALITYLESS_SERVER_NAME + )); + } + + Ok(()) +} + +#[allow(dead_code)] +async fn check_glibc_version() -> Result<(), String> { + #[cfg(target_env = "gnu")] + let version = { + let v = unsafe { libc::gnu_get_libc_version() }; + let v = unsafe { std::ffi::CStr::from_ptr(v) }; + let v = v.to_str().unwrap(); + extract_generic_version(v) + }; + #[cfg(not(target_env = "gnu"))] + let version = { + capture_command("ldd", ["--version"]) + .await + .ok() + .and_then(|o| extract_ldd_version(&o.stdout)) + }; + + if let Some(v) = version { + return if v >= *MIN_LDD_VERSION { + Ok(()) + } else { + Err(format!( + "find GLIBC >= 2.17 (but found {} instead) for GNU environments", + v + )) + }; + } + + Ok(()) +} + +/// Check for nixos to avoid mandating glibc versions. See: +/// https://github.com/microsoft/vscode-remote-release/issues/7129 +#[allow(dead_code)] +async fn check_is_nixos() -> bool { + fs::metadata(NIXOS_TEST_PATH).await.is_ok() +} + +#[allow(dead_code)] +async fn check_glibcxx_version() -> Result<(), String> { + let mut libstdc_path: Option = None; + + #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] + const DEFAULT_LIB_PATH: &str = "/usr/lib64/libstdc++.so.6"; + #[cfg(any(target_arch = "x86", target_arch = "arm"))] + const DEFAULT_LIB_PATH: &str = "/usr/lib/libstdc++.so.6"; + const LDCONFIG_PATH: &str = "/sbin/ldconfig"; + + if fs::metadata(DEFAULT_LIB_PATH).await.is_ok() { + libstdc_path = Some(DEFAULT_LIB_PATH.to_owned()); + } else if fs::metadata(LDCONFIG_PATH).await.is_ok() { + libstdc_path = capture_command(LDCONFIG_PATH, ["-p"]) + .await + .ok() + .and_then(|o| extract_libstd_from_ldconfig(&o.stdout)); + } + + match libstdc_path { + Some(path) => match fs::read(&path).await { + Ok(contents) => check_for_sufficient_glibcxx_versions(contents), + Err(e) => Err(format!( + "validate GLIBCXX version for GNU environments, but could not: {}", + e + )), + }, + None => Err("find libstdc++.so or ldconfig for GNU environments".to_owned()), + } +} + +#[allow(dead_code)] +fn check_for_sufficient_glibcxx_versions(contents: Vec) -> Result<(), String> { + let all_versions: Vec = LIBSTD_CXX_VERSION_RE + .captures_iter(&contents) + .map(|m| SimpleSemver { + major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())), + minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())), + patch: m.get(3).map_or(0, |s| u32_from_bytes(s.as_bytes())), + }) + .collect(); + + if !all_versions.iter().any(|v| &*MIN_CXX_VERSION >= v) { + return Err(format!( + "find GLIBCXX >= 3.4.18 (but found {} instead) for GNU environments", + all_versions + .iter() + .map(String::from) + .collect::>() + .join(", ") + )); + } + + Ok(()) +} + +#[allow(dead_code)] +fn extract_ldd_version(output: &[u8]) -> Option { + LDD_VERSION_RE.captures(output).map(|m| SimpleSemver { + major: m.get(1).map_or(0, |s| u32_from_bytes(s.as_bytes())), + minor: m.get(2).map_or(0, |s| u32_from_bytes(s.as_bytes())), + patch: 0, + }) +} + +#[allow(dead_code)] +fn extract_generic_version(output: &str) -> Option { + GENERIC_VERSION_RE.captures(output).map(|m| SimpleSemver { + major: m.get(1).map_or(0, |s| s.as_str().parse().unwrap()), + minor: m.get(2).map_or(0, |s| s.as_str().parse().unwrap()), + patch: 0, + }) +} + +fn extract_libstd_from_ldconfig(output: &[u8]) -> Option { + String::from_utf8_lossy(output) + .lines() + .find_map(|l| LDCONFIG_STDC_RE.captures(l)) + .and_then(|cap| cap.get(1)) + .map(|cap| cap.as_str().to_owned()) +} + +fn u32_from_bytes(b: &[u8]) -> u32 { + String::from_utf8_lossy(b).parse::().unwrap_or(0) +} + +#[derive(Debug, Default, PartialEq, Eq)] +struct SimpleSemver { + major: u32, + minor: u32, + patch: u32, +} + +impl PartialOrd for SimpleSemver { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for SimpleSemver { + fn cmp(&self, other: &Self) -> Ordering { + let major = self.major.cmp(&other.major); + if major != Ordering::Equal { + return major; + } + + let minor = self.minor.cmp(&other.minor); + if minor != Ordering::Equal { + return minor; + } + + self.patch.cmp(&other.patch) + } +} + +impl From<&SimpleSemver> for String { + fn from(s: &SimpleSemver) -> Self { + format!("v{}.{}.{}", s.major, s.minor, s.patch) + } +} + +impl std::fmt::Display for SimpleSemver { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", String::from(self)) + } +} + +#[allow(dead_code)] +impl SimpleSemver { + fn new(major: u32, minor: u32, patch: u32) -> SimpleSemver { + SimpleSemver { + major, + minor, + patch, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_extract_libstd_from_ldconfig() { + let actual = " + libstoken.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstoken.so.1 + libstemmer.so.0d (libc6,x86-64) => /lib/x86_64-linux-gnu/libstemmer.so.0d + libstdc++.so.6 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstdc++.so.6 + libstartup-notification-1.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstartup-notification-1.so.0 + libssl3.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libssl3.so + ".to_owned().into_bytes(); + + assert_eq!( + extract_libstd_from_ldconfig(&actual), + Some("/lib/x86_64-linux-gnu/libstdc++.so.6".to_owned()), + ); + + assert_eq!( + extract_libstd_from_ldconfig(&"nothing here!".to_owned().into_bytes()), + None, + ); + } + + #[test] + fn test_gte() { + assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(1, 2, 3)); + assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(0, 10, 10)); + assert!(SimpleSemver::new(1, 2, 3) >= SimpleSemver::new(1, 1, 10)); + + assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(1, 2, 10)); + assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(1, 3, 1)); + assert!(SimpleSemver::new(1, 2, 3) < SimpleSemver::new(2, 2, 1)); + } + + #[test] + fn check_for_sufficient_glibcxx_versions() { + let actual = "ldd (Ubuntu GLIBC 2.31-0ubuntu9.7) 2.31 + Copyright (C) 2020 Free Software Foundation, Inc. + This is free software; see the source for copying conditions. There is NO + warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + Written by Roland McGrath and Ulrich Drepper." + .to_owned() + .into_bytes(); + + assert_eq!( + extract_ldd_version(&actual), + Some(SimpleSemver::new(2, 31, 0)), + ); + } +} diff --git a/cli/src/util/ring_buffer.rs b/cli/src/util/ring_buffer.rs new file mode 100644 index 0000000000..e235162e41 --- /dev/null +++ b/cli/src/util/ring_buffer.rs @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +pub struct RingBuffer { + data: Vec, + i: usize, +} + +impl RingBuffer { + pub fn new(capacity: usize) -> Self { + Self { + data: Vec::with_capacity(capacity), + i: 0, + } + } + + pub fn capacity(&self) -> usize { + self.data.capacity() + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_full(&self) -> bool { + self.data.len() == self.data.capacity() + } + + pub fn is_empty(&self) -> bool { + self.data.len() == 0 + } + + pub fn push(&mut self, value: T) { + if self.data.len() == self.data.capacity() { + self.data[self.i] = value; + } else { + self.data.push(value); + } + + self.i = (self.i + 1) % self.data.capacity(); + } + + pub fn iter(&self) -> RingBufferIter<'_, T> { + RingBufferIter { + index: 0, + buffer: self, + } + } +} + +impl IntoIterator for RingBuffer { + type Item = T; + type IntoIter = OwnedRingBufferIter; + + fn into_iter(self) -> OwnedRingBufferIter + where + T: Default, + { + OwnedRingBufferIter { + index: 0, + buffer: self, + } + } +} + +pub struct OwnedRingBufferIter { + buffer: RingBuffer, + index: usize, +} + +impl Iterator for OwnedRingBufferIter { + type Item = T; + + fn next(&mut self) -> Option { + if self.index == self.buffer.len() { + return None; + } + + let ii = (self.index + self.buffer.i) % self.buffer.len(); + let item = std::mem::take(&mut self.buffer.data[ii]); + self.index += 1; + Some(item) + } +} + +pub struct RingBufferIter<'a, T> { + buffer: &'a RingBuffer, + index: usize, +} + +impl<'a, T> Iterator for RingBufferIter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + if self.index == self.buffer.len() { + return None; + } + + let ii = (self.index + self.buffer.i) % self.buffer.len(); + let item = &self.buffer.data[ii]; + self.index += 1; + Some(item) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_inserts() { + let mut rb = RingBuffer::new(3); + assert_eq!(rb.capacity(), 3); + assert!(!rb.is_full()); + assert_eq!(rb.len(), 0); + assert_eq!(rb.iter().copied().count(), 0); + + rb.push(1); + assert!(!rb.is_full()); + assert_eq!(rb.len(), 1); + assert_eq!(rb.iter().copied().collect::>(), vec![1]); + + rb.push(2); + assert!(!rb.is_full()); + assert_eq!(rb.len(), 2); + assert_eq!(rb.iter().copied().collect::>(), vec![1, 2]); + + rb.push(3); + assert!(rb.is_full()); + assert_eq!(rb.len(), 3); + assert_eq!(rb.iter().copied().collect::>(), vec![1, 2, 3]); + + rb.push(4); + assert!(rb.is_full()); + assert_eq!(rb.len(), 3); + assert_eq!(rb.iter().copied().collect::>(), vec![2, 3, 4]); + + assert_eq!(rb.into_iter().collect::>(), vec![2, 3, 4]); + } +} diff --git a/cli/src/util/sync.rs b/cli/src/util/sync.rs new file mode 100644 index 0000000000..991dd93ba1 --- /dev/null +++ b/cli/src/util/sync.rs @@ -0,0 +1,221 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use async_trait::async_trait; +use std::{marker::PhantomData, sync::Arc}; +use tokio::sync::{ + broadcast, mpsc, + watch::{self, error::RecvError}, +}; + +#[derive(Clone)] +pub struct Barrier(watch::Receiver>) +where + T: Clone; + +impl Barrier +where + T: Clone, +{ + /// Waits for the barrier to be closed, returning a value if one was sent. + pub async fn wait(&mut self) -> Result { + loop { + self.0.changed().await?; + + if let Some(v) = self.0.borrow().clone() { + return Ok(v); + } + } + } + + /// Gets whether the barrier is currently open + pub fn is_open(&self) -> bool { + self.0.borrow().is_some() + } +} + +#[async_trait] +impl Receivable for Barrier { + async fn recv_msg(&mut self) -> Option { + self.wait().await.ok() + } +} + +#[derive(Clone)] +pub struct BarrierOpener(Arc>>); + +impl BarrierOpener { + /// Opens the barrier. + pub fn open(&self, value: T) { + self.0.send_if_modified(|v| { + if v.is_none() { + *v = Some(value); + true + } else { + false + } + }); + } +} + +/// The Barrier is something that can be opened once from one side, +/// and is thereafter permanently closed. It can contain a value. +pub fn new_barrier() -> (Barrier, BarrierOpener) +where + T: Copy, +{ + let (closed_tx, closed_rx) = watch::channel(None); + (Barrier(closed_rx), BarrierOpener(Arc::new(closed_tx))) +} + +/// Type that can receive messages in an async way. +#[async_trait] +pub trait Receivable { + async fn recv_msg(&mut self) -> Option; +} + +// todo: ideally we would use an Arc in the broadcast::Receiver to avoid having +// to clone bytes everywhere, requires updating rpc consumers as well. +#[async_trait] +impl Receivable for broadcast::Receiver { + async fn recv_msg(&mut self) -> Option { + loop { + match self.recv().await { + Ok(v) => return Some(v), + Err(broadcast::error::RecvError::Lagged(_)) => continue, + Err(broadcast::error::RecvError::Closed) => return None, + } + } + } +} + +#[async_trait] +impl Receivable for mpsc::UnboundedReceiver { + async fn recv_msg(&mut self) -> Option { + self.recv().await + } +} + +#[async_trait] +impl Receivable for () { + async fn recv_msg(&mut self) -> Option { + futures::future::pending().await + } +} + +pub struct ConcatReceivable, B: Receivable> { + left: Option, + right: B, + _marker: PhantomData, +} + +impl, B: Receivable> ConcatReceivable { + pub fn new(left: A, right: B) -> Self { + Self { + left: Some(left), + right, + _marker: PhantomData, + } + } +} + +#[async_trait] +impl, B: Send + Receivable> Receivable + for ConcatReceivable +{ + async fn recv_msg(&mut self) -> Option { + if let Some(left) = &mut self.left { + match left.recv_msg().await { + Some(v) => return Some(v), + None => { + self.left = None; + } + } + } + + return self.right.recv_msg().await; + } +} + +pub struct MergedReceivable, B: Receivable> { + left: Option, + right: Option, + _marker: PhantomData, +} + +impl, B: Receivable> MergedReceivable { + pub fn new(left: A, right: B) -> Self { + Self { + left: Some(left), + right: Some(right), + _marker: PhantomData, + } + } +} + +#[async_trait] +impl, B: Send + Receivable> Receivable + for MergedReceivable +{ + async fn recv_msg(&mut self) -> Option { + loop { + match (&mut self.left, &mut self.right) { + (Some(left), Some(right)) => { + tokio::select! { + left = left.recv_msg() => match left { + Some(v) => return Some(v), + None => { self.left = None; continue; }, + }, + right = right.recv_msg() => match right { + Some(v) => return Some(v), + None => { self.right = None; continue; }, + }, + } + } + (Some(a), None) => break a.recv_msg().await, + (None, Some(b)) => break b.recv_msg().await, + (None, None) => break None, + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_barrier_close_after_spawn() { + let (mut barrier, opener) = new_barrier::(); + let (tx, rx) = tokio::sync::oneshot::channel::(); + + tokio::spawn(async move { + tx.send(barrier.wait().await.unwrap()).unwrap(); + }); + + opener.open(42); + + assert!(rx.await.unwrap() == 42); + } + + #[tokio::test] + async fn test_barrier_close_before_spawn() { + let (barrier, opener) = new_barrier::(); + let (tx1, rx1) = tokio::sync::oneshot::channel::(); + let (tx2, rx2) = tokio::sync::oneshot::channel::(); + + opener.open(42); + let mut b1 = barrier.clone(); + tokio::spawn(async move { + tx1.send(b1.wait().await.unwrap()).unwrap(); + }); + let mut b2 = barrier.clone(); + tokio::spawn(async move { + tx2.send(b2.wait().await.unwrap()).unwrap(); + }); + + assert!(rx1.await.unwrap() == 42); + assert!(rx2.await.unwrap() == 42); + } +} diff --git a/cli/src/util/tar.rs b/cli/src/util/tar.rs new file mode 100644 index 0000000000..38d758a7e7 --- /dev/null +++ b/cli/src/util/tar.rs @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use crate::util::errors::{wrap, WrappedError}; + +use flate2::read::GzDecoder; +use std::fs; +use std::io::{Seek, SeekFrom}; +use std::path::{Path, PathBuf}; +use tar::Archive; + +use super::io::ReportCopyProgress; + +fn should_skip_first_segment(file: &fs::File) -> Result { + // unfortunately, we need to re-read the archive here since you cannot reuse + // `.entries()`. But this will generally only look at one or two files, so this + // should be acceptably speedy... If not, we could hardcode behavior for + // different types of archives. + + let tar = GzDecoder::new(file); + let mut archive = Archive::new(tar); + let mut entries = archive + .entries() + .map_err(|e| wrap(e, "error opening archive"))?; + + let first_name = { + let file = entries + .next() + .expect("expected not to have an empty archive") + .map_err(|e| wrap(e, "error reading entry file"))?; + + let path = file.path().expect("expected to have path"); + + path.iter() + .next() + .expect("expected to have non-empty name") + .to_owned() + }; + + let mut had_multiple = false; + for file in entries.flatten() { + had_multiple = true; + if let Ok(name) = file.path() { + if name.iter().next() != Some(&first_name) { + return Ok(false); + } + } + } + + Ok(had_multiple) // prefix removal is invalid if there's only a single file +} + +pub fn decompress_tarball( + path: &Path, + parent_path: &Path, + mut reporter: T, +) -> Result<(), WrappedError> +where + T: ReportCopyProgress, +{ + let mut tar_gz = fs::File::open(path) + .map_err(|e| wrap(e, format!("error opening file {}", path.display())))?; + let skip_first = should_skip_first_segment(&tar_gz)?; + + // reset since skip logic read the tar already: + tar_gz + .seek(SeekFrom::Start(0)) + .map_err(|e| wrap(e, "error resetting seek position"))?; + + let tar = GzDecoder::new(tar_gz); + let mut archive = Archive::new(tar); + + let results = archive + .entries() + .map_err(|e| wrap(e, format!("error opening archive {}", path.display())))? + .filter_map(|e| e.ok()) + .map(|mut entry| { + let entry_path = entry + .path() + .map_err(|e| wrap(e, "error reading entry path"))?; + + let path = parent_path.join(if skip_first { + entry_path.iter().skip(1).collect::() + } else { + entry_path.into_owned() + }); + + if let Some(p) = path.parent() { + fs::create_dir_all(p) + .map_err(|e| wrap(e, format!("could not create dir for {}", p.display())))?; + } + + entry + .unpack(&path) + .map_err(|e| wrap(e, format!("error unpacking {}", path.display())))?; + Ok(path) + }) + .collect::, WrappedError>>()?; + + // Tarballs don't have a way to get the number of entries ahead of time + reporter.report_progress(results.len() as u64, results.len() as u64); + + Ok(()) +} diff --git a/cli/src/util/zipper.rs b/cli/src/util/zipper.rs new file mode 100644 index 0000000000..82bc5aa20c --- /dev/null +++ b/cli/src/util/zipper.rs @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +use super::errors::{wrap, WrappedError}; +use super::io::ReportCopyProgress; +use std::fs::{self, File}; +use std::io; +use std::path::Path; +use std::path::PathBuf; +use zip::read::ZipFile; +use zip::{self, ZipArchive}; + +// Borrowed and modified from https://github.com/zip-rs/zip/blob/master/examples/extract.rs + +/// Returns whether all files in the archive start with the same path segment. +/// If so, it's an indication we should skip that segment when extracting. +fn should_skip_first_segment(archive: &mut ZipArchive) -> bool { + let first_name = { + let file = archive + .by_index_raw(0) + .expect("expected not to have an empty archive"); + + let path = file + .enclosed_name() + .expect("expected to have path") + .iter() + .next() + .expect("expected to have non-empty name"); + + path.to_owned() + }; + + for i in 1..archive.len() { + if let Ok(file) = archive.by_index_raw(i) { + if let Some(name) = file.enclosed_name() { + if name.iter().next() != Some(&first_name) { + return false; + } + } + } + } + + archive.len() > 1 // prefix removal is invalid if there's only a single file +} + +pub fn unzip_file(path: &Path, parent_path: &Path, mut reporter: T) -> Result<(), WrappedError> +where + T: ReportCopyProgress, +{ + let file = fs::File::open(path) + .map_err(|e| wrap(e, format!("unable to open file {}", path.display())))?; + + let mut archive = zip::ZipArchive::new(file) + .map_err(|e| wrap(e, format!("failed to open zip archive {}", path.display())))?; + + let skip_segments_no = usize::from(should_skip_first_segment(&mut archive)); + for i in 0..archive.len() { + reporter.report_progress(i as u64, archive.len() as u64); + let mut file = archive + .by_index(i) + .map_err(|e| wrap(e, format!("could not open zip entry {}", i)))?; + + let outpath: PathBuf = match file.enclosed_name() { + Some(path) => { + let mut full_path = PathBuf::from(parent_path); + full_path.push(PathBuf::from_iter(path.iter().skip(skip_segments_no))); + full_path + } + None => continue, + }; + + if file.is_dir() || file.name().ends_with('/') { + fs::create_dir_all(&outpath) + .map_err(|e| wrap(e, format!("could not create dir for {}", outpath.display())))?; + apply_permissions(&file, &outpath)?; + continue; + } + + if let Some(p) = outpath.parent() { + fs::create_dir_all(p) + .map_err(|e| wrap(e, format!("could not create dir for {}", outpath.display())))?; + } + + #[cfg(unix)] + { + use libc::S_IFLNK; + use std::io::Read; + use std::os::unix::ffi::OsStringExt; + + if matches!(file.unix_mode(), Some(mode) if mode & (S_IFLNK as u32) == (S_IFLNK as u32)) + { + let mut link_to = Vec::new(); + file.read_to_end(&mut link_to).map_err(|e| { + wrap( + e, + format!("could not read symlink linkpath {}", outpath.display()), + ) + })?; + + let link_path = PathBuf::from(std::ffi::OsString::from_vec(link_to)); + std::os::unix::fs::symlink(link_path, &outpath).map_err(|e| { + wrap(e, format!("could not create symlink {}", outpath.display())) + })?; + continue; + } + } + + let mut outfile = fs::File::create(&outpath).map_err(|e| { + wrap( + e, + format!( + "unable to open file to write {} (from {:?})", + outpath.display(), + file.enclosed_name().map(|p| p.to_string_lossy()), + ), + ) + })?; + + io::copy(&mut file, &mut outfile) + .map_err(|e| wrap(e, format!("error copying file {}", outpath.display())))?; + + apply_permissions(&file, &outpath)?; + } + + reporter.report_progress(archive.len() as u64, archive.len() as u64); + + Ok(()) +} + +#[cfg(unix)] +fn apply_permissions(file: &ZipFile, outpath: &Path) -> Result<(), WrappedError> { + use std::os::unix::fs::PermissionsExt; + + if let Some(mode) = file.unix_mode() { + fs::set_permissions(outpath, fs::Permissions::from_mode(mode)).map_err(|e| { + wrap( + e, + format!("error setting permissions on {}", outpath.display()), + ) + })?; + } + + Ok(()) +} + +#[cfg(windows)] +fn apply_permissions(_file: &ZipFile, _outpath: &Path) -> Result<(), WrappedError> { + Ok(()) +} diff --git a/extensions/.eslintrc.json b/extensions/.eslintrc.json index d318e47f29..812176566b 100644 --- a/extensions/.eslintrc.json +++ b/extensions/.eslintrc.json @@ -1,9 +1,13 @@ { + "parserOptions": { + "project": "./extensions/tsconfig.base.json", + "createDefaultProgram": true + }, "rules": { "no-cond-assign": 2, "jsdoc/check-param-names": "error", - "@typescript-eslint/explicit-function-return-type": ["error"], + "@typescript-eslint/explicit-function-return-type": ["off"], "@typescript-eslint/await-thenable": ["error"], - "@typescript-eslint/no-unsafe-assignment": "error" + "@typescript-eslint/no-unsafe-assignment": "off" } } diff --git a/extensions/csharp/yarn.lock b/extensions/admin-pack/yarn.lock similarity index 100% rename from extensions/csharp/yarn.lock rename to extensions/admin-pack/yarn.lock diff --git a/extensions/arc/src/test/stubs.ts b/extensions/arc/src/test/stubs.ts index c7dd1aae44..1c27124ffe 100644 --- a/extensions/arc/src/test/stubs.ts +++ b/extensions/arc/src/test/stubs.ts @@ -38,6 +38,7 @@ export class MockInputBox implements vscode.InputBox { enabled: boolean = false; busy: boolean = false; ignoreFocusOut: boolean = false; + valueSelection: readonly [number, number] | undefined = undefined; show(): void { } hide(): void { diff --git a/extensions/fsharp/yarn.lock b/extensions/asde-deployment/yarn.lock similarity index 100% rename from extensions/fsharp/yarn.lock rename to extensions/asde-deployment/yarn.lock diff --git a/extensions/azurecore/src/test/account-provider/auths/azureAuth.test.ts b/extensions/azurecore/src/test/account-provider/auths/azureAuth.test.ts index a836e65993..e26b8f746f 100644 --- a/extensions/azurecore/src/test/account-provider/auths/azureAuth.test.ts +++ b/extensions/azurecore/src/test/account-provider/auths/azureAuth.test.ts @@ -99,7 +99,7 @@ describe('Azure Authentication', function () { should(securityToken).be.undefined(); }); it('dont find correct resources', async function () { - const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, TypeMoq.It.isAny(), -1); + const securityToken = await azureAuthCodeGrant.object.getAccountSecurityTokenAdal(mockAccount, TypeMoq.It.isAny(), -1); should(securityToken).be.undefined(); }); it('incorrect tenant', async function () { diff --git a/extensions/configuration-editing/.vscodeignore b/extensions/configuration-editing/.vscodeignore index c5234c346a..cfa2d155c5 100644 --- a/extensions/configuration-editing/.vscodeignore +++ b/extensions/configuration-editing/.vscodeignore @@ -6,4 +6,5 @@ extension.webpack.config.js extension-browser.webpack.config.js yarn.lock build/** -schemas/*.schema.src.json +schemas/devContainer.codespaces.schema.json +schemas/devContainer.vscode.schema.json diff --git a/extensions/configuration-editing/build/inline-allOf.ts b/extensions/configuration-editing/build/inline-allOf.ts deleted file mode 100755 index 3057e5a6db..0000000000 --- a/extensions/configuration-editing/build/inline-allOf.ts +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env ts-node - -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. 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 deleted file mode 100644 index 0f4c9da0af..0000000000 --- a/extensions/configuration-editing/build/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "resolveJsonModule": true, - "outDir": "./out" - } -} diff --git a/extensions/configuration-editing/extension-browser.webpack.config.js b/extensions/configuration-editing/extension-browser.webpack.config.js index 8c21467f46..2d0ff9157e 100644 --- a/extensions/configuration-editing/extension-browser.webpack.config.js +++ b/extensions/configuration-editing/extension-browser.webpack.config.js @@ -7,6 +7,7 @@ 'use strict'; +const path = require('path'); const withBrowserDefaults = require('../shared.webpack.config').browser; module.exports = withBrowserDefaults({ @@ -16,6 +17,11 @@ module.exports = withBrowserDefaults({ }, output: { filename: 'configurationEditingMain.js' + }, + resolve: { + alias: { + './node/net': path.resolve(__dirname, 'src', 'browser', 'net'), + } } }); diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index fdd714a55c..676e0b2f0f 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -10,8 +10,11 @@ }, "icon": "images/icon.png", "activationEvents": [ - "onLanguage:json", - "onLanguage:jsonc" + "onProfile", + "onProfile:github" + ], + "enabledApiProposals": [ + "profileContentHandlers" ], "main": "./out/configurationEditingMain", "browser": "./dist/browser/configurationEditingMain", @@ -21,7 +24,8 @@ }, "dependencies": { "jsonc-parser": "^2.2.1", - "vscode-nls": "^5.0.0" + "@octokit/rest": "19.0.4", + "tunnel": "^0.0.6" }, "capabilities": { "virtualWorkspaces": true, @@ -71,6 +75,10 @@ "fileMatch": "%APP_SETTINGS_HOME%/keybindings.json", "url": "vscode://schemas/keybindings" }, + { + "fileMatch": "%APP_SETTINGS_HOME%/profiles/*/keybindings.json", + "url": "vscode://schemas/keybindings" + }, { "fileMatch": "vscode://defaultsettings/*.json", "url": "vscode://schemas/settings/default" @@ -119,6 +127,10 @@ "fileMatch": "%APP_SETTINGS_HOME%/snippets/*.json", "url": "vscode://schemas/snippets" }, + { + "fileMatch": "%APP_SETTINGS_HOME%/profiles/*/snippets/.json", + "url": "vscode://schemas/snippets" + }, { "fileMatch": "%APP_SETTINGS_HOME%/sync/snippets/preview/*.json", "url": "vscode://schemas/snippets" @@ -133,11 +145,11 @@ }, { "fileMatch": "devcontainer.json", - "url": "./schemas/devContainer.schema.generated.json" + "url": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json" }, { "fileMatch": ".devcontainer.json", - "url": "./schemas/devContainer.schema.generated.json" + "url": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json" }, { "fileMatch": "%APP_SETTINGS_HOME%/globalStorage/ms-vscode-remote.remote-containers/nameConfigs/*.json", diff --git a/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json b/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json index d224f91910..681ca6105c 100644 --- a/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.codespaces.schema.json @@ -173,6 +173,13 @@ ] } } + }, + "openFiles": { + "type": "array", + "description": "The paths to the files to open when the codespace is created. Paths are relative to the workspace.", + "items": { + "type": "string" + } } } } diff --git a/extensions/configuration-editing/schemas/devContainer.schema.generated.json b/extensions/configuration-editing/schemas/devContainer.schema.generated.json deleted file mode 100644 index 4ae5033f3f..0000000000 --- a/extensions/configuration-editing/schemas/devContainer.schema.generated.json +++ /dev/null @@ -1,2869 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "Defines a dev container", - "allowComments": true, - "allowTrailingCommas": false, - "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." - }, - "cacheFrom": { - "type": [ - "string", - "array" - ], - "description": "The image to consider as a cache. Use an array to specify multiple images.", - "items": { - "type": "string" - } - } - }, - "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." - }, - "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 for the dev container displayed in the UI." - }, - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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-]+)*))?)|@prerelease)?$", - "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." - }, - "features": { - "type": "object", - "description": "Features to add to the dev container.", - "additionalProperties": true - }, - "forwardPorts": { - "type": "array", - "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", - "items": { - "oneOf": [ - { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - { - "type": "string", - "pattern": "^([a-z0-9-]+):(\\d{1,5})$" - } - ] - } - }, - "portsAttributes": { - "type": "object", - "patternProperties": { - "(^\\d+(-\\d+)?$)|(.+)": { - "type": "object", - "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openBrowserOnce", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "default": { - "label": "Application", - "onAutoForward": "notify" - }, - "additionalProperties": false - } - }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", - "defaultSnippets": [ - { - "body": { - "${1:3000}": { - "label": "${2:Application}", - "onAutoForward": "notify" - } - } - } - ], - "additionalProperties": false - }, - "otherPortsAttributes": { - "type": "object", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "defaultSnippets": [ - { - "body": { - "onAutoForward": "ignore" - } - } - ], - "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", - "additionalProperties": false - }, - "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 when opening from a local folder." - }, - "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. This command is run before \"onCreateCommand\". 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" - } - }, - "onCreateCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". 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" - } - }, - "updateContentCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". 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. This command is run after \"updateContentCommand\" and before \"postStartCommand\". 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. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". 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 when attaching to the container. This command is run after \"postStartCommand\". 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" - } - }, - "waitFor": { - "type": "string", - "enum": [ - "initializeCommand", - "onCreateCommand", - "updateContentCommand", - "postCreateCommand", - "postStartCommand" - ], - "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." - }, - "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 \"loginInteractiveShell\"." - }, - "codespaces": { - "type": "object", - "additionalProperties": true, - "description": "Codespaces-specific configuration." - }, - "hostRequirements": { - "type": "object", - "description": "Host hardware requirements.", - "properties": { - "cpus": { - "type": "integer", - "minimum": 1, - "description": "Number of required CPUs." - }, - "memory": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." - }, - "storage": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." - } - }, - "additionalProperties": false - }, - "customizations": { - "type": "object", - "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.", - "properties": { - "vscode": { - "type": "object", - "properties": { - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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." - }, - "devPort": { - "type": "integer", - "description": "The port VS Code can use to connect to its backend." - } - }, - "additionalProperties": false - }, - "codespaces": { - "type": "object", - "description": "Customizations specific to GitHub Codespaces", - "properties": { - "repositories": { - "type": "object", - "description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')", - "patternProperties": { - "^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": { - "type": "object", - "additionalProperties": true, - "oneOf": [ - { - "properties": { - "permissions": { - "type": "object", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "additionalProperties": true, - "anyOf": [ - { - "properties": { - "actions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "checks": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "contents": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "deployments": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "discussions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "issues": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "packages": { - "type": "string", - "enum": [ - "read" - ] - } - } - }, - { - "properties": { - "pages": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "pull_requests": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "repository_projects": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "statuses": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "workflows": { - "type": "string", - "enum": [ - "write" - ] - } - } - } - ] - } - } - }, - { - "properties": { - "permissions": { - "type": "string", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "enum": [ - "read-all", - "write-all" - ] - } - } - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "additionalProperties": { - "type": "object", - "additionalProperties": true - } - }, - "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." - }, - "cacheFrom": { - "type": [ - "string", - "array" - ], - "description": "The image to consider as a cache. Use an array to specify multiple images.", - "items": { - "type": "string" - } - } - }, - "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." - }, - "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 for the dev container displayed in the UI." - }, - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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-]+)*))?)|@prerelease)?$", - "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." - }, - "features": { - "type": "object", - "description": "Features to add to the dev container.", - "additionalProperties": true - }, - "forwardPorts": { - "type": "array", - "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", - "items": { - "oneOf": [ - { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - { - "type": "string", - "pattern": "^([a-z0-9-]+):(\\d{1,5})$" - } - ] - } - }, - "portsAttributes": { - "type": "object", - "patternProperties": { - "(^\\d+(-\\d+)?$)|(.+)": { - "type": "object", - "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openBrowserOnce", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "default": { - "label": "Application", - "onAutoForward": "notify" - }, - "additionalProperties": false - } - }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", - "defaultSnippets": [ - { - "body": { - "${1:3000}": { - "label": "${2:Application}", - "onAutoForward": "notify" - } - } - } - ], - "additionalProperties": false - }, - "otherPortsAttributes": { - "type": "object", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "defaultSnippets": [ - { - "body": { - "onAutoForward": "ignore" - } - } - ], - "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", - "additionalProperties": false - }, - "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 when opening from a local folder." - }, - "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. This command is run before \"onCreateCommand\". 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" - } - }, - "onCreateCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". 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" - } - }, - "updateContentCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". 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. This command is run after \"updateContentCommand\" and before \"postStartCommand\". 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. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". 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 when attaching to the container. This command is run after \"postStartCommand\". 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" - } - }, - "waitFor": { - "type": "string", - "enum": [ - "initializeCommand", - "onCreateCommand", - "updateContentCommand", - "postCreateCommand", - "postStartCommand" - ], - "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." - }, - "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 \"loginInteractiveShell\"." - }, - "codespaces": { - "type": "object", - "additionalProperties": true, - "description": "Codespaces-specific configuration." - }, - "hostRequirements": { - "type": "object", - "description": "Host hardware requirements.", - "properties": { - "cpus": { - "type": "integer", - "minimum": 1, - "description": "Number of required CPUs." - }, - "memory": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." - }, - "storage": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." - } - }, - "additionalProperties": false - }, - "customizations": { - "type": "object", - "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.", - "properties": { - "vscode": { - "type": "object", - "properties": { - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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." - }, - "devPort": { - "type": "integer", - "description": "The port VS Code can use to connect to its backend." - } - }, - "additionalProperties": false - }, - "codespaces": { - "type": "object", - "description": "Customizations specific to GitHub Codespaces", - "properties": { - "repositories": { - "type": "object", - "description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')", - "patternProperties": { - "^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": { - "type": "object", - "additionalProperties": true, - "oneOf": [ - { - "properties": { - "permissions": { - "type": "object", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "additionalProperties": true, - "anyOf": [ - { - "properties": { - "actions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "checks": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "contents": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "deployments": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "discussions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "issues": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "packages": { - "type": "string", - "enum": [ - "read" - ] - } - } - }, - { - "properties": { - "pages": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "pull_requests": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "repository_projects": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "statuses": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "workflows": { - "type": "string", - "enum": [ - "write" - ] - } - } - } - ] - } - } - }, - { - "properties": { - "permissions": { - "type": "string", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "enum": [ - "read-all", - "write-all" - ] - } - } - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "additionalProperties": { - "type": "object", - "additionalProperties": true - } - }, - "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." - }, - "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 for the dev container displayed in the UI." - }, - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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-]+)*))?)|@prerelease)?$", - "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." - }, - "features": { - "type": "object", - "description": "Features to add to the dev container.", - "additionalProperties": true - }, - "forwardPorts": { - "type": "array", - "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", - "items": { - "oneOf": [ - { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - { - "type": "string", - "pattern": "^([a-z0-9-]+):(\\d{1,5})$" - } - ] - } - }, - "portsAttributes": { - "type": "object", - "patternProperties": { - "(^\\d+(-\\d+)?$)|(.+)": { - "type": "object", - "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openBrowserOnce", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "default": { - "label": "Application", - "onAutoForward": "notify" - }, - "additionalProperties": false - } - }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", - "defaultSnippets": [ - { - "body": { - "${1:3000}": { - "label": "${2:Application}", - "onAutoForward": "notify" - } - } - } - ], - "additionalProperties": false - }, - "otherPortsAttributes": { - "type": "object", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "defaultSnippets": [ - { - "body": { - "onAutoForward": "ignore" - } - } - ], - "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", - "additionalProperties": false - }, - "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 when opening from a local folder." - }, - "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. This command is run before \"onCreateCommand\". 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" - } - }, - "onCreateCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". 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" - } - }, - "updateContentCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". 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. This command is run after \"updateContentCommand\" and before \"postStartCommand\". 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. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". 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 when attaching to the container. This command is run after \"postStartCommand\". 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" - } - }, - "waitFor": { - "type": "string", - "enum": [ - "initializeCommand", - "onCreateCommand", - "updateContentCommand", - "postCreateCommand", - "postStartCommand" - ], - "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." - }, - "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 \"loginInteractiveShell\"." - }, - "codespaces": { - "type": "object", - "additionalProperties": true, - "description": "Codespaces-specific configuration." - }, - "hostRequirements": { - "type": "object", - "description": "Host hardware requirements.", - "properties": { - "cpus": { - "type": "integer", - "minimum": 1, - "description": "Number of required CPUs." - }, - "memory": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." - }, - "storage": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." - } - }, - "additionalProperties": false - }, - "customizations": { - "type": "object", - "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.", - "properties": { - "vscode": { - "type": "object", - "properties": { - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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." - }, - "devPort": { - "type": "integer", - "description": "The port VS Code can use to connect to its backend." - } - }, - "additionalProperties": false - }, - "codespaces": { - "type": "object", - "description": "Customizations specific to GitHub Codespaces", - "properties": { - "repositories": { - "type": "object", - "description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')", - "patternProperties": { - "^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": { - "type": "object", - "additionalProperties": true, - "oneOf": [ - { - "properties": { - "permissions": { - "type": "object", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "additionalProperties": true, - "anyOf": [ - { - "properties": { - "actions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "checks": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "contents": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "deployments": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "discussions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "issues": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "packages": { - "type": "string", - "enum": [ - "read" - ] - } - } - }, - { - "properties": { - "pages": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "pull_requests": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "repository_projects": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "statuses": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "workflows": { - "type": "string", - "enum": [ - "write" - ] - } - } - } - ] - } - } - }, - { - "properties": { - "permissions": { - "type": "string", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "enum": [ - "read-all", - "write-all" - ] - } - } - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "additionalProperties": { - "type": "object", - "additionalProperties": true - } - }, - "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." - }, - "overrideCommand": { - "type": "boolean", - "description": "Whether to overwrite the command specified in the image. The default is false." - }, - "name": { - "type": "string", - "description": "A name for the dev container displayed in the UI." - }, - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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-]+)*))?)|@prerelease)?$", - "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." - }, - "features": { - "type": "object", - "description": "Features to add to the dev container.", - "additionalProperties": true - }, - "forwardPorts": { - "type": "array", - "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", - "items": { - "oneOf": [ - { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - { - "type": "string", - "pattern": "^([a-z0-9-]+):(\\d{1,5})$" - } - ] - } - }, - "portsAttributes": { - "type": "object", - "patternProperties": { - "(^\\d+(-\\d+)?$)|(.+)": { - "type": "object", - "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openBrowserOnce", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "default": { - "label": "Application", - "onAutoForward": "notify" - }, - "additionalProperties": false - } - }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", - "defaultSnippets": [ - { - "body": { - "${1:3000}": { - "label": "${2:Application}", - "onAutoForward": "notify" - } - } - } - ], - "additionalProperties": false - }, - "otherPortsAttributes": { - "type": "object", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "defaultSnippets": [ - { - "body": { - "onAutoForward": "ignore" - } - } - ], - "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", - "additionalProperties": false - }, - "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 when opening from a local folder." - }, - "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. This command is run before \"onCreateCommand\". 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" - } - }, - "onCreateCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". 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" - } - }, - "updateContentCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". 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. This command is run after \"updateContentCommand\" and before \"postStartCommand\". 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. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". 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 when attaching to the container. This command is run after \"postStartCommand\". 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" - } - }, - "waitFor": { - "type": "string", - "enum": [ - "initializeCommand", - "onCreateCommand", - "updateContentCommand", - "postCreateCommand", - "postStartCommand" - ], - "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." - }, - "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 \"loginInteractiveShell\"." - }, - "codespaces": { - "type": "object", - "additionalProperties": true, - "description": "Codespaces-specific configuration." - }, - "hostRequirements": { - "type": "object", - "description": "Host hardware requirements.", - "properties": { - "cpus": { - "type": "integer", - "minimum": 1, - "description": "Number of required CPUs." - }, - "memory": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." - }, - "storage": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." - } - }, - "additionalProperties": false - }, - "customizations": { - "type": "object", - "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.", - "properties": { - "vscode": { - "type": "object", - "properties": { - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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." - }, - "devPort": { - "type": "integer", - "description": "The port VS Code can use to connect to its backend." - } - }, - "additionalProperties": false - }, - "codespaces": { - "type": "object", - "description": "Customizations specific to GitHub Codespaces", - "properties": { - "repositories": { - "type": "object", - "description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')", - "patternProperties": { - "^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": { - "type": "object", - "additionalProperties": true, - "oneOf": [ - { - "properties": { - "permissions": { - "type": "object", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "additionalProperties": true, - "anyOf": [ - { - "properties": { - "actions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "checks": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "contents": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "deployments": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "discussions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "issues": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "packages": { - "type": "string", - "enum": [ - "read" - ] - } - } - }, - { - "properties": { - "pages": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "pull_requests": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "repository_projects": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "statuses": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "workflows": { - "type": "string", - "enum": [ - "write" - ] - } - } - } - ] - } - } - }, - { - "properties": { - "permissions": { - "type": "string", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "enum": [ - "read-all", - "write-all" - ] - } - } - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "additionalProperties": { - "type": "object", - "additionalProperties": true - } - }, - "required": [ - "dockerComposeFile", - "service", - "workspaceFolder" - ], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "A name for the dev container displayed in the UI." - }, - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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-]+)*))?)|@prerelease)?$", - "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." - }, - "features": { - "type": "object", - "description": "Features to add to the dev container.", - "additionalProperties": true - }, - "forwardPorts": { - "type": "array", - "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", - "items": { - "oneOf": [ - { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - { - "type": "string", - "pattern": "^([a-z0-9-]+):(\\d{1,5})$" - } - ] - } - }, - "portsAttributes": { - "type": "object", - "patternProperties": { - "(^\\d+(-\\d+)?$)|(.+)": { - "type": "object", - "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openBrowserOnce", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "default": { - "label": "Application", - "onAutoForward": "notify" - }, - "additionalProperties": false - } - }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", - "defaultSnippets": [ - { - "body": { - "${1:3000}": { - "label": "${2:Application}", - "onAutoForward": "notify" - } - } - } - ], - "additionalProperties": false - }, - "otherPortsAttributes": { - "type": "object", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "defaultSnippets": [ - { - "body": { - "onAutoForward": "ignore" - } - } - ], - "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", - "additionalProperties": false - }, - "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 when opening from a local folder." - }, - "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. This command is run before \"onCreateCommand\". 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" - } - }, - "onCreateCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". 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" - } - }, - "updateContentCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". 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. This command is run after \"updateContentCommand\" and before \"postStartCommand\". 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. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". 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 when attaching to the container. This command is run after \"postStartCommand\". 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" - } - }, - "waitFor": { - "type": "string", - "enum": [ - "initializeCommand", - "onCreateCommand", - "updateContentCommand", - "postCreateCommand", - "postStartCommand" - ], - "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." - }, - "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 \"loginInteractiveShell\"." - }, - "codespaces": { - "type": "object", - "additionalProperties": true, - "description": "Codespaces-specific configuration." - }, - "hostRequirements": { - "type": "object", - "description": "Host hardware requirements.", - "properties": { - "cpus": { - "type": "integer", - "minimum": 1, - "description": "Number of required CPUs." - }, - "memory": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." - }, - "storage": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." - } - }, - "additionalProperties": false - }, - "customizations": { - "type": "object", - "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.", - "properties": { - "vscode": { - "type": "object", - "properties": { - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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." - }, - "devPort": { - "type": "integer", - "description": "The port VS Code can use to connect to its backend." - } - }, - "additionalProperties": false - }, - "codespaces": { - "type": "object", - "description": "Customizations specific to GitHub Codespaces", - "properties": { - "repositories": { - "type": "object", - "description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')", - "patternProperties": { - "^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": { - "type": "object", - "additionalProperties": true, - "oneOf": [ - { - "properties": { - "permissions": { - "type": "object", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "additionalProperties": true, - "anyOf": [ - { - "properties": { - "actions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "checks": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "contents": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "deployments": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "discussions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "issues": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "packages": { - "type": "string", - "enum": [ - "read" - ] - } - } - }, - { - "properties": { - "pages": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "pull_requests": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "repository_projects": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "statuses": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "workflows": { - "type": "string", - "enum": [ - "write" - ] - } - } - } - ] - } - } - }, - { - "properties": { - "permissions": { - "type": "string", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "enum": [ - "read-all", - "write-all" - ] - } - } - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "additionalProperties": { - "type": "object", - "additionalProperties": true - } - }, - "additionalProperties": false - } - ] -} \ No newline at end of file diff --git a/extensions/configuration-editing/schemas/devContainer.schema.src.json b/extensions/configuration-editing/schemas/devContainer.schema.src.json deleted file mode 100644 index 73456101bb..0000000000 --- a/extensions/configuration-editing/schemas/devContainer.schema.src.json +++ /dev/null @@ -1,771 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "Defines a dev container", - "allowComments": true, - "allowTrailingCommas": false, - "definitions": { - "devContainerCommon": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "A name for the dev container displayed in the UI." - }, - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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-]+)*))?)|@prerelease)?$", - "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." - }, - "features": { - "type": "object", - "description": "Features to add to the dev container.", - "additionalProperties": true - }, - "forwardPorts": { - "type": "array", - "description": "Ports that are forwarded from the container to the local machine. Can be an integer port number, or a string of the format \"host:port_number\".", - "items": { - "oneOf": [ - { - "type": "integer", - "maximum": 65535, - "minimum": 0 - }, - { - "type": "string", - "pattern": "^([a-z0-9-]+):(\\d{1,5})$" - } - ] - } - }, - "portsAttributes": { - "type": "object", - "patternProperties": { - "(^\\d+(-\\d+)?$)|(.+)": { - "type": "object", - "description": "A port, range of ports (ex. \"40000-55000\"), or regular expression (ex. \".+\\\\/server.js\"). For a port number or range, the attributes will apply to that port number or range of port numbers. Attributes which use a regular expression will apply to ports whose associated process command line matches the expression.", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openBrowserOnce", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens the browser when the port is automatically forwarded, but only the first time the port is forward during a session. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "default": { - "label": "Application", - "onAutoForward": "notify" - } - } - }, - "markdownDescription": "Set default properties that are applied when a specific port number is forwarded. For example:\n\n```\n\"3000\": {\n \"label\": \"Application\"\n},\n\"40000-55000\": {\n \"onAutoForward\": \"ignore\"\n},\n\".+\\\\/server.js\": {\n \"onAutoForward\": \"openPreview\"\n}\n```", - "defaultSnippets": [ - { - "body": { - "${1:3000}": { - "label": "${2:Application}", - "onAutoForward": "notify" - } - } - } - ], - "additionalProperties": false - }, - "otherPortsAttributes": { - "type": "object", - "properties": { - "onAutoForward": { - "type": "string", - "enum": [ - "notify", - "openBrowser", - "openPreview", - "silent", - "ignore" - ], - "enumDescriptions": [ - "Shows a notification when a port is automatically forwarded.", - "Opens the browser when the port is automatically forwarded. Depending on your settings, this could open an embedded browser.", - "Opens a preview in the same window when the port is automatically forwarded.", - "Shows no notification and takes no action when this port is automatically forwarded.", - "This port will not be automatically forwarded." - ], - "description": "Defines the action that occurs when the port is discovered for automatic forwarding", - "default": "notify" - }, - "elevateIfNeeded": { - "type": "boolean", - "description": "Automatically prompt for elevation (if needed) when this port is forwarded. Elevate is required if the local port is a privileged port.", - "default": false - }, - "label": { - "type": "string", - "description": "Label that will be shown in the UI for this port.", - "default": "Application" - }, - "requireLocalPort": { - "type": "boolean", - "markdownDescription": "When true, a modal dialog will show if the chosen local port isn't used for forwarding.", - "default": false - }, - "protocol": { - "type": "string", - "enum": [ - "http", - "https" - ], - "description": "The protocol to use when forwarding this port." - } - }, - "defaultSnippets": [ - { - "body": { - "onAutoForward": "ignore" - } - } - ], - "markdownDescription": "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", - "additionalProperties": false - }, - "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 when opening from a local folder." - }, - "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. This command is run before \"onCreateCommand\". 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" - } - }, - "onCreateCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container. This command is run after \"initializeCommand\" and before \"updateContentCommand\". 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" - } - }, - "updateContentCommand": { - "type": [ - "string", - "array" - ], - "description": "A command to run when creating the container and rerun when the workspace content was updated while creating the container. This command is run after \"onCreateCommand\" and before \"postCreateCommand\". 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. This command is run after \"updateContentCommand\" and before \"postStartCommand\". 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. This command is run after \"postCreateCommand\" and before \"postAttachCommand\". 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 when attaching to the container. This command is run after \"postStartCommand\". 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" - } - }, - "waitFor": { - "type": "string", - "enum": [ - "initializeCommand", - "onCreateCommand", - "updateContentCommand", - "postCreateCommand", - "postStartCommand" - ], - "description": "The user command to wait for before continuing execution in the background while the UI is starting up. The default is \"updateContentCommand\"." - }, - "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 \"loginInteractiveShell\"." - }, - "codespaces": { - "type": "object", - "additionalProperties": true, - "description": "Codespaces-specific configuration." - }, - "hostRequirements": { - "type": "object", - "description": "Host hardware requirements.", - "allOf": [ - { - "type": "object", - "properties": { - "cpus": { - "type": "integer", - "minimum": 1, - "description": "Number of required CPUs." - }, - "memory": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." - }, - "storage": { - "type": "string", - "pattern": "^\\d+([tgmk]b)?$", - "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." - } - } - } - ] - }, - "customizations": { - "type": "object", - "description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.", - "properties": { - "vscode": { - "type": "object", - "properties": { - "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-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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." - }, - "devPort": { - "type": "integer", - "description": "The port VS Code can use to connect to its backend." - } - } - }, - "codespaces": { - "type": "object", - "description": "Customizations specific to GitHub Codespaces", - "properties": { - "repositories": { - "type": "object", - "description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')", - "patternProperties": { - "^[a-zA-Z0-9-_.]+[.]*\/[a-zA-Z0-9-_*]+[.]*$": { - "type": "object", - "additionalProperties": true, - "oneOf": [ - { - "properties": { - "permissions": { - "type": "object", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "additionalProperties": true, - "anyOf": [ - { - "properties": { - "actions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "checks": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "contents": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "deployments": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "discussions": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "issues": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "packages": { - "type": "string", - "enum": [ - "read" - ] - } - } - }, - { - "properties": { - "pages": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "pull_requests": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "repository_projects": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "statuses": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "properties": { - "workflows": { - "type": "string", - "enum": [ - "write" - ] - } - } - } - ] - } - } - }, - { - "properties": { - "permissions": { - "type": "string", - "description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.", - "enum": [ - "read-all", - "write-all" - ] - } - } - } - ] - } - } - } - } - } - } - }, - "additionalProperties": { - "type": "object", - "additionalProperties": true - } - } - }, - "nonComposeBase": { - "type": "object", - "properties": { - "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." - }, - "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." - } - } - }, - "dockerfileContainer": { - "oneOf": [ - { - "type": "object", - "properties": { - "build": { - "type": "object", - "description": "Docker build-related options.", - "allOf": [ - { - "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." - } - }, - "required": [ - "dockerfile" - ] - }, - { - "$ref": "#/definitions/buildOptions" - } - ] - } - }, - "required": [ - "build" - ] - }, - { - "allOf": [ - { - "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." - } - }, - "required": [ - "dockerFile" - ] - }, - { - "type": "object", - "properties": { - "build": { - "description": "Docker build-related options.", - "$ref": "#/definitions/buildOptions" - } - } - } - ] - } - ] - }, - "buildOptions": { - "type": "object", - "properties": { - "target": { - "type": "string", - "description": "Target stage in a multi-stage build." - }, - "args": { - "type": "object", - "additionalProperties": { - "type": [ - "string" - ] - }, - "description": "Build arguments." - }, - "cacheFrom": { - "type": [ - "string", - "array" - ], - "description": "The image to consider as a cache. Use an array to specify multiple images.", - "items": { - "type": "string" - } - } - } - }, - "imageContainer": { - "type": "object", - "properties": { - "image": { - "type": "string", - "description": "The docker image that will be used to create the container." - } - }, - "required": [ - "image" - ] - }, - "composeContainer": { - "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." - }, - "overrideCommand": { - "type": "boolean", - "description": "Whether to overwrite the command specified in the image. The default is false." - } - }, - "required": [ - "dockerComposeFile", - "service", - "workspaceFolder" - ] - } - }, - "oneOf": [ - { - "allOf": [ - { - "oneOf": [ - { - "allOf": [ - { - "oneOf": [ - { - "$ref": "#/definitions/dockerfileContainer" - }, - { - "$ref": "#/definitions/imageContainer" - } - ] - }, - { - "$ref": "#/definitions/nonComposeBase" - } - ] - }, - { - "$ref": "#/definitions/composeContainer" - } - ] - }, - { - "$ref": "#/definitions/devContainerCommon" - } - ] - }, - { - "type": "object", - "$ref": "#/definitions/devContainerCommon", - "additionalProperties": false - } - ] -} diff --git a/extensions/configuration-editing/schemas/devContainer.vscode.schema.json b/extensions/configuration-editing/schemas/devContainer.vscode.schema.json index e0f2a8aa68..13b4e53f89 100644 --- a/extensions/configuration-editing/schemas/devContainer.vscode.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.vscode.schema.json @@ -13,7 +13,7 @@ "description": "An array of extensions that should be installed into the container.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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-]+)*))?)?$", + "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-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-]+)*))?)|@prerelease)?$", "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, diff --git a/extensions/datavirtualization/src/typings/markdown-it-named-headers.d.ts b/extensions/configuration-editing/src/browser/net.ts similarity index 88% rename from extensions/datavirtualization/src/typings/markdown-it-named-headers.d.ts rename to extensions/configuration-editing/src/browser/net.ts index c80a3af89d..824bdf7855 100644 --- a/extensions/datavirtualization/src/typings/markdown-it-named-headers.d.ts +++ b/extensions/configuration-editing/src/browser/net.ts @@ -2,4 +2,5 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -declare module 'markdown-it-named-headers' { } + +export const agent = undefined; diff --git a/extensions/configuration-editing/src/configurationEditingMain.ts b/extensions/configuration-editing/src/configurationEditingMain.ts index 2a08fc7791..ed1fd114c9 100644 --- a/extensions/configuration-editing/src/configurationEditingMain.ts +++ b/extensions/configuration-editing/src/configurationEditingMain.ts @@ -5,10 +5,9 @@ import { getLocation, JSONPath, parse, visit, Location } from 'jsonc-parser'; import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; import { SettingsDocument } from './settingsDocumentHelper'; import { provideInstalledExtensionProposals } from './extensionsProposals'; -const localize = nls.loadMessageBundle(); +import './importExportProfiles'; export function activate(context: vscode.ExtensionContext): void { //settings.json suggestions @@ -23,6 +22,9 @@ export function activate(context: vscode.ExtensionContext): void { // task.json variable suggestions context.subscriptions.push(registerVariableCompletions('**/tasks.json')); + // Workspace file launch/tasks variable completions + context.subscriptions.push(registerVariableCompletions('**/*.code-workspace')); + // keybindings.json/package.json context key suggestions context.subscriptions.push(registerContextKeyCompletions()); } @@ -40,27 +42,31 @@ function registerVariableCompletions(pattern: string): vscode.Disposable { provideCompletionItems(document, position, _token) { const location = getLocation(document.getText(), document.offsetAt(position)); if (isCompletingInsidePropertyStringValue(document, location, position)) { + if (document.fileName.endsWith('.code-workspace') && !isLocationInsideTopLevelProperty(location, ['launch', 'tasks'])) { + return []; + } + let range = document.getWordRangeAtPosition(position, /\$\{[^"\}]*\}?/); if (!range || range.start.isEqual(position) || range.end.isEqual(position) && document.getText(range).endsWith('}')) { range = new vscode.Range(position, position); } return [ - { label: 'workspaceFolder', detail: localize('workspaceFolder', "The path of the folder opened in VS Code") }, - { label: 'workspaceFolderBasename', detail: localize('workspaceFolderBasename', "The name of the folder opened in VS Code without any slashes (/)") }, - { label: 'relativeFile', detail: localize('relativeFile', "The current opened file relative to ${workspaceFolder}") }, - { label: 'relativeFileDirname', detail: localize('relativeFileDirname', "The current opened file's dirname relative to ${workspaceFolder}") }, - { label: 'file', detail: localize('file', "The current opened file") }, - { label: 'cwd', detail: localize('cwd', "The task runner's current working directory on startup") }, - { label: 'lineNumber', detail: localize('lineNumber', "The current selected line number in the active file") }, - { label: 'selectedText', detail: localize('selectedText', "The current selected text in the active file") }, - { label: 'fileDirname', detail: localize('fileDirname', "The current opened file's dirname") }, - { label: 'fileExtname', detail: localize('fileExtname', "The current opened file's extension") }, - { label: 'fileBasename', detail: localize('fileBasename', "The current opened file's basename") }, - { label: 'fileBasenameNoExtension', detail: localize('fileBasenameNoExtension', "The current opened file's basename with no file extension") }, - { label: 'defaultBuildTask', detail: localize('defaultBuildTask', "The name of the default build task. If there is not a single default build task then a quick pick is shown to choose the build task.") }, - { label: 'pathSeparator', detail: localize('pathSeparator', "The character used by the operating system to separate components in file paths") }, - { label: 'extensionInstallFolder', detail: localize('extensionInstallFolder', "The path where an an extension is installed."), param: 'publisher.extension' }, + { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in VS Code") }, + { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in VS Code without any slashes (/)") }, + { label: 'relativeFile', detail: vscode.l10n.t("The current opened file relative to ${workspaceFolder}") }, + { label: 'relativeFileDirname', detail: vscode.l10n.t("The current opened file's dirname relative to ${workspaceFolder}") }, + { label: 'file', detail: vscode.l10n.t("The current opened file") }, + { label: 'cwd', detail: vscode.l10n.t("The task runner's current working directory on startup") }, + { label: 'lineNumber', detail: vscode.l10n.t("The current selected line number in the active file") }, + { label: 'selectedText', detail: vscode.l10n.t("The current selected text in the active file") }, + { label: 'fileDirname', detail: vscode.l10n.t("The current opened file's dirname") }, + { label: 'fileExtname', detail: vscode.l10n.t("The current opened file's extension") }, + { label: 'fileBasename', detail: vscode.l10n.t("The current opened file's basename") }, + { label: 'fileBasenameNoExtension', detail: vscode.l10n.t("The current opened file's basename with no file extension") }, + { label: 'defaultBuildTask', detail: vscode.l10n.t("The name of the default build task. If there is not a single default build task then a quick pick is shown to choose the build task.") }, + { label: 'pathSeparator', detail: vscode.l10n.t("The character used by the operating system to separate components in file paths") }, + { label: 'extensionInstallFolder', detail: vscode.l10n.t("The path where an an extension is installed."), param: 'publisher.extension' }, ].map(variable => ({ label: `\${${variable.label}}`, range, @@ -86,6 +92,10 @@ function isCompletingInsidePropertyStringValue(document: vscode.TextDocument, lo return false; } +function isLocationInsideTopLevelProperty(location: Location, values: string[]) { + return values.includes(location.path[0] as string); +} + interface IExtensionsContent { recommendations: string[]; } diff --git a/extensions/configuration-editing/src/extensionsProposals.ts b/extensions/configuration-editing/src/extensionsProposals.ts index 419a23e879..3c8660f6bb 100644 --- a/extensions/configuration-editing/src/extensionsProposals.ts +++ b/extensions/configuration-editing/src/extensionsProposals.ts @@ -4,8 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; -const localize = nls.loadMessageBundle(); export async function provideInstalledExtensionProposals(existing: string[], additionalText: string, range: vscode.Range, includeBuiltinExtensions: boolean): Promise { @@ -23,7 +21,7 @@ export async function provideInstalledExtensionProposals(existing: string[], add return item; }); } else { - const example = new vscode.CompletionItem(localize('exampleExtension', "Example")); + const example = new vscode.CompletionItem(vscode.l10n.t("Example")); example.insertText = '"vscode.csharp"'; example.kind = vscode.CompletionItemKind.Value; example.range = range; @@ -48,7 +46,7 @@ export async function provideWorkspaceTrustExtensionProposals(existing: string[] return item; }); } else { - const example = new vscode.CompletionItem(localize('exampleExtension', "Example")); + const example = new vscode.CompletionItem(vscode.l10n.t("Example")); example.insertText = '"vscode.csharp: {\n\t"supported": false,\n\t"version": "0.0.0"\n}`;"'; example.kind = vscode.CompletionItemKind.Value; example.range = range; diff --git a/extensions/configuration-editing/src/importExportProfiles.ts b/extensions/configuration-editing/src/importExportProfiles.ts new file mode 100644 index 0000000000..4788c933b6 --- /dev/null +++ b/extensions/configuration-editing/src/importExportProfiles.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Octokit } from '@octokit/rest'; +import * as vscode from 'vscode'; +import { basename } from 'path'; +import { agent } from './node/net'; + +class GitHubGistProfileContentHandler implements vscode.ProfileContentHandler { + + readonly name = vscode.l10n.t('GitHub'); + readonly description = vscode.l10n.t('gist'); + + private _octokit: Promise | undefined; + private getOctokit(): Promise { + if (!this._octokit) { + this._octokit = (async () => { + const session = await vscode.authentication.getSession('github', ['gist', 'user:email'], { createIfNone: true }); + const token = session.accessToken; + + const { Octokit } = await import('@octokit/rest'); + + return new Octokit({ + request: { agent }, + userAgent: 'GitHub VSCode', + auth: `token ${token}` + }); + })(); + } + return this._octokit; + } + + async saveProfile(name: string, content: string): Promise<{ readonly id: string; readonly link: vscode.Uri } | null> { + const octokit = await this.getOctokit(); + const result = await octokit.gists.create({ + public: false, + files: { + [name]: { + content + } + } + }); + if (result.data.id && result.data.html_url) { + const link = vscode.Uri.parse(result.data.html_url); + return { id: result.data.id, link }; + } + return null; + } + + private _public_octokit: Promise | undefined; + private getPublicOctokit(): Promise { + if (!this._public_octokit) { + this._public_octokit = (async () => { + const { Octokit } = await import('@octokit/rest'); + return new Octokit({ request: { agent }, userAgent: 'GitHub VSCode' }); + })(); + } + return this._public_octokit; + } + + async readProfile(id: string): Promise; + async readProfile(uri: vscode.Uri): Promise; + async readProfile(arg: string | vscode.Uri): Promise { + const gist_id = typeof arg === 'string' ? arg : basename(arg.path); + const octokit = await this.getPublicOctokit(); + try { + const gist = await octokit.gists.get({ gist_id }); + if (gist.data.files) { + return gist.data.files[Object.keys(gist.data.files)[0]]?.content ?? null; + } + } catch (error) { + // ignore + } + return null; + } + +} + +vscode.window.registerProfileContentHandler('github', new GitHubGistProfileContentHandler()); diff --git a/extensions/configuration-editing/src/node/net.ts b/extensions/configuration-editing/src/node/net.ts new file mode 100644 index 0000000000..f510d8f68b --- /dev/null +++ b/extensions/configuration-editing/src/node/net.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Agent, globalAgent } from 'https'; +import { URL } from 'url'; +import { httpsOverHttp } from 'tunnel'; +import { window } from 'vscode'; + +export const agent = getAgent(); + +/** + * Return an https agent for the given proxy URL, or return the + * global https agent if the URL was empty or invalid. + */ +function getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent { + if (!url) { + return globalAgent; + } + try { + const { hostname, port, username, password } = new URL(url); + const auth = username && password && `${username}:${password}`; + return httpsOverHttp({ proxy: { host: hostname, port, proxyAuth: auth } }); + } catch (e) { + window.showErrorMessage(`HTTPS_PROXY environment variable ignored: ${e.message}`); + return globalAgent; + } +} diff --git a/extensions/configuration-editing/src/settingsDocumentHelper.ts b/extensions/configuration-editing/src/settingsDocumentHelper.ts index 2a001cd382..d18fa7db0e 100644 --- a/extensions/configuration-editing/src/settingsDocumentHelper.ts +++ b/extensions/configuration-editing/src/settingsDocumentHelper.ts @@ -5,10 +5,8 @@ import * as vscode from 'vscode'; import { getLocation, Location, parse } from 'jsonc-parser'; -import * as nls from 'vscode-nls'; import { provideInstalledExtensionProposals } from './extensionsProposals'; -const localize = nls.loadMessageBundle(); const OVERRIDE_IDENTIFIER_REGEX = /\[([^\[\]]*)\]/g; export class SettingsDocument { @@ -28,8 +26,8 @@ export class SettingsDocument { return this.provideFilesAssociationsCompletionItems(location, position); } - // files.exclude, search.exclude - if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude') { + // files.exclude, search.exclude, explorer.autoRevealExclude + if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude' || location.path[0] === 'explorer.autoRevealExclude') { return this.provideExcludeCompletionItems(location, position); } @@ -107,20 +105,21 @@ export class SettingsDocument { }; - completions.push(this.newSimpleCompletionItem(getText('activeEditorShort'), range, localize('activeEditorShort', "the file name (e.g. myFile.txt)"))); - completions.push(this.newSimpleCompletionItem(getText('activeEditorMedium'), range, localize('activeEditorMedium', "the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)"))); - completions.push(this.newSimpleCompletionItem(getText('activeEditorLong'), range, localize('activeEditorLong', "the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)"))); - completions.push(this.newSimpleCompletionItem(getText('activeFolderShort'), range, localize('activeFolderShort', "the name of the folder the file is contained in (e.g. myFileFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('activeFolderMedium'), range, localize('activeFolderMedium', "the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('activeFolderLong'), range, localize('activeFolderLong', "the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('rootName'), range, localize('rootName', "name of the workspace (e.g. myFolder or myWorkspace)"))); - completions.push(this.newSimpleCompletionItem(getText('rootPath'), range, localize('rootPath', "file path of the workspace (e.g. /Users/Development/myWorkspace)"))); - completions.push(this.newSimpleCompletionItem(getText('folderName'), range, localize('folderName', "name of the workspace folder the file is contained in (e.g. myFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('folderPath'), range, localize('folderPath', "file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)"))); - completions.push(this.newSimpleCompletionItem(getText('appName'), range, localize('appName', "e.g. VS Code"))); - completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, localize('remoteName', "e.g. SSH"))); - completions.push(this.newSimpleCompletionItem(getText('dirty'), range, localize('dirty', "an indicator for when the active editor has unsaved changes"))); - completions.push(this.newSimpleCompletionItem(getText('separator'), range, localize('separator', "a conditional separator (' - ') that only shows when surrounded by variables with values"))); + completions.push(this.newSimpleCompletionItem(getText('activeEditorShort'), range, vscode.l10n.t("the file name (e.g. myFile.txt)"))); + completions.push(this.newSimpleCompletionItem(getText('activeEditorMedium'), range, vscode.l10n.t("the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)"))); + completions.push(this.newSimpleCompletionItem(getText('activeEditorLong'), range, vscode.l10n.t("the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)"))); + completions.push(this.newSimpleCompletionItem(getText('activeFolderShort'), range, vscode.l10n.t("the name of the folder the file is contained in (e.g. myFileFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('activeFolderMedium'), range, vscode.l10n.t("the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('activeFolderLong'), range, vscode.l10n.t("the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('rootName'), range, vscode.l10n.t("name of the workspace with optional remote name and workspace indicator if applicable (e.g. myFolder, myRemoteFolder [SSH] or myWorkspace (Workspace))"))); + completions.push(this.newSimpleCompletionItem(getText('rootNameShort'), range, vscode.l10n.t("shortened name of the workspace without suffixes (e.g. myFolder or myWorkspace)"))); + completions.push(this.newSimpleCompletionItem(getText('rootPath'), range, vscode.l10n.t("file path of the workspace (e.g. /Users/Development/myWorkspace)"))); + completions.push(this.newSimpleCompletionItem(getText('folderName'), range, vscode.l10n.t("name of the workspace folder the file is contained in (e.g. myFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('folderPath'), range, vscode.l10n.t("file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)"))); + completions.push(this.newSimpleCompletionItem(getText('appName'), range, vscode.l10n.t("e.g. VS Code"))); + completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, vscode.l10n.t("e.g. SSH"))); + completions.push(this.newSimpleCompletionItem(getText('dirty'), range, vscode.l10n.t("an indicator for when the active editor has unsaved changes"))); + completions.push(this.newSimpleCompletionItem(getText('separator'), range, vscode.l10n.t("a conditional separator (' - ') that only shows when surrounded by variables with values"))); return completions; } @@ -134,15 +133,15 @@ export class SettingsDocument { const range = this.getReplaceRange(location, position); completions.push(this.newSnippetCompletionItem({ - label: localize('assocLabelFile', "Files with Extension"), - documentation: localize('assocDescriptionFile', "Map all files matching the glob pattern in their filename to the language with the given identifier."), + label: vscode.l10n.t("Files with Extension"), + documentation: vscode.l10n.t("Map all files matching the glob pattern in their filename to the language with the given identifier."), snippet: location.isAtPropertyKey ? '"*.${1:extension}": "${2:language}"' : '{ "*.${1:extension}": "${2:language}" }', range })); completions.push(this.newSnippetCompletionItem({ - label: localize('assocLabelPath', "Files with Path"), - documentation: localize('assocDescriptionPath', "Map all files matching the absolute path glob pattern in their path to the language with the given identifier."), + label: vscode.l10n.t("Files with Path"), + documentation: vscode.l10n.t("Map all files matching the absolute path glob pattern in their path to the language with the given identifier."), snippet: location.isAtPropertyKey ? '"/${1:path to file}/*.${2:extension}": "${3:language}"' : '{ "/${1:path to file}/*.${2:extension}": "${3:language}" }', range })); @@ -163,43 +162,43 @@ export class SettingsDocument { const range = this.getReplaceRange(location, position); completions.push(this.newSnippetCompletionItem({ - label: localize('fileLabel', "Files by Extension"), - documentation: localize('fileDescription', "Match all files of a specific file extension."), + label: vscode.l10n.t("Files by Extension"), + documentation: vscode.l10n.t("Match all files of a specific file extension."), snippet: location.path.length === 2 ? '"**/*.${1:extension}": true' : '{ "**/*.${1:extension}": true }', range })); completions.push(this.newSnippetCompletionItem({ - label: localize('filesLabel', "Files with Multiple Extensions"), - documentation: localize('filesDescription', "Match all files with any of the file extensions."), + label: vscode.l10n.t("Files with Multiple Extensions"), + documentation: vscode.l10n.t("Match all files with any of the file extensions."), snippet: location.path.length === 2 ? '"**/*.{ext1,ext2,ext3}": true' : '{ "**/*.{ext1,ext2,ext3}": true }', range })); completions.push(this.newSnippetCompletionItem({ - label: localize('derivedLabel', "Files with Siblings by Name"), - documentation: localize('derivedDescription', "Match files that have siblings with the same name but a different extension."), + label: vscode.l10n.t("Files with Siblings by Name"), + documentation: vscode.l10n.t("Match files that have siblings with the same name but a different extension."), snippet: location.path.length === 2 ? '"**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" }' : '{ "**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" } }', range })); completions.push(this.newSnippetCompletionItem({ - label: localize('topFolderLabel', "Folder by Name (Top Level)"), - documentation: localize('topFolderDescription', "Match a top level folder with a specific name."), + label: vscode.l10n.t("Folder by Name (Top Level)"), + documentation: vscode.l10n.t("Match a top level folder with a specific name."), snippet: location.path.length === 2 ? '"${1:name}": true' : '{ "${1:name}": true }', range })); completions.push(this.newSnippetCompletionItem({ - label: localize('topFoldersLabel', "Folders with Multiple Names (Top Level)"), - documentation: localize('topFoldersDescription', "Match multiple top level folders."), + label: vscode.l10n.t("Folders with Multiple Names (Top Level)"), + documentation: vscode.l10n.t("Match multiple top level folders."), snippet: location.path.length === 2 ? '"{folder1,folder2,folder3}": true' : '{ "{folder1,folder2,folder3}": true }', range })); completions.push(this.newSnippetCompletionItem({ - label: localize('folderLabel', "Folder by Name (Any Location)"), - documentation: localize('folderDescription', "Match a folder with a specific name in any location."), + label: vscode.l10n.t("Folder by Name (Any Location)"), + documentation: vscode.l10n.t("Match a folder with a specific name in any location."), snippet: location.path.length === 2 ? '"**/${1:name}": true' : '{ "**/${1:name}": true }', range })); @@ -209,8 +208,8 @@ export class SettingsDocument { else if (location.path.length === 2 && this.isCompletingPropertyValue(location, position)) { const range = this.getReplaceRange(location, position); completions.push(this.newSnippetCompletionItem({ - label: localize('derivedLabel', "Files with Siblings by Name"), - documentation: localize('siblingsDescription', "Match files that have siblings with the same name but a different extension."), + label: vscode.l10n.t("Files with Siblings by Name"), + documentation: vscode.l10n.t("Match files that have siblings with the same name but a different extension."), snippet: '{ "when": "$(basename).${1:extension}" }', range })); @@ -224,7 +223,7 @@ export class SettingsDocument { const range = this.getReplaceRange(location, position); const languages = await vscode.languages.getLanguages(); return [ - this.newSimpleCompletionItem(JSON.stringify('${activeEditorLanguage}'), range, localize('activeEditor', "Use the language of the currently active text editor if any")), + this.newSimpleCompletionItem(JSON.stringify('${activeEditorLanguage}'), range, vscode.l10n.t("Use the language of the currently active text editor if any")), ...languages.map(l => this.newSimpleCompletionItem(JSON.stringify(l), range)) ]; } @@ -267,7 +266,7 @@ export class SettingsDocument { const languageOverrideRange = languageOverridesRanges.find(range => range.contains(position)); /** - * Skip if suggestsions are for first language override range + * Skip if suggestions are for first language override range * Since VSCode registers language overrides to the schema, JSON language server does suggestions for first language override. */ if (languageOverrideRange && !languageOverrideRange.isEqual(languageOverridesRanges[0])) { diff --git a/extensions/configuration-editing/src/test/index.ts b/extensions/configuration-editing/src/test/index.ts new file mode 100644 index 0000000000..a140c32d69 --- /dev/null +++ b/extensions/configuration-editing/src/test/index.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as testRunner from '../../../../test/integration/electron/testrunner'; + +const options: import('mocha').MochaOptions = { + ui: 'tdd', + color: true, + timeout: 60000 +}; + +// These integration tests is being run in multiple environments (electron, web, remote) +// so we need to set the suite name based on the environment as the suite name is used +// for the test results file name +let suite = ''; +if (process.env.VSCODE_BROWSER) { + suite = `${process.env.VSCODE_BROWSER} Browser Integration Configuration-Editing Tests`; +} else if (process.env.REMOTE_VSCODE) { + suite = 'Remote Integration Configuration-Editing Tests'; +} else { + suite = 'Integration Configuration-Editing Tests'; +} + +if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + options.reporter = 'mocha-multi-reporters'; + options.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; +} + +testRunner.configure(options); + +export = testRunner; diff --git a/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts b/extensions/configuration-editing/src/typings/ref.d.ts similarity index 83% rename from src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts rename to extensions/configuration-editing/src/typings/ref.d.ts index eca2d6718e..4ec08b0f51 100644 --- a/src/vscode-dts/vscode.proposed.contribMergeEditorToolbar.d.ts +++ b/extensions/configuration-editing/src/typings/ref.d.ts @@ -3,4 +3,4 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// empty placeholder declaration for the `mergeEditor/toolbar` menu +declare module 'tunnel'; diff --git a/extensions/configuration-editing/tsconfig.json b/extensions/configuration-editing/tsconfig.json index 7234fdfeb9..3013ee5422 100644 --- a/extensions/configuration-editing/tsconfig.json +++ b/extensions/configuration-editing/tsconfig.json @@ -8,6 +8,7 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts" + "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts", ] } diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index f7ac959fc0..39b630a1bc 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -2,17 +2,187 @@ # yarn lockfile v1 +"@octokit/auth-token@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.2.tgz#a0fc8de149fd15876e1ac78f6525c1c5ab48435f" + integrity sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q== + dependencies: + "@octokit/types" "^8.0.0" + +"@octokit/core@^4.0.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.1.0.tgz#b6b03a478f1716de92b3f4ec4fd64d05ba5a9251" + integrity sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ== + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" + before-after-hook "^2.2.0" + universal-user-agent "^6.0.0" + +"@octokit/endpoint@^7.0.0": + version "7.0.3" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.3.tgz#0b96035673a9e3bedf8bab8f7335de424a2147ed" + integrity sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw== + dependencies: + "@octokit/types" "^8.0.0" + is-plain-object "^5.0.0" + universal-user-agent "^6.0.0" + +"@octokit/graphql@^5.0.0": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.4.tgz#519dd5c05123868276f3ae4e50ad565ed7dff8c8" + integrity sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A== + dependencies: + "@octokit/request" "^6.0.0" + "@octokit/types" "^8.0.0" + universal-user-agent "^6.0.0" + +"@octokit/openapi-types@^13.11.0": + version "13.13.1" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-13.13.1.tgz#a783bacb1817c9f61a2a0c3f81ea22ad62340fdf" + integrity sha512-4EuKSk3N95UBWFau3Bz9b3pheQ8jQYbKmBL5+GSuY8YDPDwu03J4BjI+66yNi8aaX/3h1qDpb0mbBkLdr+cfGQ== + +"@octokit/openapi-types@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-14.0.0.tgz#949c5019028c93f189abbc2fb42f333290f7134a" + integrity sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw== + +"@octokit/plugin-paginate-rest@^4.0.0": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-4.3.1.tgz#553e653ee0318605acd23bf3a799c8bfafdedae3" + integrity sha512-h8KKxESmSFTcXX409CAxlaOYscEDvN2KGQRsLCGT1NSqRW+D6EXLVQ8vuHhFznS9MuH9QYw1GfsUN30bg8hjVA== + dependencies: + "@octokit/types" "^7.5.0" + +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@^6.0.0": + version "6.7.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.7.0.tgz#2f6f17f25b6babbc8b41d2bb0a95a8839672ce7c" + integrity sha512-orxQ0fAHA7IpYhG2flD2AygztPlGYNAdlzYz8yrD8NDgelPfOYoRPROfEyIe035PlxvbYrgkfUZIhSBKju/Cvw== + dependencies: + "@octokit/types" "^8.0.0" + deprecation "^2.3.1" + +"@octokit/request-error@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.2.tgz#f74c0f163d19463b87528efe877216c41d6deb0a" + integrity sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg== + dependencies: + "@octokit/types" "^8.0.0" + deprecation "^2.0.0" + once "^1.4.0" + +"@octokit/request@^6.0.0": + version "6.2.2" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.2.tgz#a2ba5ac22bddd5dcb3f539b618faa05115c5a255" + integrity sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw== + dependencies: + "@octokit/endpoint" "^7.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" + is-plain-object "^5.0.0" + node-fetch "^2.6.7" + universal-user-agent "^6.0.0" + +"@octokit/rest@19.0.4": + version "19.0.4" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.4.tgz#fd8bed1cefffa486e9ae46a9dc608ce81bcfcbdd" + integrity sha512-LwG668+6lE8zlSYOfwPj4FxWdv/qFXYBpv79TWIQEpBLKA9D/IMcWsF/U9RGpA3YqMVDiTxpgVpEW3zTFfPFTA== + dependencies: + "@octokit/core" "^4.0.0" + "@octokit/plugin-paginate-rest" "^4.0.0" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^6.0.0" + +"@octokit/types@^7.5.0": + version "7.5.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-7.5.1.tgz#4e8b182933c17e1f41cc25d44757dbdb7bd76c1b" + integrity sha512-Zk4OUMLCSpXNI8KZZn47lVLJSsgMyCimsWWQI5hyjZg7hdYm0kjotaIkbG0Pp8SfU2CofMBzonboTqvzn3FrJA== + dependencies: + "@octokit/openapi-types" "^13.11.0" + +"@octokit/types@^8.0.0": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-8.1.1.tgz#92e304e0f00d563667dfdbe0ae6b52e70d5149bb" + integrity sha512-7tjk+6DyhYAmei8FOEwPfGKc0VE1x56CKPJ+eE44zhDbOyMT+9yan8apfQFxo8oEFsy+0O7PiBtH8w0Yo0Y9Kw== + dependencies: + "@octokit/openapi-types" "^14.0.0" + "@types/node@16.x": version "16.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== +before-after-hook@^2.2.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.3.tgz#c51e809c81a4e354084422b9b26bad88249c517c" + integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== + +deprecation@^2.0.0, deprecation@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" + integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + 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== -vscode-nls@^5.0.0: +node-fetch@^2.6.7: + version "2.6.8" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e" + integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg== + dependencies: + whatwg-url "^5.0.0" + +once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + +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== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^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== + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== diff --git a/extensions/csharp/.vscodeignore b/extensions/csharp/.vscodeignore deleted file mode 100644 index 0a622e7e30..0000000000 --- a/extensions/csharp/.vscodeignore +++ /dev/null @@ -1,2 +0,0 @@ -test/** -cgmanifest.json diff --git a/extensions/csharp/cgmanifest.json b/extensions/csharp/cgmanifest.json deleted file mode 100644 index 8b65e5000f..0000000000 --- a/extensions/csharp/cgmanifest.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "registrations": [ - { - "component": { - "type": "git", - "git": { - "name": "dotnet/csharp-tmLanguage", - "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "16612717ccd557383c0c821d7b6ae6662492ffde" - } - }, - "license": "MIT", - "version": "0.1.0", - "description": "The file syntaxes/csharp.tmLanguage.json was derived from https://github.com/dotnet/csharp-tmLanguage" - } - ], - "version": 1 -} \ No newline at end of file diff --git a/extensions/csharp/language-configuration.json b/extensions/csharp/language-configuration.json deleted file mode 100644 index d8698b46c0..0000000000 --- a/extensions/csharp/language-configuration.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": ["/*", "*/"] - }, - "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] - ], - "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - { "open": "'", "close": "'", "notIn": ["string", "comment"] }, - { "open": "\"", "close": "\"", "notIn": ["string", "comment"] } - ], - "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["<", ">"], - ["'", "'"], - ["\"", "\""] - ], - "folding": { - "markers": { - "start": "^\\s*#region\\b", - "end": "^\\s*#endregion\\b" - } - } -} diff --git a/extensions/csharp/package.json b/extensions/csharp/package.json deleted file mode 100644 index df77c14cdf..0000000000 --- a/extensions/csharp/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "csharp", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { - "vscode": "0.10.x" - }, - "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin dotnet/csharp-tmLanguage grammars/csharp.tmLanguage ./syntaxes/csharp.tmLanguage.json" - }, - "contributes": { - "languages": [ - { - "id": "csharp", - "extensions": [ - ".cs", - ".csx", - ".cake" - ], - "aliases": [ - "C#", - "csharp" - ], - "configuration": "./language-configuration.json" - } - ], - "grammars": [ - { - "language": "csharp", - "scopeName": "source.cs", - "path": "./syntaxes/csharp.tmLanguage.json" - } - ], - "snippets": [ - { - "language": "csharp", - "path": "./snippets/csharp.code-snippets" - } - ] - }, - "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode.git" - } -} diff --git a/extensions/csharp/package.nls.json b/extensions/csharp/package.nls.json deleted file mode 100644 index dff81ccd7c..0000000000 --- a/extensions/csharp/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "C# Language Basics", - "description": "Provides snippets, syntax highlighting, bracket matching and folding in C# files." -} diff --git a/extensions/csharp/snippets/csharp.code-snippets b/extensions/csharp/snippets/csharp.code-snippets deleted file mode 100644 index 5ad4bfca6c..0000000000 --- a/extensions/csharp/snippets/csharp.code-snippets +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Region Start": { - "prefix": "#region", - "body": [ - "#region $0" - ], - "description": "Folding Region Start" - }, - "Region End": { - "prefix": "#endregion", - "body": [ - "#endregion" - ], - "description": "Folding Region End" - } -} diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json deleted file mode 100644 index f6317202d2..0000000000 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ /dev/null @@ -1,4742 +0,0 @@ -{ - "information_for_contributors": [ - "This file has been converted from https://github.com/dotnet/csharp-tmLanguage/blob/master/grammars/csharp.tmLanguage", - "If you want to provide a fix or improvement, please create a pull request against the original repository.", - "Once accepted there, we are happy to receive an update request." - ], - "version": "https://github.com/dotnet/csharp-tmLanguage/commit/16612717ccd557383c0c821d7b6ae6662492ffde", - "name": "C#", - "scopeName": "source.cs", - "patterns": [ - { - "include": "#preprocessor" - }, - { - "include": "#comment" - }, - { - "include": "#directives" - }, - { - "include": "#declarations" - }, - { - "include": "#script-top-level" - } - ], - "repository": { - "directives": { - "patterns": [ - { - "include": "#extern-alias-directive" - }, - { - "include": "#using-directive" - }, - { - "include": "#attribute-section" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "declarations": { - "patterns": [ - { - "include": "#namespace-declaration" - }, - { - "include": "#type-declarations" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "script-top-level": { - "patterns": [ - { - "include": "#method-declaration" - }, - { - "include": "#statement" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "type-declarations": { - "patterns": [ - { - "include": "#preprocessor" - }, - { - "include": "#comment" - }, - { - "include": "#storage-modifier" - }, - { - "include": "#class-declaration" - }, - { - "include": "#delegate-declaration" - }, - { - "include": "#enum-declaration" - }, - { - "include": "#interface-declaration" - }, - { - "include": "#record-declaration" - }, - { - "include": "#struct-declaration" - }, - { - "include": "#attribute-section" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "class-or-struct-members": { - "patterns": [ - { - "include": "#preprocessor" - }, - { - "include": "#comment" - }, - { - "include": "#storage-modifier" - }, - { - "include": "#type-declarations" - }, - { - "include": "#property-declaration" - }, - { - "include": "#field-declaration" - }, - { - "include": "#event-declaration" - }, - { - "include": "#indexer-declaration" - }, - { - "include": "#variable-initializer" - }, - { - "include": "#constructor-declaration" - }, - { - "include": "#destructor-declaration" - }, - { - "include": "#operator-declaration" - }, - { - "include": "#conversion-operator-declaration" - }, - { - "include": "#method-declaration" - }, - { - "include": "#attribute-section" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "interface-members": { - "patterns": [ - { - "include": "#preprocessor" - }, - { - "include": "#comment" - }, - { - "include": "#property-declaration" - }, - { - "include": "#event-declaration" - }, - { - "include": "#indexer-declaration" - }, - { - "include": "#method-declaration" - }, - { - "include": "#attribute-section" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "statement": { - "patterns": [ - { - "include": "#preprocessor" - }, - { - "include": "#comment" - }, - { - "include": "#while-statement" - }, - { - "include": "#do-statement" - }, - { - "include": "#for-statement" - }, - { - "include": "#foreach-statement" - }, - { - "include": "#if-statement" - }, - { - "include": "#else-part" - }, - { - "include": "#switch-statement" - }, - { - "include": "#goto-statement" - }, - { - "include": "#return-statement" - }, - { - "include": "#break-or-continue-statement" - }, - { - "include": "#throw-statement" - }, - { - "include": "#yield-statement" - }, - { - "include": "#await-statement" - }, - { - "include": "#try-statement" - }, - { - "include": "#checked-unchecked-statement" - }, - { - "include": "#lock-statement" - }, - { - "include": "#using-statement" - }, - { - "include": "#labeled-statement" - }, - { - "include": "#object-creation-expression" - }, - { - "include": "#array-creation-expression" - }, - { - "include": "#anonymous-object-creation-expression" - }, - { - "include": "#local-declaration" - }, - { - "include": "#block" - }, - { - "include": "#expression" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "expression": { - "patterns": [ - { - "include": "#preprocessor" - }, - { - "include": "#comment" - }, - { - "include": "#checked-unchecked-expression" - }, - { - "include": "#typeof-or-default-expression" - }, - { - "include": "#nameof-expression" - }, - { - "include": "#throw-expression" - }, - { - "include": "#interpolated-string" - }, - { - "include": "#verbatim-interpolated-string" - }, - { - "include": "#this-or-base-expression" - }, - { - "include": "#switch-expression" - }, - { - "include": "#conditional-operator" - }, - { - "include": "#expression-operators" - }, - { - "include": "#await-expression" - }, - { - "include": "#query-expression" - }, - { - "include": "#as-expression" - }, - { - "include": "#is-expression" - }, - { - "include": "#anonymous-method-expression" - }, - { - "include": "#object-creation-expression" - }, - { - "include": "#array-creation-expression" - }, - { - "include": "#anonymous-object-creation-expression" - }, - { - "include": "#invocation-expression" - }, - { - "include": "#member-access-expression" - }, - { - "include": "#element-access-expression" - }, - { - "include": "#cast-expression" - }, - { - "include": "#literal" - }, - { - "include": "#parenthesized-expression" - }, - { - "include": "#tuple-deconstruction-assignment" - }, - { - "include": "#initializer-expression" - }, - { - "include": "#identifier" - } - ] - }, - "extern-alias-directive": { - "begin": "\\s*(extern)\\b\\s*(alias)\\b\\s*(@?[_[:alpha:]][_[:alnum:]]*)", - "beginCaptures": { - "1": { - "name": "keyword.other.extern.cs" - }, - "2": { - "name": "keyword.other.alias.cs" - }, - "3": { - "name": "variable.other.alias.cs" - } - }, - "end": "(?=;)" - }, - "using-directive": { - "patterns": [ - { - "begin": "\\b(using)\\b\\s+(static)\\s+", - "beginCaptures": { - "1": { - "name": "keyword.other.using.cs" - }, - "2": { - "name": "keyword.other.static.cs" - } - }, - "end": "(?=;)", - "patterns": [ - { - "include": "#type" - } - ] - }, - { - "begin": "\\b(using)\\s+(?=(@?[_[:alpha:]][_[:alnum:]]*)\\s*=)", - "beginCaptures": { - "1": { - "name": "keyword.other.using.cs" - }, - "2": { - "name": "entity.name.type.alias.cs" - } - }, - "end": "(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type" - }, - { - "include": "#operator-assignment" - } - ] - }, - { - "begin": "\\b(using)\\s*", - "beginCaptures": { - "1": { - "name": "keyword.other.using.cs" - } - }, - "end": "(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "name": "entity.name.type.namespace.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#operator-assignment" - } - ] - } - ] - }, - "attribute-section": { - "begin": "(\\[)(assembly|module|field|event|method|param|property|return|type)?(\\:)?", - "beginCaptures": { - "1": { - "name": "punctuation.squarebracket.open.cs" - }, - "2": { - "name": "keyword.other.attribute-specifier.cs" - }, - "3": { - "name": "punctuation.separator.colon.cs" - } - }, - "end": "(\\])", - "endCaptures": { - "1": { - "name": "punctuation.squarebracket.close.cs" - } - }, - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#attribute" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "attribute": { - "patterns": [ - { - "include": "#type-name" - }, - { - "include": "#attribute-arguments" - } - ] - }, - "attribute-arguments": { - "begin": "(\\()", - "beginCaptures": { - "1": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "punctuation.parenthesis.close.cs" - } - }, - "patterns": [ - { - "include": "#attribute-named-argument" - }, - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "attribute-named-argument": { - "begin": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(?==)", - "beginCaptures": { - "1": { - "name": "entity.name.variable.property.cs" - } - }, - "end": "(?=(,|\\)))", - "patterns": [ - { - "include": "#operator-assignment" - }, - { - "include": "#expression" - } - ] - }, - "namespace-declaration": { - "begin": "\\b(namespace)\\s+", - "beginCaptures": { - "1": { - "name": "keyword.other.namespace.cs" - } - }, - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "name": "entity.name.type.namespace.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#punctuation-accessor" - }, - { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "include": "#declarations" - }, - { - "include": "#using-directive" - }, - { - "include": "#punctuation-semicolon" - } - ] - } - ] - }, - "storage-modifier": { - "name": "storage.modifier.cs", - "match": "(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", - "beginCaptures": { - "1": { - "name": "keyword.other.delegate.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.type.delegate.cs" - }, - "8": { - "patterns": [ - { - "include": "#type-parameter-list" - } - ] - } - }, - "end": "(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#parenthesized-parameter-list" - }, - { - "include": "#generic-constraints" - } - ] - }, - "enum-declaration": { - "begin": "(?=\\benum\\b)", - "end": "(?<=\\})", - "patterns": [ - { - "begin": "(?=enum)", - "end": "(?=\\{)", - "patterns": [ - { - "include": "#comment" - }, - { - "match": "(enum)\\s+(@?[_[:alpha:]][_[:alnum:]]*)", - "captures": { - "1": { - "name": "keyword.other.enum.cs" - }, - "2": { - "name": "entity.name.type.enum.cs" - } - } - }, - { - "begin": ":", - "beginCaptures": { - "0": { - "name": "punctuation.separator.colon.cs" - } - }, - "end": "(?=\\{)", - "patterns": [ - { - "include": "#type" - } - ] - } - ] - }, - { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "include": "#preprocessor" - }, - { - "include": "#comment" - }, - { - "include": "#attribute-section" - }, - { - "include": "#punctuation-comma" - }, - { - "begin": "@?[_[:alpha:]][_[:alnum:]]*", - "beginCaptures": { - "0": { - "name": "entity.name.variable.enum-member.cs" - } - }, - "end": "(?=(,|\\}))", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#variable-initializer" - } - ] - } - ] - }, - { - "include": "#preprocessor" - }, - { - "include": "#comment" - } - ] - }, - "interface-declaration": { - "begin": "(?=\\binterface\\b)", - "end": "(?<=\\})", - "patterns": [ - { - "begin": "(?x)\n(interface)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", - "beginCaptures": { - "1": { - "name": "keyword.other.interface.cs" - }, - "2": { - "name": "entity.name.type.interface.cs" - } - }, - "end": "(?=\\{)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-parameter-list" - }, - { - "include": "#base-types" - }, - { - "include": "#generic-constraints" - } - ] - }, - { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "include": "#interface-members" - } - ] - }, - { - "include": "#preprocessor" - }, - { - "include": "#comment" - } - ] - }, - "record-declaration": { - "begin": "(?=\\brecord\\b)", - "end": "(?<=\\})", - "patterns": [ - { - "begin": "(?x)\n(record)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", - "beginCaptures": { - "1": { - "name": "keyword.other.record.cs" - }, - "2": { - "name": "entity.name.type.record.cs" - } - }, - "end": "(?=\\{)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-parameter-list" - }, - { - "include": "#base-types" - }, - { - "include": "#generic-constraints" - } - ] - }, - { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "include": "#class-or-struct-members" - } - ] - }, - { - "include": "#preprocessor" - }, - { - "include": "#comment" - } - ] - }, - "struct-declaration": { - "begin": "(?=\\bstruct\\b)", - "end": "(?<=\\})", - "patterns": [ - { - "begin": "(?x)\n(struct)\\b\\s+\n(@?[_[:alpha:]][_[:alnum:]]*)", - "beginCaptures": { - "1": { - "name": "keyword.other.struct.cs" - }, - "2": { - "name": "entity.name.type.struct.cs" - } - }, - "end": "(?=\\{)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-parameter-list" - }, - { - "include": "#base-types" - }, - { - "include": "#generic-constraints" - } - ] - }, - { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "include": "#class-or-struct-members" - } - ] - }, - { - "include": "#preprocessor" - }, - { - "include": "#comment" - } - ] - }, - "type-parameter-list": { - "begin": "\\<", - "beginCaptures": { - "0": { - "name": "punctuation.definition.typeparameters.begin.cs" - } - }, - "end": "\\>", - "endCaptures": { - "0": { - "name": "punctuation.definition.typeparameters.end.cs" - } - }, - "patterns": [ - { - "match": "\\b(in|out)\\b", - "captures": { - "1": { - "name": "storage.modifier.cs" - } - } - }, - { - "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\b", - "captures": { - "1": { - "name": "entity.name.type.type-parameter.cs" - } - } - }, - { - "include": "#comment" - }, - { - "include": "#punctuation-comma" - }, - { - "include": "#attribute-section" - } - ] - }, - "base-types": { - "begin": ":", - "beginCaptures": { - "0": { - "name": "punctuation.separator.colon.cs" - } - }, - "end": "(?=\\{|where)", - "patterns": [ - { - "include": "#type" - }, - { - "include": "#punctuation-comma" - }, - { - "include": "#preprocessor" - } - ] - }, - "generic-constraints": { - "begin": "(where)\\s+(@?[_[:alpha:]][_[:alnum:]]*)\\s*(:)", - "beginCaptures": { - "1": { - "name": "keyword.other.where.cs" - }, - "2": { - "name": "entity.name.type.type-parameter.cs" - }, - "3": { - "name": "punctuation.separator.colon.cs" - } - }, - "end": "(?=\\{|where|;|=>)", - "patterns": [ - { - "name": "keyword.other.class.cs", - "match": "\\bclass\\b" - }, - { - "name": "keyword.other.struct.cs", - "match": "\\bstruct\\b" - }, - { - "match": "(new)\\s*(\\()\\s*(\\))", - "captures": { - "1": { - "name": "keyword.other.new.cs" - }, - "2": { - "name": "punctuation.parenthesis.open.cs" - }, - "3": { - "name": "punctuation.parenthesis.close.cs" - } - } - }, - { - "include": "#type" - }, - { - "include": "#punctuation-comma" - }, - { - "include": "#generic-constraints" - } - ] - }, - "field-declaration": { - "begin": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s* # first field name\n(?!=>|==)(?=,|;|=|$)", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "6": { - "name": "entity.name.variable.field.cs" - } - }, - "end": "(?=;)", - "patterns": [ - { - "name": "entity.name.variable.field.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#punctuation-comma" - }, - { - "include": "#comment" - }, - { - "include": "#variable-initializer" - }, - { - "include": "#class-or-struct-members" - } - ] - }, - "property-declaration": { - "begin": "(?x)\n\n# The negative lookahead below ensures that we don't match nested types\n# or other declarations as properties.\n(?![[:word:][:space:]]*\\b(?:class|interface|struct|enum|event)\\b)\n\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g)\\s*\n(?=\\{|=>|$)", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "patterns": [ - { - "include": "#type" - }, - { - "include": "#punctuation-accessor" - } - ] - }, - "8": { - "name": "entity.name.variable.property.cs" - } - }, - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#property-accessors" - }, - { - "include": "#expression-body" - }, - { - "include": "#variable-initializer" - }, - { - "include": "#class-or-struct-members" - } - ] - }, - "indexer-declaration": { - "begin": "(?x)\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?this)\\s*\n(?=\\[)", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "patterns": [ - { - "include": "#type" - }, - { - "include": "#punctuation-accessor" - } - ] - }, - "8": { - "name": "keyword.other.this.cs" - } - }, - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#bracketed-parameter-list" - }, - { - "include": "#property-accessors" - }, - { - "include": "#expression-body" - }, - { - "include": "#variable-initializer" - } - ] - }, - "event-declaration": { - "begin": "(?x)\n\\b(event)\\b\\s*\n(?\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(?\\g(?:\\s*,\\s*\\g)*)\\s*\n(?=\\{|;|$)", - "beginCaptures": { - "1": { - "name": "keyword.other.event.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "8": { - "patterns": [ - { - "include": "#type" - }, - { - "include": "#punctuation-accessor" - } - ] - }, - "9": { - "patterns": [ - { - "name": "entity.name.variable.event.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#punctuation-comma" - } - ] - } - }, - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#event-accessors" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "property-accessors": { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "name": "storage.modifier.cs", - "match": "\\b(private|protected|internal)\\b" - }, - { - "name": "keyword.other.get.cs", - "match": "\\b(get)\\b" - }, - { - "name": "keyword.other.set.cs", - "match": "\\b(set)\\b" - }, - { - "name": "keyword.other.init.cs", - "match": "\\b(init)\\b" - }, - { - "include": "#comment" - }, - { - "include": "#attribute-section" - }, - { - "include": "#expression-body" - }, - { - "include": "#block" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "event-accessors": { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "name": "keyword.other.add.cs", - "match": "\\b(add)\\b" - }, - { - "name": "keyword.other.remove.cs", - "match": "\\b(remove)\\b" - }, - { - "include": "#comment" - }, - { - "include": "#attribute-section" - }, - { - "include": "#expression-body" - }, - { - "include": "#block" - }, - { - "include": "#punctuation-semicolon" - } - ] - }, - "method-declaration": { - "begin": "(?x)\n(?\n (?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\\s+\n)\n(?\\g\\s*\\.\\s*)?\n(\\g)\\s*\n(<([^<>]+)>)?\\s*\n(?=\\()", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "patterns": [ - { - "include": "#type" - }, - { - "include": "#punctuation-accessor" - } - ] - }, - "8": { - "name": "entity.name.function.cs" - }, - "9": { - "patterns": [ - { - "include": "#type-parameter-list" - } - ] - } - }, - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#parenthesized-parameter-list" - }, - { - "include": "#generic-constraints" - }, - { - "include": "#expression-body" - }, - { - "include": "#block" - } - ] - }, - "constructor-declaration": { - "begin": "(?=@?[_[:alpha:]][_[:alnum:]]*\\s*\\()", - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\b", - "captures": { - "1": { - "name": "entity.name.function.cs" - } - } - }, - { - "begin": "(:)", - "beginCaptures": { - "1": { - "name": "punctuation.separator.colon.cs" - } - }, - "end": "(?=\\{|=>)", - "patterns": [ - { - "include": "#constructor-initializer" - } - ] - }, - { - "include": "#parenthesized-parameter-list" - }, - { - "include": "#preprocessor" - }, - { - "include": "#comment" - }, - { - "include": "#expression-body" - }, - { - "include": "#block" - } - ] - }, - "constructor-initializer": { - "begin": "\\b(?:(base)|(this))\\b\\s*(?=\\()", - "beginCaptures": { - "1": { - "name": "keyword.other.base.cs" - }, - "2": { - "name": "keyword.other.this.cs" - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#argument-list" - } - ] - }, - "destructor-declaration": { - "begin": "(~)(@?[_[:alpha:]][_[:alnum:]]*)\\s*(?=\\()", - "beginCaptures": { - "1": { - "name": "punctuation.tilde.cs" - }, - "2": { - "name": "entity.name.function.cs" - } - }, - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#parenthesized-parameter-list" - }, - { - "include": "#expression-body" - }, - { - "include": "#block" - } - ] - }, - "operator-declaration": { - "begin": "(?x)\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?(?:\\b(?:operator)))\\s*\n(?(?:\\+|-|\\*|/|%|&|\\||\\^|\\<\\<|\\>\\>|==|!=|\\>|\\<|\\>=|\\<=|!|~|\\+\\+|--|true|false))\\s*\n(?=\\()", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "6": { - "name": "keyword.other.operator-decl.cs" - }, - "7": { - "name": "entity.name.function.cs" - } - }, - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#parenthesized-parameter-list" - }, - { - "include": "#expression-body" - }, - { - "include": "#block" - } - ] - }, - "conversion-operator-declaration": { - "begin": "(?x)\n(?(?:\\b(?:explicit|implicit)))\\s*\n(?(?:\\b(?:operator)))\\s*\n(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\()", - "beginCaptures": { - "1": { - "patterns": [ - { - "match": "\\b(explicit)\\b", - "captures": { - "1": { - "name": "keyword.other.explicit.cs" - } - } - }, - { - "match": "\\b(implicit)\\b", - "captures": { - "1": { - "name": "keyword.other.implicit.cs" - } - } - } - ] - }, - "2": { - "name": "keyword.other.operator-decl.cs" - }, - "3": { - "patterns": [ - { - "include": "#type" - } - ] - } - }, - "end": "(?<=\\})|(?=;)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#parenthesized-parameter-list" - }, - { - "include": "#expression-body" - }, - { - "include": "#block" - } - ] - }, - "block": { - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "include": "#statement" - } - ] - }, - "variable-initializer": { - "begin": "(?)", - "beginCaptures": { - "1": { - "name": "keyword.operator.assignment.cs" - } - }, - "end": "(?=[,\\)\\];}])", - "patterns": [ - { - "include": "#ref-modifier" - }, - { - "include": "#expression" - } - ] - }, - "expression-body": { - "begin": "=>", - "beginCaptures": { - "0": { - "name": "keyword.operator.arrow.cs" - } - }, - "end": "(?=[,\\);}])", - "patterns": [ - { - "include": "#ref-modifier" - }, - { - "include": "#expression" - } - ] - }, - "goto-statement": { - "begin": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\b\\s*", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "2": { - "name": "entity.name.variable.local.cs" - } - }, - "end": "(?==>)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#switch-when-clause" - } - ] - }, - "switch-property-expression": { - "begin": "(?x) # e.g. int x OR var x\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(\\{)", - "beginCaptures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "6": { - "name": "punctuation.curlybrace.open.cs" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.curlybrace.close.cs" - } - }, - "patterns": [ - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "switch-var-pattern": { - "begin": "(?x) # match foreach (var (x, y) in ...)\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*", - "beginCaptures": { - "1": { - "name": "keyword.other.var.cs" - }, - "2": { - "patterns": [ - { - "include": "#tuple-declaration-deconstruction-element-list" - } - ] - } - }, - "end": "(?==>)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#switch-when-clause" - } - ] - }, - "switch-when-clause": { - "begin": "(?)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - }, - { - "match": "\\(", - "captures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - } - }, - { - "match": "\\)", - "captures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - } - } - ] - }, - "switch-label": { - "patterns": [ - { - "begin": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s+\n\\b(in)\\b", - "captures": { - "1": { - "name": "keyword.other.var.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.variable.local.cs" - }, - "8": { - "name": "keyword.control.loop.in.cs" - } - } - }, - { - "match": "(?x) # match foreach (var (x, y) in ...)\n(?:\\b(var)\\b\\s*)?\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s+\n\\b(in)\\b", - "captures": { - "1": { - "name": "keyword.other.var.cs" - }, - "2": { - "patterns": [ - { - "include": "#tuple-declaration-deconstruction-element-list" - } - ] - }, - "3": { - "name": "keyword.control.loop.in.cs" - } - } - }, - { - "include": "#expression" - } - ] - }, - { - "include": "#statement" - } - ] - }, - "try-statement": { - "patterns": [ - { - "include": "#try-block" - }, - { - "include": "#catch-clause" - }, - { - "include": "#finally-clause" - } - ] - }, - "try-block": { - "begin": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?:(\\g)\\b)?", - "captures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "6": { - "name": "entity.name.variable.local.cs" - } - } - } - ] - }, - { - "include": "#when-clause" - }, - { - "include": "#comment" - }, - { - "include": "#block" - } - ] - }, - "when-clause": { - "begin": "(?\n (?:\n (?:ref\\s+(?:readonly\\s+)?)? # ref local\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\s*\n(?!=>)\n(?=,|;|=|\\))", - "beginCaptures": { - "1": { - "name": "keyword.other.using.cs" - }, - "2": { - "name": "storage.modifier.cs" - }, - "3": { - "name": "storage.modifier.cs" - }, - "4": { - "name": "keyword.other.var.cs" - }, - "5": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "10": { - "name": "entity.name.variable.local.cs" - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "name": "entity.name.variable.local.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#punctuation-comma" - }, - { - "include": "#comment" - }, - { - "include": "#variable-initializer" - } - ] - }, - "local-constant-declaration": { - "begin": "(?x)\n(?\\b(?:const)\\b)\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)\\s*\n(?=,|;|=)", - "beginCaptures": { - "1": { - "name": "storage.modifier.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.variable.local.cs" - } - }, - "end": "(?=;)", - "patterns": [ - { - "name": "entity.name.variable.local.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - }, - { - "include": "#punctuation-comma" - }, - { - "include": "#comment" - }, - { - "include": "#variable-initializer" - } - ] - }, - "local-function-declaration": { - "patterns": [ - { - "include": "#method-declaration" - } - ] - }, - "local-tuple-var-deconstruction": { - "begin": "(?x) # e.g. var (x, y) = GetPoint();\n(?:\\b(var)\\b\\s*)\n(?\\((?:[^\\(\\)]|\\g)+\\))\\s*\n(?=;|=|\\))", - "beginCaptures": { - "1": { - "name": "keyword.other.var.cs" - }, - "2": { - "patterns": [ - { - "include": "#tuple-declaration-deconstruction-element-list" - } - ] - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#variable-initializer" - } - ] - }, - "tuple-deconstruction-assignment": { - "match": "(?x)\n(?\\s*\\((?:[^\\(\\)]|\\g)+\\))\\s*\n(?!=>|==)(?==)", - "captures": { - "1": { - "patterns": [ - { - "include": "#tuple-deconstruction-element-list" - } - ] - } - } - }, - "tuple-declaration-deconstruction-element-list": { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - }, - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#tuple-declaration-deconstruction-element-list" - }, - { - "include": "#declaration-expression-tuple" - }, - { - "include": "#punctuation-comma" - }, - { - "match": "(?x) # e.g. x\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(?=[,)])", - "captures": { - "1": { - "name": "entity.name.variable.tuple-element.cs" - } - } - } - ] - }, - "tuple-deconstruction-element-list": { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - }, - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#tuple-deconstruction-element-list" - }, - { - "include": "#declaration-expression-tuple" - }, - { - "include": "#punctuation-comma" - }, - { - "match": "(?x) # e.g. x\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(?=[,)])", - "captures": { - "1": { - "name": "variable.other.readwrite.cs" - } - } - } - ] - }, - "declaration-expression-local": { - "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)\\]])", - "captures": { - "1": { - "name": "keyword.other.var.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.variable.local.cs" - } - } - }, - "declaration-expression-tuple": { - "match": "(?x) # e.g. int x OR var x\n(?:\n \\b(var)\\b|\n (?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n )\n)\\s+\n(\\g)\\b\\s*\n(?=[,)])", - "captures": { - "1": { - "name": "keyword.other.var.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.variable.tuple-element.cs" - } - } - }, - "checked-unchecked-expression": { - "begin": "(?>=|\\|=" - }, - { - "name": "keyword.operator.bitwise.shift.cs", - "match": "<<|>>" - }, - { - "name": "keyword.operator.comparison.cs", - "match": "==|!=" - }, - { - "name": "keyword.operator.relational.cs", - "match": "<=|>=|<|>" - }, - { - "name": "keyword.operator.logical.cs", - "match": "\\!|&&|\\|\\|" - }, - { - "name": "keyword.operator.bitwise.cs", - "match": "\\&|~|\\^|\\|" - }, - { - "name": "keyword.operator.assignment.cs", - "match": "\\=" - }, - { - "name": "keyword.operator.decrement.cs", - "match": "--" - }, - { - "name": "keyword.operator.increment.cs", - "match": "\\+\\+" - }, - { - "name": "keyword.operator.arithmetic.cs", - "match": "%|\\*|/|-|\\+" - }, - { - "name": "keyword.operator.null-coalescing.cs", - "match": "\\?\\?" - } - ] - }, - "switch-literal": { - "name": "constant.language.null.cs", - "match": "(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(\\))(?=\\s*-*!*@?[_[:alnum:]\\(])", - "captures": { - "1": { - "name": "punctuation.parenthesis.open.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "punctuation.parenthesis.close.cs" - } - } - }, - "as-expression": { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", - "captures": { - "1": { - "name": "keyword.other.as.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - } - } - }, - "is-expression": { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?", - "captures": { - "1": { - "name": "keyword.other.is.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - } - } - }, - "this-or-base-expression": { - "match": "\\b(?:(base)|(this))\\b", - "captures": { - "1": { - "name": "keyword.other.base.cs" - }, - "2": { - "name": "keyword.other.this.cs" - } - } - }, - "invocation-expression": { - "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # method name\n(?\\s*<([^<>]|\\g)+>\\s*)?\\s* # type arguments\n(?=\\() # open paren of argument list", - "beginCaptures": { - "1": { - "name": "keyword.operator.null-conditional.cs" - }, - "2": { - "name": "punctuation.accessor.cs" - }, - "3": { - "name": "entity.name.function.cs" - }, - "4": { - "patterns": [ - { - "include": "#type-arguments" - } - ] - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#argument-list" - } - ] - }, - "element-access-expression": { - "begin": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(?:(\\.)\\s*)? # preceding dot?\n(?:(@?[_[:alpha:]][_[:alnum:]]*)\\s*)? # property name\n(?:(\\?)\\s*)? # null-conditional operator?\n(?=\\[) # open bracket of argument list", - "beginCaptures": { - "1": { - "name": "keyword.operator.null-conditional.cs" - }, - "2": { - "name": "punctuation.accessor.cs" - }, - "3": { - "name": "variable.other.object.property.cs" - }, - "4": { - "name": "keyword.operator.null-conditional.cs" - } - }, - "end": "(?<=\\])(?!\\s*\\[)", - "patterns": [ - { - "include": "#bracketed-argument-list" - } - ] - }, - "member-access-expression": { - "patterns": [ - { - "match": "(?x)\n(?:(\\?)\\s*)? # preceding null-conditional operator?\n(\\.)\\s* # preceding dot\n(@?[_[:alpha:]][_[:alnum:]]*)\\s* # property name\n(?![_[:alnum:]]|\\(|(\\?)?\\[|<) # next character is not alpha-numeric, nor a (, [, or <. Also, test for ?[", - "captures": { - "1": { - "name": "keyword.operator.null-conditional.cs" - }, - "2": { - "name": "punctuation.accessor.cs" - }, - "3": { - "name": "variable.other.object.property.cs" - } - } - }, - { - "match": "(?x)\n(\\.)?\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?\\s*<([^<>]|\\g)+>\\s*)\n(?=\n (\\s*\\?)?\n \\s*\\.\\s*@?[_[:alpha:]][_[:alnum:]]*\n)", - "captures": { - "1": { - "name": "punctuation.accessor.cs" - }, - "2": { - "name": "variable.other.object.cs" - }, - "3": { - "patterns": [ - { - "include": "#type-arguments" - } - ] - } - } - }, - { - "match": "(?x)\n(@?[_[:alpha:]][_[:alnum:]]*)\n(?=\n (\\s*\\?)?\n \\s*\\.\\s*@?[_[:alpha:]][_[:alnum:]]*\n)", - "captures": { - "1": { - "name": "variable.other.object.cs" - } - } - } - ] - }, - "object-creation-expression": { - "patterns": [ - { - "include": "#object-creation-expression-with-parameters" - }, - { - "include": "#object-creation-expression-with-no-parameters" - } - ] - }, - "object-creation-expression-with-parameters": { - "begin": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\()", - "beginCaptures": { - "1": { - "name": "keyword.other.new.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#argument-list" - } - ] - }, - "object-creation-expression-with-no-parameters": { - "match": "(?x)\n(new)\\s+\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s*\n(?=\\{|$)", - "captures": { - "1": { - "name": "keyword.other.new.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - } - } - }, - "array-creation-expression": { - "begin": "(?x)\n\\b(new|stackalloc)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\\s*\n(?=\\[)", - "beginCaptures": { - "1": { - "name": "keyword.other.new.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - } - }, - "end": "(?<=\\])", - "patterns": [ - { - "include": "#bracketed-argument-list" - } - ] - }, - "anonymous-object-creation-expression": { - "begin": "\\b(new)\\b\\s*(?=\\{|$)", - "beginCaptures": { - "1": { - "name": "keyword.other.new.cs" - } - }, - "end": "(?<=\\})", - "patterns": [ - { - "include": "#initializer-expression" - } - ] - }, - "bracketed-parameter-list": { - "begin": "(?=(\\[))", - "beginCaptures": { - "1": { - "name": "punctuation.squarebracket.open.cs" - } - }, - "end": "(?=(\\]))", - "endCaptures": { - "1": { - "name": "punctuation.squarebracket.close.cs" - } - }, - "patterns": [ - { - "begin": "(?<=\\[)", - "end": "(?=\\])", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#attribute-section" - }, - { - "include": "#parameter" - }, - { - "include": "#punctuation-comma" - }, - { - "include": "#variable-initializer" - } - ] - } - ] - }, - "parenthesized-parameter-list": { - "begin": "(\\()", - "beginCaptures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "(\\))", - "endCaptures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - }, - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#attribute-section" - }, - { - "include": "#parameter" - }, - { - "include": "#punctuation-comma" - }, - { - "include": "#variable-initializer" - } - ] - }, - "parameter": { - "match": "(?x)\n(?:(?:\\b(ref|params|out|in|this)\\b)\\s+)?\n(?\n (?:\n (?:ref\\s+)? # ref return\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+\n(\\g)", - "captures": { - "1": { - "name": "storage.modifier.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.variable.parameter.cs" - } - } - }, - "argument-list": { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - }, - "patterns": [ - { - "include": "#named-argument" - }, - { - "include": "#argument" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "bracketed-argument-list": { - "begin": "\\[", - "beginCaptures": { - "0": { - "name": "punctuation.squarebracket.open.cs" - } - }, - "end": "\\]", - "endCaptures": { - "0": { - "name": "punctuation.squarebracket.close.cs" - } - }, - "patterns": [ - { - "include": "#named-argument" - }, - { - "include": "#argument" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "named-argument": { - "begin": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(:)", - "beginCaptures": { - "1": { - "name": "entity.name.variable.parameter.cs" - }, - "2": { - "name": "punctuation.separator.colon.cs" - } - }, - "end": "(?=(,|\\)|\\]))", - "patterns": [ - { - "include": "#argument" - } - ] - }, - "argument": { - "patterns": [ - { - "name": "storage.modifier.cs", - "match": "\\b(ref|out|in)\\b" - }, - { - "include": "#declaration-expression-local" - }, - { - "include": "#expression" - } - ] - }, - "query-expression": { - "begin": "(?x)\n\\b(from)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", - "beginCaptures": { - "1": { - "name": "keyword.query.from.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.variable.range-variable.cs" - }, - "8": { - "name": "keyword.query.in.cs" - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "include": "#query-body" - }, - { - "include": "#expression" - } - ] - }, - "query-body": { - "patterns": [ - { - "include": "#let-clause" - }, - { - "include": "#where-clause" - }, - { - "include": "#join-clause" - }, - { - "include": "#orderby-clause" - }, - { - "include": "#select-clause" - }, - { - "include": "#group-clause" - } - ] - }, - "let-clause": { - "begin": "(?x)\n\\b(let)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=)\\s*", - "beginCaptures": { - "1": { - "name": "keyword.query.let.cs" - }, - "2": { - "name": "entity.name.variable.range-variable.cs" - }, - "3": { - "name": "keyword.operator.assignment.cs" - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "include": "#query-body" - }, - { - "include": "#expression" - } - ] - }, - "where-clause": { - "begin": "(?x)\n\\b(where)\\b\\s*", - "beginCaptures": { - "1": { - "name": "keyword.query.where.cs" - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "include": "#query-body" - }, - { - "include": "#expression" - } - ] - }, - "join-clause": { - "begin": "(?x)\n\\b(join)\\b\\s*\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)?\n\\s+(\\g)\\b\\s*\n\\b(in)\\b\\s*", - "beginCaptures": { - "1": { - "name": "keyword.query.join.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.variable.range-variable.cs" - }, - "8": { - "name": "keyword.query.in.cs" - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "include": "#join-on" - }, - { - "include": "#join-equals" - }, - { - "include": "#join-into" - }, - { - "include": "#query-body" - }, - { - "include": "#expression" - } - ] - }, - "join-on": { - "match": "\\b(on)\\b\\s*", - "captures": { - "1": { - "name": "keyword.query.on.cs" - } - } - }, - "join-equals": { - "match": "\\b(equals)\\b\\s*", - "captures": { - "1": { - "name": "keyword.query.equals.cs" - } - } - }, - "join-into": { - "match": "(?x)\n\\b(into)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*", - "captures": { - "1": { - "name": "keyword.query.into.cs" - }, - "2": { - "name": "entity.name.variable.range-variable.cs" - } - } - }, - "orderby-clause": { - "begin": "\\b(orderby)\\b\\s*", - "beginCaptures": { - "1": { - "name": "keyword.query.orderby.cs" - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "include": "#ordering-direction" - }, - { - "include": "#query-body" - }, - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "ordering-direction": { - "match": "\\b(?:(ascending)|(descending))\\b", - "captures": { - "1": { - "name": "keyword.query.ascending.cs" - }, - "2": { - "name": "keyword.query.descending.cs" - } - } - }, - "select-clause": { - "begin": "\\b(select)\\b\\s*", - "beginCaptures": { - "1": { - "name": "keyword.query.select.cs" - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "include": "#query-body" - }, - { - "include": "#expression" - } - ] - }, - "group-clause": { - "begin": "\\b(group)\\b\\s*", - "beginCaptures": { - "1": { - "name": "keyword.query.group.cs" - } - }, - "end": "(?=;|\\))", - "patterns": [ - { - "include": "#group-by" - }, - { - "include": "#group-into" - }, - { - "include": "#query-body" - }, - { - "include": "#expression" - } - ] - }, - "group-by": { - "match": "\\b(by)\\b\\s*", - "captures": { - "1": { - "name": "keyword.query.by.cs" - } - } - }, - "group-into": { - "match": "(?x)\n\\b(into)\\b\\s*\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*", - "captures": { - "1": { - "name": "keyword.query.into.cs" - }, - "2": { - "name": "entity.name.variable.range-variable.cs" - } - } - }, - "anonymous-method-expression": { - "patterns": [ - { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(@?[_[:alpha:]][_[:alnum:]]*)\\b\\s*\n(=>)", - "beginCaptures": { - "1": { - "name": "storage.modifier.cs" - }, - "2": { - "name": "entity.name.variable.parameter.cs" - }, - "3": { - "name": "keyword.operator.arrow.cs" - } - }, - "end": "(?=\\)|;|}|,)", - "patterns": [ - { - "include": "#block" - }, - { - "include": "#ref-modifier" - }, - { - "include": "#expression" - } - ] - }, - { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(\\(.*?\\))\\s*\n(=>)", - "beginCaptures": { - "1": { - "name": "storage.modifier.cs" - }, - "2": { - "patterns": [ - { - "include": "#lambda-parameter-list" - } - ] - }, - "3": { - "name": "keyword.operator.arrow.cs" - } - }, - "end": "(?=\\)|;|}|,)", - "patterns": [ - { - "include": "#block" - }, - { - "include": "#ref-modifier" - }, - { - "include": "#expression" - } - ] - }, - { - "begin": "(?x)\n(?:\\b(async)\\b\\s*)?\n(?:\\b(delegate)\\b\\s*)", - "beginCaptures": { - "1": { - "name": "storage.modifier.cs" - }, - "2": { - "name": "keyword.other.delegate.cs" - } - }, - "end": "(?=\\)|;|}|,)", - "patterns": [ - { - "include": "#parenthesized-parameter-list" - }, - { - "include": "#block" - }, - { - "include": "#expression" - } - ] - } - ] - }, - "lambda-parameter-list": { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - }, - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#attribute-section" - }, - { - "include": "#lambda-parameter" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "lambda-parameter": { - "match": "(?x)\n(?:\\b(ref|out|in)\\b)?\\s*\n(?:(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\\s+)?\n(\\g)\\b\\s*\n(?=[,)])", - "captures": { - "1": { - "name": "storage.modifier.cs" - }, - "2": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "7": { - "name": "entity.name.variable.parameter.cs" - } - } - }, - "type": { - "name": "meta.type.cs", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#ref-modifier" - }, - { - "include": "#readonly-modifier" - }, - { - "include": "#tuple-type" - }, - { - "include": "#type-builtin" - }, - { - "include": "#type-name" - }, - { - "include": "#type-arguments" - }, - { - "include": "#type-array-suffix" - }, - { - "include": "#type-nullable-suffix" - } - ] - }, - "ref-modifier": { - "name": "storage.modifier.cs", - "match": "\\b(ref)\\b" - }, - "readonly-modifier": { - "name": "storage.modifier.cs", - "match": "\\b(readonly)\\b" - }, - "tuple-type": { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.parenthesis.open.cs" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.parenthesis.close.cs" - } - }, - "patterns": [ - { - "include": "#tuple-element" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "tuple-element": { - "match": "(?x)\n(?\n (?:\n (?:\n (?:(?@?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s* # array suffix?\n \\[\n (?:\\s*,\\s*)* # commata for multi-dimensional arrays\n \\]\n \\s*\n (?:\\?)? # arrays can be nullable reference types\n \\s*\n )*\n )\n)\n(?:(?\\g)\\b)?", - "captures": { - "1": { - "patterns": [ - { - "include": "#type" - } - ] - }, - "6": { - "name": "entity.name.variable.tuple-element.cs" - } - } - }, - "type-builtin": { - "match": "\\b(bool|byte|char|decimal|double|float|int|long|object|sbyte|short|string|uint|ulong|ushort|void|dynamic)\\b", - "captures": { - "1": { - "name": "keyword.type.cs" - } - } - }, - "type-name": { - "patterns": [ - { - "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(\\:\\:)", - "captures": { - "1": { - "name": "entity.name.type.alias.cs" - }, - "2": { - "name": "punctuation.separator.coloncolon.cs" - } - } - }, - { - "match": "(@?[_[:alpha:]][_[:alnum:]]*)\\s*(\\.)", - "captures": { - "1": { - "name": "entity.name.type.cs" - }, - "2": { - "name": "punctuation.accessor.cs" - } - } - }, - { - "match": "(\\.)\\s*(@?[_[:alpha:]][_[:alnum:]]*)", - "captures": { - "1": { - "name": "punctuation.accessor.cs" - }, - "2": { - "name": "entity.name.type.cs" - } - } - }, - { - "name": "entity.name.type.cs", - "match": "@?[_[:alpha:]][_[:alnum:]]*" - } - ] - }, - "type-arguments": { - "begin": "<", - "beginCaptures": { - "0": { - "name": "punctuation.definition.typeparameters.begin.cs" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.typeparameters.end.cs" - } - }, - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "type-array-suffix": { - "begin": "\\[", - "beginCaptures": { - "0": { - "name": "punctuation.squarebracket.open.cs" - } - }, - "end": "\\]", - "endCaptures": { - "0": { - "name": "punctuation.squarebracket.close.cs" - } - }, - "patterns": [ - { - "include": "#punctuation-comma" - } - ] - }, - "type-nullable-suffix": { - "match": "\\?", - "captures": { - "0": { - "name": "punctuation.separator.question-mark.cs" - } - } - }, - "operator-assignment": { - "name": "keyword.operator.assignment.cs", - "match": "(?)", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.cs" - } - }, - "patterns": [ - { - "include": "#xml-attribute" - } - ] - }, - "xml-attribute": { - "patterns": [ - { - "match": "(?x)\n(?:^|\\s+)\n(\n (?:\n ([-_[:alnum:]]+)\n (:)\n )?\n ([-_[:alnum:]]+)\n)\n(=)", - "captures": { - "1": { - "name": "entity.other.attribute-name.cs" - }, - "2": { - "name": "entity.other.attribute-name.namespace.cs" - }, - "3": { - "name": "punctuation.separator.colon.cs" - }, - "4": { - "name": "entity.other.attribute-name.localname.cs" - }, - "5": { - "name": "punctuation.separator.equals.cs" - } - } - }, - { - "include": "#xml-string" - } - ] - }, - "xml-cdata": { - "name": "string.unquoted.cdata.cs", - "begin": "", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.cs" - } - } - }, - "xml-string": { - "patterns": [ - { - "name": "string.quoted.single.cs", - "begin": "\\'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.cs" - } - }, - "end": "\\'", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.cs" - } - }, - "patterns": [ - { - "include": "#xml-character-entity" - } - ] - }, - { - "name": "string.quoted.double.cs", - "begin": "\\\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.cs" - } - }, - "end": "\\\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.cs" - } - }, - "patterns": [ - { - "include": "#xml-character-entity" - } - ] - } - ] - }, - "xml-character-entity": { - "patterns": [ - { - "name": "constant.character.entity.cs", - "match": "(?x)\n(&)\n(\n (?:[[:alpha:]:_][[:alnum:]:_.-]*)|\n (?:\\#[[:digit:]]+)|\n (?:\\#x[[:xdigit:]]+)\n)\n(;)", - "captures": { - "1": { - "name": "punctuation.definition.constant.cs" - }, - "3": { - "name": "punctuation.definition.constant.cs" - } - } - }, - { - "name": "invalid.illegal.bad-ampersand.cs", - "match": "&" - } - ] - }, - "xml-comment": { - "name": "comment.block.cs", - "begin": "", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.cs" - } - } - } - } -} \ No newline at end of file diff --git a/extensions/css-language-features/.eslintrc.json b/extensions/css-language-features/.eslintrc.json deleted file mode 100644 index ce28ab7a81..0000000000 --- a/extensions/css-language-features/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rules": { - "@typescript-eslint/explicit-function-return-type": ["off"], - "@typescript-eslint/await-thenable": ["off"], - "@typescript-eslint/no-unsafe-assignment": "off" - } -} diff --git a/extensions/css-language-features/server/src/utils/validation.ts b/extensions/css-language-features/server/src/utils/validation.ts deleted file mode 100644 index 07d40d2ff3..0000000000 --- a/extensions/css-language-features/server/src/utils/validation.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver'; -import { TextDocument } from 'vscode-css-languageservice'; -import { formatError, runSafeAsync } from './runner'; -import { RuntimeEnvironment } from '../cssServer'; - -export type Validator = (textDocument: TextDocument) => Promise; -export type DiagnosticsSupport = { - dispose(): void; - requestRefresh(): void; -}; - -export function registerDiagnosticsPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { - - const pendingValidationRequests: { [uri: string]: Disposable } = {}; - const validationDelayMs = 500; - - const disposables: Disposable[] = []; - - // The content of a text document has changed. This event is emitted - // when the text document first opened or when its content has changed. - documents.onDidChangeContent(change => { - triggerValidation(change.document); - }, undefined, disposables); - - // a document has closed: clear all diagnostics - documents.onDidClose(event => { - cleanPendingValidation(event.document); - connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); - }, undefined, disposables); - - function cleanPendingValidation(textDocument: TextDocument): void { - const request = pendingValidationRequests[textDocument.uri]; - if (request) { - request.dispose(); - delete pendingValidationRequests[textDocument.uri]; - } - } - - function triggerValidation(textDocument: TextDocument): void { - cleanPendingValidation(textDocument); - const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => { - if (request === pendingValidationRequests[textDocument.uri]) { - try { - const diagnostics = await validate(textDocument); - if (request === pendingValidationRequests[textDocument.uri]) { - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); - } - delete pendingValidationRequests[textDocument.uri]; - } catch (e) { - connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); - } - } - }, validationDelayMs); - } - - return { - requestRefresh: () => { - documents.all().forEach(triggerValidation); - }, - dispose: () => { - disposables.forEach(d => d.dispose()); - disposables.length = 0; - const keys = Object.keys(pendingValidationRequests); - for (const key of keys) { - pendingValidationRequests[key].dispose(); - delete pendingValidationRequests[key]; - } - } - }; -} - -export function registerDiagnosticsPullSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { - - function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport { - return { - kind: DocumentDiagnosticReportKind.Full, - items: diagnostics - }; - } - - const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { - return runSafeAsync(runtime, async () => { - const document = documents.get(params.textDocument.uri); - if (document) { - return newDocumentDiagnosticReport(await validate(document)); - } - return newDocumentDiagnosticReport([]); - - }, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token); - }); - - function requestRefresh(): void { - connection.languages.diagnostics.refresh(); - } - - return { - requestRefresh, - dispose: () => { - registration.dispose(); - } - }; - -} diff --git a/extensions/dart/.vscodeignore b/extensions/dart/.vscodeignore deleted file mode 100644 index d9011becfb..0000000000 --- a/extensions/dart/.vscodeignore +++ /dev/null @@ -1,2 +0,0 @@ -build/** -cgmanifest.json diff --git a/extensions/dart/cgmanifest.json b/extensions/dart/cgmanifest.json deleted file mode 100644 index 84f084ffdb..0000000000 --- a/extensions/dart/cgmanifest.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "registrations": [ - { - "component": { - "type": "git", - "git": { - "name": "dart-lang/dart-syntax-highlight", - "repositoryUrl": "https://github.com/dart-lang/dart-syntax-highlight", - "commitHash": "9d4857e114b7000d94232d83187ad142961c678a" - } - }, - "licenseDetail": [ - "Copyright 2020, the Dart project authors.", - "", - "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.", - " * Neither the name of Google LLC nor the names of its", - " contributors may be used to endorse or promote products derived", - " from this software without specific prior written permission.", - "", - "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 THE COPYRIGHT", - "OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,", - "SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT", - "LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,", - "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY", - "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT", - "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE", - "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." - ], - "license": "BSD", - "version": "0.0.0" - } - ], - "version": 1 -} \ No newline at end of file diff --git a/extensions/dart/language-configuration.json b/extensions/dart/language-configuration.json deleted file mode 100644 index 9d44a40ee8..0000000000 --- a/extensions/dart/language-configuration.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "comments": { - "lineComment": "//", - "blockComment": [ "/*", "*/" ] - }, - "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] - ], - "autoClosingPairs": [ - { "open": "{", "close": "}" }, - { "open": "[", "close": "]" }, - { "open": "(", "close": ")" }, - { "open": "'", "close": "'", "notIn": ["string", "comment"] }, - { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "`", "close": "`", "notIn": ["string", "comment"] }, - { "open": "/**", "close": " */", "notIn": ["string"] } - ], - "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["<", ">"], - ["'", "'"], - ["\"", "\""], - ["`", "`"] - ] -} diff --git a/extensions/dart/package.json b/extensions/dart/package.json deleted file mode 100644 index c6b58c1e68..0000000000 --- a/extensions/dart/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "dart", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { - "vscode": "0.10.x" - }, - "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin dart-lang/dart-syntax-highlight grammars/dart.json ./syntaxes/dart.tmLanguage.json" - }, - "contributes": { - "languages": [ - { - "id": "dart", - "extensions": [ - ".dart" - ], - "aliases": [ - "Dart" - ], - "configuration": "./language-configuration.json" - } - ], - "grammars": [ - { - "language": "dart", - "scopeName": "source.dart", - "path": "./syntaxes/dart.tmLanguage.json" - } - ] - } -} diff --git a/extensions/dart/package.nls.json b/extensions/dart/package.nls.json deleted file mode 100644 index 71e6b91e93..0000000000 --- a/extensions/dart/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "Dart Language Basics", - "description": "Provides syntax highlighting & bracket matching in Dart files." -} diff --git a/extensions/dart/syntaxes/dart.tmLanguage.json b/extensions/dart/syntaxes/dart.tmLanguage.json deleted file mode 100644 index 00f374a6ba..0000000000 --- a/extensions/dart/syntaxes/dart.tmLanguage.json +++ /dev/null @@ -1,516 +0,0 @@ -{ - "information_for_contributors": [ - "This file has been converted from https://github.com/dart-lang/dart-syntax-highlight/blob/master/grammars/dart.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/dart-lang/dart-syntax-highlight/commit/9d4857e114b7000d94232d83187ad142961c678a", - "name": "Dart", - "scopeName": "source.dart", - "patterns": [ - { - "name": "meta.preprocessor.script.dart", - "match": "^(#!.*)$" - }, - { - "name": "meta.declaration.dart", - "begin": "^\\w*\\b(library|import|part of|part|export)\\b", - "beginCaptures": { - "0": { - "name": "keyword.other.import.dart" - } - }, - "end": ";", - "endCaptures": { - "0": { - "name": "punctuation.terminator.dart" - } - }, - "patterns": [ - { - "include": "#strings" - }, - { - "include": "#comments" - }, - { - "name": "keyword.other.import.dart", - "match": "\\b(as|show|hide)\\b" - }, - { - "name": "keyword.control.dart", - "match": "\\b(if)\\b" - } - ] - }, - { - "include": "#comments" - }, - { - "include": "#punctuation" - }, - { - "include": "#annotations" - }, - { - "include": "#keywords" - }, - { - "include": "#constants-and-special-vars" - }, - { - "include": "#strings" - } - ], - "repository": { - "dartdoc": { - "patterns": [ - { - "match": "(\\[.*?\\])", - "captures": { - "0": { - "name": "variable.name.source.dart" - } - } - }, - { - "match": "^ {4,}(?![ \\*]).*", - "captures": { - "0": { - "name": "variable.name.source.dart" - } - } - }, - { - "contentName": "variable.other.source.dart", - "begin": "```.*?$", - "end": "```" - }, - { - "match": "(`.*?`)", - "captures": { - "0": { - "name": "variable.other.source.dart" - } - } - }, - { - "match": "(`.*?`)", - "captures": { - "0": { - "name": "variable.other.source.dart" - } - } - }, - { - "match": "(\\* (( ).*))$", - "captures": { - "2": { - "name": "variable.other.source.dart" - } - } - } - ] - }, - "comments": { - "patterns": [ - { - "name": "comment.block.empty.dart", - "match": "/\\*\\*/", - "captures": { - "0": { - "name": "punctuation.definition.comment.dart" - } - } - }, - { - "include": "#comments-doc-oldschool" - }, - { - "include": "#comments-doc" - }, - { - "include": "#comments-inline" - } - ] - }, - "comments-doc-oldschool": { - "patterns": [ - { - "name": "comment.block.documentation.dart", - "begin": "/\\*\\*", - "end": "\\*/", - "patterns": [ - { - "include": "#comments-doc-oldschool" - }, - { - "include": "#comments-block" - }, - { - "include": "#dartdoc" - } - ] - } - ] - }, - "comments-doc": { - "patterns": [ - { - "name": "comment.block.documentation.dart", - "begin": "///", - "while": "^\\s*///", - "patterns": [ - { - "include": "#dartdoc" - } - ] - } - ] - }, - "comments-inline": { - "patterns": [ - { - "include": "#comments-block" - }, - { - "match": "((//).*)$", - "captures": { - "1": { - "name": "comment.line.double-slash.dart" - } - } - } - ] - }, - "comments-block": { - "patterns": [ - { - "name": "comment.block.dart", - "begin": "/\\*", - "end": "\\*/", - "patterns": [ - { - "include": "#comments-block" - } - ] - } - ] - }, - "annotations": { - "patterns": [ - { - "name": "storage.type.annotation.dart", - "match": "@[a-zA-Z]+" - } - ] - }, - "constants-and-special-vars": { - "patterns": [ - { - "name": "constant.language.dart", - "match": "(??]|,\\s*|\\s+extends\\s+)+>)?|bool\\b|num\\b|int\\b|double\\b|dynamic\\b|(void)\\b)", - "captures": { - "1": { - "name": "support.class.dart" - }, - "2": { - "patterns": [ - { - "include": "#type-args" - } - ] - }, - "3": { - "name": "storage.type.primitive.dart" - } - } - } - ] - }, - "function-identifier": { - "patterns": [ - { - "match": "([_$]*[a-z][a-zA-Z0-9_$]*)(<(?:[a-zA-Z0-9_$<>?]|,\\s*|\\s+extends\\s+)+>)?[!?]?(\\(|\\s+=>)", - "captures": { - "1": { - "name": "entity.name.function.dart" - }, - "2": { - "patterns": [ - { - "include": "#type-args" - } - ] - } - } - } - ] - }, - "type-args": { - "begin": "(<)", - "end": "(>)", - "beginCaptures": { - "1": { - "name": "other.source.dart" - } - }, - "endCaptures": { - "1": { - "name": "other.source.dart" - } - }, - "patterns": [ - { - "include": "#class-identifier" - }, - { - "match": "[\\s,]+" - }, - { - "name": "keyword.declaration.dart", - "match": "extends" - } - ] - }, - "keywords": { - "patterns": [ - { - "name": "keyword.cast.dart", - "match": "(?>>?|~|\\^|\\||&)" - }, - { - "name": "keyword.operator.assignment.bitwise.dart", - "match": "((&|\\^|\\||<<|>>>?)=)" - }, - { - "name": "keyword.operator.closure.dart", - "match": "(=>)" - }, - { - "name": "keyword.operator.comparison.dart", - "match": "(==|!=|<=?|>=?)" - }, - { - "name": "keyword.operator.assignment.arithmetic.dart", - "match": "(([+*/%-]|\\~)=)" - }, - { - "name": "keyword.operator.assignment.dart", - "match": "(=)" - }, - { - "name": "keyword.operator.increment-decrement.dart", - "match": "(\\-\\-|\\+\\+)" - }, - { - "name": "keyword.operator.arithmetic.dart", - "match": "(\\-|\\+|\\*|\\/|\\~\\/|%)" - }, - { - "name": "keyword.operator.logical.dart", - "match": "(!|&&|\\|\\|)" - }, - { - "name": "storage.modifier.dart", - "match": "(?^._]+|``[[:alpha:]0-9' <>^._]+``)\\s*(:)", - "captures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "variable.fsharp" - }, - "3": { - "name": "keyword.symbol.fsharp" - } - } - }, - { - "include": "#compiler_directives" - }, - { - "include": "#constants" - }, - { - "include": "#strings" - }, - { - "include": "#chars" - }, - { - "include": "#double_tick" - }, - { - "include": "#keywords" - }, - { - "include": "#text" - }, - { - "include": "#definition" - }, - { - "include": "#attributes" - }, - { - "include": "#keywords" - }, - { - "include": "#cexprs" - }, - { - "include": "#text" - } - ] - }, - "strp_inlined": { - "patterns": [ - { - "begin": "(\\()", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "include": "#strp_inlined_body" - } - ] - } - ] - }, - "generic_declaration": { - "patterns": [ - { - "comments": "SRTP syntax support", - "begin": "(:)\\s*(\\()\\s*(static member|member)", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "keyword.symbol.fsharp" - }, - "3": { - "name": "keyword.fsharp" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "begin": "(\\()", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "include": "#member_declaration" - } - ] - }, - { - "match": "(('|\\^)[[:alpha:]0-9'._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#variables" - }, - { - "include": "#keywords" - } - ] - }, - { - "name": "keyword.fsharp", - "match": "\\b(private|to|public|internal|function|yield!|yield|class|exception|match|delegate|of|new|in|as|if|then|else|elif|for|begin|end|inherit|do|let\\!|return\\!|return|interface|with|abstract|enum|member|try|finally|and|when|or|use|use\\!|struct|while|mutable|assert|base|done|downcast|downto|extern|fixed|global|lazy|upcast|not)(?!')\\b" - }, - { - "name": "keyword.fsharp", - "match": ":" - }, - { - "include": "#constants" - }, - { - "match": "(('|\\^)[[:alpha:]0-9'._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "begin": "(<)", - "end": "(>)", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "(('|\\^)[[:alpha:]0-9'._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#tuple_signature" - }, - { - "include": "#generic_declaration" - } - ] - }, - { - "begin": "(\\()", - "end": "(\\))", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "(([?[:alpha:]0-9'`^._ ]+))+", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#tuple_signature" - } - ] - }, - { - "match": "(?!when|and|or\\b)\\b([\\w0-9'`^._]+)", - "comments": "Here we need the \\w modifier in order to check that the words isn't blacklisted", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "match": "(\\|)", - "comments": "Prevent captures of `|>` as a keyword when defining custom operator like `<|>`", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - } - } - }, - { - "include": "#keywords" - } - ] - }, - "anonymous_record_declaration": { - "begin": "(\\{\\|)", - "end": "(\\|\\})", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "[[:alpha:]0-9'`^_ ]+(:)", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - } - } - }, - { - "match": "([[:alpha:]0-9'`^_ ]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#anonymous_record_declaration" - }, - { - "include": "#keywords" - } - ] - }, - "record_signature": { - "patterns": [ - { - "match": "[[:alpha:]0-9'`^_ ]+(=)([[:alpha:]0-9'`^_ ]+)", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "variable.parameter.fsharp" - } - } - }, - { - "begin": "({)", - "end": "(})", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "[[:alpha:]0-9'`^_ ]+(=)([[:alpha:]0-9'`^_ ]+)", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "variable.parameter.fsharp" - } - } - }, - { - "include": "#record_signature" - } - ] - }, - { - "include": "#keywords" - } - ] - }, - "tuple_signature": { - "patterns": [ - { - "match": "(([?[:alpha:]0-9'`^._ ]+))+", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "begin": "(\\()", - "end": "(\\))", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "(([?[:alpha:]0-9'`^._ ]+))+", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#tuple_signature" - } - ] - }, - { - "include": "#keywords" - } - ] - }, - "anonymous_functions": { - "patterns": [ - { - "name": "function.anonymous", - "begin": "\\b(fun)\\b", - "end": "(->)", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.arrow.fsharp" - } - }, - "patterns": [ - { - "include": "#comments" - }, - { - "begin": "(\\()", - "end": "\\s*(?=(->))", - "beginCaptures": { - "1": { - "name": "keyword.symbol.arrow.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.arrow.fsharp" - } - }, - "patterns": [ - { - "include": "#member_declaration" - } - ] - }, - { - "include": "#variables" - } - ] - } - ] - }, - "attributes": { - "patterns": [ - { - "name": "support.function.attribute.fsharp", - "begin": "\\[\\<", - "end": "\\>\\]|\\]", - "patterns": [ - { - "include": "$self" - } - ] - } - ] - }, - "comments": { - "patterns": [ - { - "name": "comment.literate.command.fsharp", - "match": "(\\(\\*{3}.*\\*{3}\\))", - "beginCaptures": { - "1": { - "name": "comment.block.fsharp" - } - } - }, - { - "name": "comment.block.markdown.fsharp", - "begin": "^\\s*(\\(\\*\\*(?!\\)))((?!\\*\\)).)*$", - "while": "^(?!\\s*(\\*)+\\)\\s*$)", - "beginCaptures": { - "1": { - "name": "comment.block.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "comment.block.fsharp" - } - }, - "patterns": [ - { - "include": "text.html.markdown" - } - ] - }, - { - "name": "comment.block.fsharp", - "begin": "(\\(\\*(?!\\)))", - "end": "(\\*+\\))", - "beginCaptures": { - "1": { - "name": "comment.block.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "comment.block.fsharp" - } - }, - "patterns": [ - { - "comments": "Capture // when inside of (* *) like that the rule which capture comments starting by // is not trigger. See https://github.com/ionide/ionide-fsgrammar/issues/155", - "name": "fast-capture.comment.line.double-slash.fsharp", - "match": "//" - }, - { - "include": "#comments" - } - ] - }, - { - "name": "comment.block.markdown.fsharp.end", - "match": "((?\\])?\\s*([_[:alpha:]0-9,\\._`\\s]+)(:)", - "end": "\\s*(with)\\b|=|$", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "keyword.fsharp" - }, - "3": { - "name": "support.function.attribute.fsharp" - }, - "5": { - "name": "keyword.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "patterns": [ - { - "include": "#comments" - }, - { - "include": "#common_declaration" - }, - { - "match": "(\\?{0,1})([[:alpha:]0-9'`^._ ]+)\\s*(:)((?!with\\b)\\b([\\w0-9'`^._ ]+)){0,1}", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "variable.parameter.fsharp" - }, - "3": { - "name": "keyword.symbol.fsharp" - }, - "4": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "match": "(?!with|get|set\\b)\\b([\\w0-9'`^._]+)", - "comments": "Here we need the \\w modifier in order to check that the words isn't blacklisted", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#keywords" - } - ] - }, - "common_binding_definition": { - "patterns": [ - { - "include": "#comments" - }, - { - "include": "#attributes" - }, - { - "comments": "SRTP syntax support", - "begin": "(:)\\s*(\\()\\s*(static member|member)", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "keyword.symbol.fsharp" - }, - "3": { - "name": "keyword.fsharp" - } - }, - "end": "(\\))\\s*((?=,)|(?=\\=))", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "(\\^[[:alpha:]0-9'._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#variables" - }, - { - "include": "#keywords" - } - ] - }, - { - "begin": "(:)\\s*(\\()", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "keyword.symbol.fsharp" - } - }, - "end": "(\\)\\s*(([?[:alpha:]0-9'`^._ ]*)))", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "entity.name.type.fsharp" - } - }, - "patterns": [ - { - "include": "#tuple_signature" - } - ] - }, - { - "begin": "(:)\\s*(\\^[[:alpha:]0-9'._]+)\\s*(when)", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "entity.name.type.fsharp" - }, - "3": { - "name": "keyword.fsharp" - } - }, - "end": "(?=:)", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "name": "keyword.fsharp", - "match": "\\b(and|when|or)\\b" - }, - { - "comment": "Because we first capture the keywords, we can capture what looks like a word and assume it's an entity definition", - "match": "([[:alpha:]0-9'^._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "name": "keyword.symbol.fsharp", - "match": "(\\(|\\))" - } - ] - }, - { - "match": "(:)\\s*([?[:alpha:]0-9'`^._ ]+)", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "match": "(->)\\s*(\\()?\\s*([?[:alpha:]0-9'`^._ ]+)*", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "keyword.symbol.fsharp" - }, - "3": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "begin": "(\\*)\\s*(\\()", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "keyword.symbol.fsharp" - } - }, - "end": "(\\)\\s*(([?[:alpha:]0-9'`^._ ]+))+)", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "entity.name.type.fsharp" - } - }, - "patterns": [ - { - "include": "#tuple_signature" - } - ] - }, - { - "begin": "(\\*)(\\s*([?[:alpha:]0-9'`^._ ]+))*", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "entity.name.type.fsharp" - } - }, - "end": "(?==)|(?=\\))", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "include": "#tuple_signature" - } - ] - }, - { - "begin": "(<+(?![[:space:]]*\\)))", - "beginComment": "The group (?![[:space:]]*\\) is for protection against overload operator. static member (<)", - "end": "((?|\\))", - "endComment": "The group (? when using SRTP synthax", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "include": "#generic_declaration" - } - ] - }, - { - "include": "#anonymous_record_declaration" - }, - { - "begin": "({)", - "end": "(})", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "include": "#record_signature" - } - ] - }, - { - "include": "#definition" - }, - { - "include": "#variables" - }, - { - "include": "#keywords" - } - ] - }, - "definition": { - "patterns": [ - { - "name": "binding.fsharp", - "begin": "\\b(let mutable|static let mutable|static let|let inline|let|member val|static member inline|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", - "end": "\\s*(with\\b|=|\\n+=|(?<=\\=))", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "keyword.fsharp" - }, - "3": { - "name": "support.function.attribute.fsharp" - }, - "4": { - "name": "storage.modifier.fsharp" - }, - "5": { - "name": "variable.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "patterns": [ - { - "include": "#common_binding_definition" - } - ] - }, - { - "name": "binding.fsharp", - "begin": "\\b(use|use\\!|and|and!)\\s*(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", - "end": "\\s*(=)", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "patterns": [ - { - "include": "#common_binding_definition" - } - ] - }, - { - "name": "binding.fsharp", - "begin": "(?<=with|and)\\s*\\b((get|set)\\s*(?=\\())(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", - "end": "\\s*(=|\\n+=|(?<=\\=))", - "beginCaptures": { - "4": { - "name": "variable.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "patterns": [ - { - "include": "#common_binding_definition" - } - ] - }, - { - "name": "binding.fsharp", - "begin": "\\b(static val mutable|val mutable|val)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9,\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9,\\._`\\s]+|(?<=,)\\s)*)?", - "end": "\\n$", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "keyword.fsharp" - }, - "3": { - "name": "support.function.attribute.fsharp" - }, - "4": { - "name": "storage.modifier.fsharp" - }, - "5": { - "name": "variable.fsharp" - } - }, - "patterns": [ - { - "include": "#common_binding_definition" - } - ] - }, - { - "name": "binding.fsharp", - "begin": "\\b(new)\\b\\s+(\\()", - "end": "(\\))", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "keyword.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "patterns": [ - { - "include": "#common_binding_definition" - } - ] - } - ] - }, - "du_declaration": { - "patterns": [ - { - "name": "du_declaration.fsharp", - "begin": "\\b(of)\\b", - "end": "$|(\\|)", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "include": "#comments" - }, - { - "match": "([[:alpha:]0-9'`<>^._]+|``[[:alpha:]0-9' <>^._]+``)\\s*(:)\\s*([[:alpha:]0-9'`<>^._]+|``[[:alpha:]0-9' <>^._]+``)", - "captures": { - "1": { - "name": "variable.parameter.fsharp" - }, - "2": { - "name": "keyword.symbol.fsharp" - }, - "3": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "match": "(``([[:alpha:]0-9'^._ ]+)``|[[:alpha:]0-9'`^._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#anonymous_record_declaration" - }, - { - "include": "#keywords" - } - ] - } - ] - }, - "keywords": { - "patterns": [ - { - "name": "storage.modifier", - "match": "\\b(private|public|internal)\\b" - }, - { - "name": "keyword.fsharp", - "match": "\\b(private|to|public|internal|function|class|exception|delegate|of|as|begin|end|inherit|let!|interface|abstract|enum|member|and|when|or|use|use\\!|struct|mutable|assert|base|done|downcast|downto|extern|fixed|global|lazy|upcast|not)(?!')\\b" - }, - { - "name": "keyword.control", - "match": "\\b(match|yield|yield!|with|if|then|else|elif|for|in|return!|return|try|finally|while|do)(?!')\\b" - }, - { - "name": "keyword.symbol.new", - "match": "\\b(new)\\b" - }, - { - "name": "keyword.symbol.fsharp", - "match": "(&&&|\\|\\|\\||\\^\\^\\^|~~~|<<<|>>>|\\|>|\\->|\\<\\-|:>|:\\?>|:|\\[|\\]|\\;|<>|=|@|\\|\\||&&|{|}|\\||_|\\.\\.|\\,|\\+|\\-|\\*|\\/|\\^|\\!|\\>|\\>\\=|\\>\\>|\\<|\\<\\=|\\(|\\)|\\<\\<)" - } - ] - }, - "modules": { - "patterns": [ - { - "name": "entity.name.section.fsharp", - "begin": "\\b(namespace global)|\\b(namespace|module)\\s*(public|internal|private|rec)?\\s+([[:alpha:]][[:alpha:]0-9'_. ]*)", - "end": "(\\s?=|\\s|$)", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "keyword.fsharp" - }, - "3": { - "name": "storage.modifier.fsharp" - }, - "4": { - "name": "entity.name.section.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "name": "entity.name.section.fsharp", - "match": "(\\.)([A-Z][[:alpha:]0-9'_]*)", - "captures": { - "1": { - "name": "punctuation.separator.namespace-reference.fsharp" - }, - "2": { - "name": "entity.name.section.fsharp" - } - } - } - ] - }, - { - "name": "namespace.open.fsharp", - "begin": "\\b(open type|open)\\s+([[:alpha:]][[:alpha:]0-9'_]*)(?=(\\.[A-Z][[:alpha:]0-9_]*)*)", - "end": "(\\s|$)", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "entity.name.section.fsharp" - } - }, - "patterns": [ - { - "name": "entity.name.section.fsharp", - "match": "(\\.)([[:alpha:]][[:alpha:]0-9'_]*)", - "captures": { - "1": { - "name": "punctuation.separator.namespace-reference.fsharp" - }, - "2": { - "name": "entity.name.section.fsharp" - } - } - }, - { - "include": "#comments" - } - ] - }, - { - "name": "namespace.alias.fsharp", - "begin": "^\\s*(module)\\s+([A-Z][[:alpha:]0-9'_]*)\\s*(=)\\s*([A-Z][[:alpha:]0-9'_]*)", - "end": "(\\s|$)", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "entity.name.type.namespace.fsharp" - }, - "3": { - "name": "punctuation.separator.namespace-definition.fsharp" - }, - "4": { - "name": "entity.name.section.fsharp" - } - }, - "patterns": [ - { - "name": "entity.name.section.fsharp", - "match": "(\\.)([A-Z][[:alpha:]0-9'_]*)", - "captures": { - "1": { - "name": "punctuation.separator.namespace-reference.fsharp" - }, - "2": { - "name": "entity.name.section.fsharp" - } - } - } - ] - } - ] - }, - "strings": { - "patterns": [ - { - "name": "string.quoted.literal.fsharp", - "begin": "(?=[^\\\\])(@\")", - "end": "(\")(?!\")", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.fsharp" - } - }, - "patterns": [ - { - "name": "constant.character.string.escape.fsharp", - "match": "\"(\")" - } - ] - }, - { - "name": "string.quoted.triple.fsharp", - "begin": "(?=[^\\\\])(\"\"\")", - "end": "(\"\"\")", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.fsharp" - } - }, - "patterns": [ - { - "include": "#string_formatter" - } - ] - }, - { - "name": "string.quoted.double.fsharp", - "begin": "(?=[^\\\\])(\")", - "end": "(\")", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.fsharp" - } - }, - "patterns": [ - { - "name": "punctuation.separator.string.ignore-eol.fsharp", - "match": "\\\\$[ \\t]*" - }, - { - "name": "constant.character.string.escape.fsharp", - "match": "\\\\(['\"\\\\abfnrtv]|([01][0-9][0-9]|2[0-4][0-9]|25[0-5])|(x[0-9a-fA-F]{2})|(u[0-9a-fA-F]{4})|(U00(0[0-9a-fA-F]|10)[0-9a-fA-F]{4}))" - }, - { - "name": "invalid.illegal.character.string.fsharp", - "match": "\\\\(([0-9]{1,3})|(x[^\\s]{0,2})|(u[^\\s]{0,4})|(U[^\\s]{0,8})|[^\\s])" - }, - { - "include": "#string_formatter" - } - ] - } - ] - }, - "string_formatter": { - "patterns": [ - { - "name": "entity.name.type.format.specifier.fsharp", - "match": "(%0?-?(\\d+)?((a|t)|(\\.\\d+)?(f|F|e|E|g|G|M)|(b|c|s|d|i|x|X|o|u)|(s|b|O)|(\\+?A)))", - "captures": { - "1": { - "name": "keyword.format.specifier.fsharp" - } - } - } - ] - }, - "variables": { - "patterns": [ - { - "name": "constant.language.unit.fsharp", - "match": "\\(\\)" - }, - { - "match": "(\\?{0,1})(``[[:alpha:]0-9'`^:,._ ]+``|(?!private\\b)\\b[\\w[:alpha:]0-9'`<>^._ ]+)", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "variable.parameter.fsharp" - } - } - } - ] - }, - "common_declaration": { - "patterns": [ - { - "begin": "\\s*(->)\\s*([[:alpha:]0-9'`^._ ]+)(<)", - "end": "(>)", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "entity.name.type.fsharp" - }, - "3": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "([[:alpha:]0-9'`^._ ]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#keywords" - } - ] - }, - { - "match": "\\s*(->)\\s*(?!with|get|set\\b)\\b([\\w0-9'`^._]+)", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#anonymous_record_declaration" - }, - { - "begin": "(\\?{0,1})([[:alpha:]0-9'`^._ ]+)\\s*(:)(\\s*([?[:alpha:]0-9'`^._ ]+)(<))", - "end": "(>)", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "variable.parameter.fsharp" - }, - "3": { - "name": "keyword.symbol.fsharp" - }, - "4": { - "name": "keyword.symbol.fsharp" - }, - "5": { - "name": "entity.name.type.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "([[:alpha:]0-9'`^._ ]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#keywords" - } - ] - } - ] - }, - "member_declaration": { - "patterns": [ - { - "include": "#comments" - }, - { - "include": "#common_declaration" - }, - { - "comments": "SRTP syntax support", - "begin": "(:)\\s*(\\()\\s*(static member|member)", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "keyword.symbol.fsharp" - }, - "3": { - "name": "keyword.fsharp" - } - }, - "end": "(\\))\\s*((?=,)|(?=\\=))", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "begin": "(\\()", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "end": "(\\))", - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "include": "#member_declaration" - } - ] - }, - { - "match": "(\\^[[:alpha:]0-9'._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#variables" - }, - { - "include": "#keywords" - } - ] - }, - { - "match": "(\\^[[:alpha:]0-9'._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "name": "keyword.fsharp", - "match": "\\b(and|when|or)\\b" - }, - { - "name": "keyword.symbol.fsharp", - "match": "(\\(|\\))" - }, - { - "match": "(\\?{0,1})([[:alpha:]0-9'`^._]+|``[[:alpha:]0-9'`^:,._ ]+``)\\s*(:{0,1})(\\s*([?[:alpha:]0-9'`<>._ ]+)){0,1}", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "variable.parameter.fsharp" - }, - "3": { - "name": "keyword.symbol.fsharp" - }, - "4": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#keywords" - } - ] - }, - "double_tick": { - "patterns": [ - { - "name": "variable.other.binding.fsharp", - "match": "(``)([^`]*)(``)", - "captures": { - "1": { - "name": "string.quoted.single.fsharp" - }, - "2": { - "name": "variable.other.binding.fsharp" - }, - "3": { - "name": "string.quoted.single.fsharp" - } - } - } - ] - }, - "records": { - "patterns": [ - { - "name": "record.fsharp", - "begin": "\\b(type)[\\s]+(private|internal|public)?\\s*", - "end": "\\s*((with)|((as)\\s+([[:alpha:]0-9']+))|(=)|[\\n=]|(\\(\\)))", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - }, - "2": { - "name": "storage.modifier.fsharp" - } - }, - "endCaptures": { - "2": { - "name": "keyword.fsharp" - }, - "3": { - "name": "keyword.fsharp" - }, - "4": { - "name": "keyword.fsharp" - }, - "5": { - "name": "variable.parameter.fsharp" - }, - "6": { - "name": "keyword.symbol.fsharp" - }, - "7": { - "name": "constant.language.unit.fsharp" - } - }, - "patterns": [ - { - "include": "#comments" - }, - { - "include": "#attributes" - }, - { - "match": "([[:alpha:]0-9'^._]+|``[[:alpha:]0-9'`^:,._ ]+``)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "begin": "(<)", - "end": "((?)", - "beginCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.fsharp" - } - }, - "patterns": [ - { - "match": "(('|\\^)``[[:alpha:]0-9`^:,._ ]+``|('|\\^)[[:alpha:]0-9`^:._]+)", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "name": "keyword.fsharp", - "match": "\\b(interface|with|abstract|and|when|or|not|struct|equality|comparison|unmanaged|delegate|enum)\\b" - }, - { - "begin": "(\\()", - "end": "(\\))", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "match": "(static member|member|new)", - "captures": { - "1": { - "name": "keyword.fsharp" - } - } - }, - { - "include": "#common_binding_definition" - } - ] - }, - { - "match": "([\\w0-9'`^._]+)", - "comments": "Here we need the \\w modifier in order to check that the words isn't blacklisted", - "captures": { - "1": { - "name": "entity.name.type.fsharp" - } - } - }, - { - "include": "#keywords" - } - ] - }, - { - "match": "\\s*(private|internal|public)", - "captures": { - "1": { - "name": "keyword.symbol.fsharp" - }, - "2": { - "name": "storage.modifier.fsharp" - } - } - }, - { - "begin": "(\\()", - "end": "\\s*(?=(=)|[\\n=]|(\\(\\))|(as))", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "patterns": [ - { - "include": "#member_declaration" - } - ] - }, - { - "include": "#keywords" - } - ] - } - ] - }, - "record_declaration": { - "patterns": [ - { - "begin": "(\\{)", - "beginCaptures": { - "1": { - "name": "keyword.symbol.fsharp" - } - }, - "end": "(?<=\\})", - "patterns": [ - { - "include": "#comments" - }, - { - "begin": "(((mutable)\\s[[:alpha:]]+)|[[:alpha:]0-9'`<>^._]*)\\s*((?", + "", + "Permission is hereby granted, free of charge, to any person obtaining", + "a copy of this software and associated documentation files (the\"", + "Software\"), to deal in the Software without restriction, including", + "without limitation the rights to use, copy, modify, merge, publish,", + "distribute, sublicense, and/or sell copies of the Software, and to", + "permit persons to whom the Software is furnished to do so, subject to", + "the following conditions:", + "", + "The above copyright notice and this permission notice shall be", + "included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,", + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", + "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND", + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE", + "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION", + "OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION", + "WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ], + "license": "MIT", + "version": "1.0.0", + "description": "The original JSON grammars were derived from https://github.com/microsoft/vscode/blob/e95c74c4c7af876e79ec58df262464467c06df28/extensions/git-base/syntaxes/git-commit.tmLanguage.json. That file was originally copied from https://github.com/textmate/git.tmbundle." } ], "version": 1 diff --git a/extensions/git-base/package.json b/extensions/git-base/package.json index 07e95e3a1a..6a2ea8903a 100644 --- a/extensions/git-base/package.json +++ b/extensions/git-base/package.json @@ -6,9 +6,9 @@ "publisher": "vscode", "license": "MIT", "engines": { - "vscode": "0.10.x" + "vscode": "0.10.0" }, - "categories": [ + "categories": [ "Other" ], "activationEvents": [ @@ -66,6 +66,9 @@ "filenames": [ "git-rebase-todo" ], + "filenamePatterns": [ + "**/rebase-merge/done" + ], "configuration": "./languages/git-rebase.language-configuration.json" }, { @@ -99,9 +102,6 @@ } ] }, - "dependencies": { - "vscode-nls": "^5.0.0" - }, "devDependencies": { "@types/node": "16.x" }, diff --git a/extensions/git-base/src/api/api1.ts b/extensions/git-base/src/api/api1.ts index 4412101aa6..c10bcd047b 100644 --- a/extensions/git-base/src/api/api1.ts +++ b/extensions/git-base/src/api/api1.ts @@ -5,9 +5,9 @@ import { Disposable, commands } from 'vscode'; import { Model } from '../model'; -import { pickRemoteSource } from '../remoteSource'; +import { getRemoteSourceActions, pickRemoteSource } from '../remoteSource'; import { GitBaseExtensionImpl } from './extension'; -import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceProvider } from './git-base'; +import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction, RemoteSourceProvider } from './git-base'; export class ApiImpl implements API { @@ -17,6 +17,10 @@ export class ApiImpl implements API { return pickRemoteSource(this._model, options as any); } + getRemoteSourceActions(url: string): Promise { + return getRemoteSourceActions(this._model, url); + } + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable { return this._model.registerRemoteSourceProvider(provider); } diff --git a/extensions/git-base/src/api/git-base.d.ts b/extensions/git-base/src/api/git-base.d.ts index b003b3dfc1..74f3e7ff55 100644 --- a/extensions/git-base/src/api/git-base.d.ts +++ b/extensions/git-base/src/api/git-base.d.ts @@ -44,6 +44,15 @@ export interface PickRemoteSourceResult { readonly branch?: string; } +export interface RemoteSourceAction { + readonly label: string; + /** + * Codicon name + */ + readonly icon: string; + run(branch: string): void; +} + export interface RemoteSource { readonly name: string; readonly description?: string; @@ -70,6 +79,7 @@ export interface RemoteSourceProvider { readonly supportsQuery?: boolean; getBranches?(url: string): ProviderResult; + getRemoteSourceActions?(url: string): ProviderResult; getRecentRemoteSources?(query?: string): ProviderResult; getRemoteSources(query?: string): ProviderResult; } diff --git a/extensions/git-base/src/remoteSource.ts b/extensions/git-base/src/remoteSource.ts index 9fd4bd61a5..d7872ac97d 100644 --- a/extensions/git-base/src/remoteSource.ts +++ b/extensions/git-base/src/remoteSource.ts @@ -3,14 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { QuickPickItem, window, QuickPick, QuickPickItemKind } from 'vscode'; -import * as nls from 'vscode-nls'; -import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult } from './api/git-base'; +import { QuickPickItem, window, QuickPick, QuickPickItemKind, l10n } from 'vscode'; +import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceAction } from './api/git-base'; import { Model } from './model'; import { throttle, debounce } from './decorators'; -const localize = nls.loadMessageBundle(); - async function getQuickPickResult(quickpick: QuickPick): Promise { const result = await new Promise(c => { quickpick.onDidAccept(() => c(quickpick.selectedItems[0])); @@ -33,10 +30,10 @@ class RemoteSourceProviderQuickPick { this.quickpick = window.createQuickPick(); this.quickpick.ignoreFocusOut = true; if (this.provider.supportsQuery) { - this.quickpick.placeholder = this.provider.placeholder ?? localize('type to search', "Repository name (type to search)"); + this.quickpick.placeholder = this.provider.placeholder ?? l10n.t('Repository name (type to search)'); this.quickpick.onDidChangeValue(this.onDidChangeValue, this); } else { - this.quickpick.placeholder = this.provider.placeholder ?? localize('type to filter', "Repository name"); + this.quickpick.placeholder = this.provider.placeholder ?? l10n.t('Repository name'); } } } @@ -57,7 +54,7 @@ class RemoteSourceProviderQuickPick { if (remoteSources.length === 0) { this.quickpick!.items = [{ - label: localize('none found', "No remote repositories found."), + label: l10n.t('No remote repositories found.'), alwaysShow: true }]; } else { @@ -70,7 +67,7 @@ class RemoteSourceProviderQuickPick { })); } } catch (err) { - this.quickpick!.items = [{ label: localize('error', "{0} Error: {1}", '$(error)', err.message), alwaysShow: true }]; + this.quickpick!.items = [{ label: l10n.t('{0} Error: {1}', '$(error)', err.message), alwaysShow: true }]; console.error(err); } finally { this.quickpick!.busy = false; @@ -84,11 +81,24 @@ class RemoteSourceProviderQuickPick { } } +export async function getRemoteSourceActions(model: Model, url: string): Promise { + const providers = model.getRemoteProviders(); + + const remoteSourceActions = []; + for (const provider of providers) { + const providerActions = await provider.getRemoteSourceActions?.(url); + if (providerActions?.length) { + remoteSourceActions.push(...providerActions); + } + } + + return remoteSourceActions; +} + 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; quickpick.title = options.title; if (options.providerName) { @@ -118,19 +128,19 @@ export async function pickRemoteSource(model: Model, options: PickRemoteSourceOp } const items = [ - { kind: QuickPickItemKind.Separator, label: localize('remote sources', 'remote sources') }, + { kind: QuickPickItemKind.Separator, label: l10n.t('remote sources') }, ...remoteProviders, - { kind: QuickPickItemKind.Separator, label: localize('recently opened', 'recently opened') }, + { kind: QuickPickItemKind.Separator, label: l10n.t('recently opened') }, ...recentSources.sort((a, b) => b.timestamp - a.timestamp) ]; quickpick.placeholder = options.placeholder ?? (remoteProviders.length === 0 - ? localize('provide url', "Provide repository URL") - : localize('provide url or pick', "Provide repository URL or pick a repository source.")); + ? l10n.t('Provide repository URL') + : l10n.t('Provide repository URL or pick a repository source.')); const updatePicks = (value?: string) => { if (value) { - const label = (typeof options.urlLabel === 'string' ? options.urlLabel : options.urlLabel?.(value)) ?? localize('url', "URL"); + const label = (typeof options.urlLabel === 'string' ? options.urlLabel : options.urlLabel?.(value)) ?? l10n.t('URL'); quickpick.items = [{ label: label, description: value, @@ -170,7 +180,7 @@ async function pickProviderSource(provider: RemoteSourceProvider, options: PickR 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.") }); + url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: l10n.t('Choose a URL to clone from.') }); } } @@ -189,7 +199,7 @@ async function pickProviderSource(provider: RemoteSourceProvider, options: PickR } const branch = await window.showQuickPick(branches, { - placeHolder: localize('branch name', "Branch name") + placeHolder: l10n.t('Branch name') }); if (!branch) { diff --git a/extensions/git-base/syntaxes/git-commit.tmLanguage.json b/extensions/git-base/syntaxes/git-commit.tmLanguage.json index c2b8d628ab..e9252a5141 100644 --- a/extensions/git-base/syntaxes/git-commit.tmLanguage.json +++ b/extensions/git-base/syntaxes/git-commit.tmLanguage.json @@ -1,141 +1,90 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/textmate/git.tmbundle/blob/master/Syntaxes/Git%20Commit%20Message.tmLanguage", + "This file has been converted from https://github.com/walles/git-commit-message-plus/blob/master/syntaxes/git-commit.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/textmate/git.tmbundle/commit/93897a78c6e52bef13dadc0d4091d203c5facb40", + "version": "https://github.com/walles/git-commit-message-plus/commit/35a079dea5a91b087021b40c01a6bb4eb0337a87", "name": "Git Commit Message", "scopeName": "text.git-commit", "patterns": [ { - "begin": "\\A(?!# Please enter the commit message)", - "end": "^(?=# Please enter the commit message)", - "name": "meta.scope.message.git-commit", + "comment": "diff presented at the end of the commit message when using commit -v.", + "name": "meta.embedded.diff.git-commit", + "contentName": "source.diff", + "begin": "(?=^diff\\ \\-\\-git)", + "end": "\\z", "patterns": [ { - "begin": "\\A(?=#)", - "end": "^(?!#)", - "patterns": [ - { - "include": "#comment" - } - ] - }, - { - "begin": "^(?!# Please enter the commit message)", - "end": "^(?=# Please enter the commit message)", - "patterns": [ - { - "begin": "\\G", - "end": "^(?!\\G)", - "name": "meta.scope.subject.git-commit", - "patterns": [ - { - "captures": { - "1": { - "name": "keyword.other.$2.git-commit" - } - }, - "match": "\\G((fixup|squash)!)\\s*" - }, - { - "match": ".{73,}$", - "name": "invalid.illegal.line-too-long.git-commit" - }, - { - "match": ".{51,}$", - "name": "invalid.deprecated.line-too-long.git-commit" - } - ] - }, - { - "begin": "^(?!# Please enter the commit message)", - "end": "^(?=# Please enter the commit message)", - "patterns": [ - { - "include": "#comment" - } - ] - } - ] + "include": "source.diff" } ] }, { - "begin": "^(?=# Please enter the commit message)", - "end": "\\z", - "name": "meta.scope.metadata.git-commit", + "comment": "User supplied message", + "name": "meta.scope.message.git-commit", + "begin": "^(?!#)", + "end": "^(?=#)", "patterns": [ { - "include": "#metadata" + "comment": "Mark > 50 lines as deprecated, > 72 as illegal", + "name": "meta.scope.subject.git-commit", + "match": "\\G.{0,50}(.{0,22}(.*))$", + "captures": { + "1": { + "name": "invalid.deprecated.line-too-long.git-commit" + }, + "2": { + "name": "invalid.illegal.line-too-long.git-commit" + } + } } ] - } - ], - "repository": { - "comment": { - "begin": "^(#)", - "captures": { - "1": { - "name": "punctuation.definition.comment.git-commit" - } - }, - "end": "\\n", - "name": "comment.line.number-sign.git-commit" }, - "metadata": { + { + "comment": "Git supplied metadata in a number of lines starting with #", + "name": "meta.scope.metadata.git-commit", + "begin": "^(?=#)", + "contentName": "comment.line.number-sign.git-commit", + "end": "^(?!#)", "patterns": [ { - "begin": "(?=^# Changes to be committed:)", - "end": "(?!\\G)((?=^# \\w)|(?!^#))", - "patterns": [ - { - "begin": "(^[ \\t]+)?(?=#)", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.git-commit" - } - }, - "contentName": "comment.line.number-sign.git-commit", - "end": "(?!\\G)^", - "patterns": [ - { - "match": "\\G#", - "name": "punctuation.definition.comment.git-commit" - }, - { - "match": "((modified|renamed):.*)$\\n?", - "name": "markup.changed.git-commit" - }, - { - "match": "(new file:.*)$\\n?", - "name": "markup.inserted.git-commit" - }, - { - "match": "(deleted:.*)$\\n?", - "name": "markup.deleted.git-commit" - } - ] + "match": "^#\\t((modified|renamed):.*)$", + "captures": { + "1": { + "name": "markup.changed.git-commit" } - ] + } }, { - "include": "#comment" + "match": "^#\\t(new file:.*)$", + "captures": { + "1": { + "name": "markup.inserted.git-commit" + } + } }, { - "begin": "(?=diff\\ \\-\\-git)", - "comment": "diff presented at the end of the commit message when using commit -v.", - "contentName": "source.diff", - "end": "\\z", - "name": "meta.embedded.diff.git-commit", - "patterns": [ - { - "include": "source.diff" + "match": "^#\\t(deleted.*)$", + "captures": { + "1": { + "name": "markup.deleted.git-commit" } - ] + } + }, + { + "comment": "Fallback for non-English git commit template", + "match": "^#\\t([^:]+): *(.*)$", + "captures": { + "1": { + "name": "keyword.other.file-type.git-commit" + }, + "2": { + "name": "string.unquoted.filename.git-commit" + } + } } ] } - } + ] } \ No newline at end of file diff --git a/extensions/git-base/yarn.lock b/extensions/git-base/yarn.lock index 8fb6777123..cad6a8475b 100644 --- a/extensions/git-base/yarn.lock +++ b/extensions/git-base/yarn.lock @@ -6,8 +6,3 @@ version "16.11.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.21.tgz#474d7589a30afcf5291f59bd49cca9ad171ffde4" integrity sha512-Pf8M1XD9i1ksZEcCP8vuSNwooJ/bZapNmIzpmsMaL+jMI+8mEYU3PKvs+xDNuQcJWF/x24WzY4qxLtB0zNow9A== - -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== diff --git a/extensions/git/package.json b/extensions/git/package.json index 4933625182..5e00d21546 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -11,19 +11,25 @@ "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "enabledApiProposals": [ "diffCommand", - "contribMergeEditorToolbar", + "contribEditorContentMenu", + "contribEditSessions", + "canonicalUriProvider", "contribViewsWelcome", + "editSessionIdentityProvider", + "quickDiffProvider", "scmActionButton", "scmSelectedProvider", "scmValidation", "tabInputTextMerge", - "timeline" + "timeline", + "contribMergeEditorMenus" ], "categories": [ "Other" ], "activationEvents": [ "*", + "onEditSession:file", "onFileSystem:git", "onFileSystem:git-show" ], @@ -47,41 +53,49 @@ "contributes": { "commands": [ { - "command": "git.setLogLevel", - "title": "%command.setLogLevel%", - "category": "Git" + "command": "git.continueInLocalClone", + "title": "%command.continueInLocalClone%", + "category": "Git", + "icon": "$(repo-clone)", + "enablement": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && remoteName" }, { "command": "git.clone", "title": "%command.clone%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.cloneRecursive", "title": "%command.cloneRecursive%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.init", "title": "%command.init%", "category": "Git", - "icon": "$(add)" + "icon": "$(add)", + "enablement": "!operationInProgress" }, { "command": "git.openRepository", "title": "%command.openRepository%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.close", "title": "%command.close%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.refresh", "title": "%command.refresh%", "category": "Git", - "icon": "$(refresh)" + "icon": "$(refresh)", + "enablement": "!operationInProgress" }, { "command": "git.openChange", @@ -115,198 +129,215 @@ "command": "git.stage", "title": "%command.stage%", "category": "Git", - "icon": "$(add)" + "icon": "$(add)", + "enablement": "!operationInProgress" }, { "command": "git.stageAll", "title": "%command.stageAll%", "category": "Git", - "icon": "$(add)" + "icon": "$(add)", + "enablement": "!operationInProgress" }, { "command": "git.stageAllTracked", "title": "%command.stageAllTracked%", "category": "Git", - "icon": "$(add)" + "icon": "$(add)", + "enablement": "!operationInProgress" }, { "command": "git.stageAllUntracked", "title": "%command.stageAllUntracked%", "category": "Git", - "icon": "$(add)" + "icon": "$(add)", + "enablement": "!operationInProgress" }, { "command": "git.stageAllMerge", "title": "%command.stageAllMerge%", "category": "Git", - "icon": "$(add)" + "icon": "$(add)", + "enablement": "!operationInProgress" }, { "command": "git.stageSelectedRanges", "title": "%command.stageSelectedRanges%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.revertSelectedRanges", "title": "%command.revertSelectedRanges%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.stageChange", "title": "%command.stageChange%", "category": "Git", - "icon": "$(add)" + "icon": "$(add)", + "enablement": "!operationInProgress" }, { "command": "git.revertChange", "title": "%command.revertChange%", "category": "Git", - "icon": "$(discard)" + "icon": "$(discard)", + "enablement": "!operationInProgress" }, { "command": "git.unstage", "title": "%command.unstage%", "category": "Git", - "icon": "$(remove)" + "icon": "$(remove)", + "enablement": "!operationInProgress" }, { "command": "git.unstageAll", "title": "%command.unstageAll%", "category": "Git", - "icon": "$(remove)" + "icon": "$(remove)", + "enablement": "!operationInProgress" }, { "command": "git.unstageSelectedRanges", "title": "%command.unstageSelectedRanges%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.clean", "title": "%command.clean%", "category": "Git", - "icon": "$(discard)" + "icon": "$(discard)", + "enablement": "!operationInProgress" }, { "command": "git.cleanAll", "title": "%command.cleanAll%", "category": "Git", - "icon": "$(discard)" + "icon": "$(discard)", + "enablement": "!operationInProgress" }, { "command": "git.cleanAllTracked", "title": "%command.cleanAllTracked%", "category": "Git", - "icon": "$(discard)" + "icon": "$(discard)", + "enablement": "!operationInProgress" }, { "command": "git.cleanAllUntracked", "title": "%command.cleanAllUntracked%", "category": "Git", - "icon": "$(discard)" + "icon": "$(discard)", + "enablement": "!operationInProgress" }, { "command": "git.rename", "title": "%command.rename%", "category": "Git", - "icon": "$(discard)" + "icon": "$(discard)", + "enablement": "!operationInProgress" }, { "command": "git.commit", "title": "%command.commit%", "category": "Git", "icon": "$(check)", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitStaged", "title": "%command.commitStaged%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitEmpty", "title": "%command.commitEmpty%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitStagedSigned", "title": "%command.commitStagedSigned%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitStagedAmend", "title": "%command.commitStagedAmend%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitAll", "title": "%command.commitAll%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitAllSigned", "title": "%command.commitAllSigned%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitAllAmend", "title": "%command.commitAllAmend%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitNoVerify", "title": "%command.commitNoVerify%", "category": "Git", "icon": "$(check)", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitStagedNoVerify", "title": "%command.commitStagedNoVerify%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitEmptyNoVerify", "title": "%command.commitEmptyNoVerify%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitStagedSignedNoVerify", "title": "%command.commitStagedSignedNoVerify%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitStagedAmendNoVerify", "title": "%command.commitStagedAmendNoVerify%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitAllNoVerify", "title": "%command.commitAllNoVerify%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitAllSignedNoVerify", "title": "%command.commitAllSignedNoVerify%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitAllAmendNoVerify", "title": "%command.commitAllAmendNoVerify%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.commitMessageAccept", @@ -323,158 +354,200 @@ { "command": "git.restoreCommitTemplate", "title": "%command.restoreCommitTemplate%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.undoCommit", "title": "%command.undoCommit%", "category": "Git", - "enablement": "!commitInProgress" + "enablement": "!operationInProgress" }, { "command": "git.checkout", "title": "%command.checkout%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.checkoutDetached", "title": "%command.checkoutDetached%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.branch", "title": "%command.branch%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.branchFrom", "title": "%command.branchFrom%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.deleteBranch", "title": "%command.deleteBranch%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.renameBranch", "title": "%command.renameBranch%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.merge", "title": "%command.merge%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.mergeAbort", + "title": "%command.mergeAbort%", + "category": "Git", + "enablement": "gitMergeInProgress" }, { "command": "git.rebase", "title": "%command.rebase%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.createTag", "title": "%command.createTag%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.deleteTag", "title": "%command.deleteTag%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.deleteRemoteTag", + "title": "%command.deleteRemoteTag%", + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.fetch", "title": "%command.fetch%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.fetchPrune", "title": "%command.fetchPrune%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.fetchAll", "title": "%command.fetchAll%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pull", "title": "%command.pull%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pullRebase", "title": "%command.pullRebase%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pullFrom", "title": "%command.pullFrom%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.push", "title": "%command.push%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pushForce", "title": "%command.pushForce%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pushTo", "title": "%command.pushTo%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pushToForce", "title": "%command.pushToForce%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pushTags", "title": "%command.pushTags%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pushWithTags", "title": "%command.pushFollowTags%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.pushWithTagsForce", "title": "%command.pushFollowTagsForce%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.cherryPick", "title": "%command.cherryPick%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.addRemote", "title": "%command.addRemote%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.removeRemote", "title": "%command.removeRemote%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.sync", "title": "%command.sync%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.syncRebase", "title": "%command.syncRebase%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.publish", "title": "%command.publish%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.showOutput", @@ -484,7 +557,8 @@ { "command": "git.ignore", "title": "%command.ignore%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.revealInExplorer", @@ -509,42 +583,56 @@ { "command": "git.stashIncludeUntracked", "title": "%command.stashIncludeUntracked%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.stash", "title": "%command.stash%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.stashStaged", + "title": "%command.stashStaged%", + "category": "Git", + "enablement": "!operationInProgress && gitVersion2.35" }, { "command": "git.stashPop", "title": "%command.stashPop%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.stashPopLatest", "title": "%command.stashPopLatest%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.stashApply", "title": "%command.stashApply%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.stashApplyLatest", "title": "%command.stashApplyLatest%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.stashDrop", "title": "%command.stashDrop%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.stashDropAll", "title": "%command.stashDropAll%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.timeline.openDiff", @@ -575,12 +663,14 @@ { "command": "git.rebaseAbort", "title": "%command.rebaseAbort%", - "category": "Git" + "category": "Git", + "enablement": "gitRebaseInProgress" }, { "command": "git.closeAllDiffEditors", "title": "%command.closeAllDiffEditors%", - "category": "Git" + "category": "Git", + "enablement": "!operationInProgress" }, { "command": "git.api.getRepositories", @@ -602,6 +692,41 @@ "title": "%command.git.acceptMerge%", "category": "Git", "enablement": "isMergeEditor && mergeEditorResultUri in git.mergeChanges" + }, + { + "command": "git.openMergeEditor", + "title": "%command.git.openMergeEditor%", + "category": "Git" + }, + { + "command": "git.runGitMerge", + "title": "%command.git.runGitMerge%", + "category": "Git", + "enablement": "isMergeEditor" + }, + { + "command": "git.runGitMergeDiff3", + "title": "%command.git.runGitMergeDiff3%", + "category": "Git", + "enablement": "isMergeEditor" + }, + { + "command": "git.manageUnsafeRepositories", + "title": "%command.manageUnsafeRepositories%", + "category": "Git" + }, + { + "command": "git.openRepositoriesInParentFolders", + "title": "%command.openRepositoriesInParentFolders%", + "category": "Git" + } + ], + "continueEditSession": [ + { + "command": "git.continueInLocalClone", + "qualifiedName": "%command.continueInLocalClone.qualifiedName%", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && remoteName", + "remoteGroup": "remote_42_git_0_local@0" } ], "keybindings": [ @@ -627,8 +752,8 @@ "menus": { "commandPalette": [ { - "command": "git.setLogLevel", - "when": "config.git.enabled && !git.missing" + "command": "git.continueInLocalClone", + "when": "false" }, { "command": "git.clone", @@ -640,7 +765,7 @@ }, { "command": "git.init", - "when": "config.git.enabled && !git.missing" + "when": "config.git.enabled && !git.missing && remoteName != 'codespaces'" }, { "command": "git.openRepository", @@ -688,7 +813,7 @@ }, { "command": "git.stageSelectedRanges", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme =~ /^git$|^file$/" }, { "command": "git.stageChange", @@ -696,7 +821,7 @@ }, { "command": "git.revertSelectedRanges", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && resourceScheme =~ /^git$|^file$/" }, { "command": "git.revertChange", @@ -716,7 +841,7 @@ }, { "command": "git.unstageSelectedRanges", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.clean", @@ -878,6 +1003,10 @@ "command": "git.merge", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.mergeAbort", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && gitMergeInProgress" + }, { "command": "git.rebase", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -890,6 +1019,10 @@ "command": "git.deleteTag", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.deleteRemoteTag", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" + }, { "command": "git.fetch", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -966,6 +1099,10 @@ "command": "git.stash", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.stashStaged", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && gitVersion2.35" + }, { "command": "git.stashPop", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -1025,6 +1162,18 @@ { "command": "git.api.getRemoteSources", "when": "false" + }, + { + "command": "git.openMergeEditor", + "when": "false" + }, + { + "command": "git.manageUnsafeRepositories", + "when": "config.git.enabled && !git.missing && git.unsafeRepositoryCount != 0" + }, + { + "command": "git.openRepositoriesInParentFolders", + "when": "config.git.enabled && !git.missing && git.parentRepositoryCount != 0" } ], "scm/title": [ @@ -1506,12 +1655,12 @@ { "command": "git.commitMessageAccept", "group": "navigation", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit && commitInProgress" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit" }, { "command": "git.commitMessageDiscard", "group": "navigation", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit && commitInProgress" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit" }, { "command": "git.stageSelectedRanges", @@ -1533,23 +1682,28 @@ { "command": "git.stageSelectedRanges", "group": "2_git@1", - "when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.unstageSelectedRanges", "group": "2_git@2", - "when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" }, { "command": "git.revertSelectedRanges", "group": "2_git@3", - "when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" + "when": "isInDiffRightEditor && !isEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/" } ], - "merge/toolbar": [ + "editor/content": [ { "command": "git.acceptMerge", - "when": "isMergeEditor && mergeEditorBaseUri =~ /^(git|file):/ && mergeEditorResultUri in git.mergeChanges" + "when": "isMergeResultEditor && mergeEditorBaseUri =~ /^(git|file):/ && mergeEditorResultUri in git.mergeChanges" + }, + { + "command": "git.openMergeEditor", + "group": "navigation@-10", + "when": "config.git.enabled && !git.missing && !isInDiffEditor && !isMergeEditor && resource in git.mergeChanges" } ], "scm/change/title": [ @@ -1779,28 +1933,33 @@ "group": "stash@2" }, { - "command": "git.stashApplyLatest", + "command": "git.stashStaged", + "when": "gitVersion2.35", "group": "stash@3" }, { - "command": "git.stashApply", + "command": "git.stashApplyLatest", "group": "stash@4" }, { - "command": "git.stashPopLatest", + "command": "git.stashApply", "group": "stash@5" }, { - "command": "git.stashPop", + "command": "git.stashPopLatest", "group": "stash@6" }, { - "command": "git.stashDrop", + "command": "git.stashPop", "group": "stash@7" }, { - "command": "git.stashDropAll", + "command": "git.stashDrop", "group": "stash@8" + }, + { + "command": "git.stashDropAll", + "group": "stash@9" } ], "git.tags": [ @@ -1811,6 +1970,10 @@ { "command": "git.deleteTag", "group": "tags@2" + }, + { + "command": "git.deleteRemoteTag", + "group": "tags@3" } ] }, @@ -1911,6 +2074,12 @@ "markdownDescription": "%config.autofetchPeriod%", "default": 180 }, + "git.defaultBranchName": { + "type": "string", + "description": "%config.defaultBranchName%", + "default": "main", + "scope": "resource" + }, "git.branchPrefix": { "type": "string", "description": "%config.branchPrefix%", @@ -2176,6 +2345,12 @@ "scope": "resource", "default": "none" }, + "git.rememberPostCommitCommand": { + "type": "boolean", + "description": "%config.rememberPostCommitCommand%", + "scope": "resource", + "default": false + }, "git.openAfterClone": { "type": "string", "enum": [ @@ -2286,6 +2461,12 @@ "default": false, "description": "%config.rebaseWhenSync%" }, + "git.pullBeforeCheckout": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.pullBeforeCheckout%" + }, "git.fetchOnPull": { "type": "boolean", "scope": "resource", @@ -2496,35 +2677,44 @@ "default": [], "markdownDescription": "%config.commandsToLog%" }, - "git.logLevel": { - "type": "string", - "default": "Info", - "enum": [ - "Trace", - "Debug", - "Info", - "Warning", - "Error", - "Critical", - "Off" - ], - "enumDescriptions": [ - "%config.logLevel.trace%", - "%config.logLevel.debug%", - "%config.logLevel.info%", - "%config.logLevel.warn%", - "%config.logLevel.error%", - "%config.logLevel.critical%", - "%config.logLevel.off%" - ], - "markdownDescription": "%config.logLevel%", - "scope": "window" - }, "git.mergeEditor": { "type": "boolean", - "default": true, + "default": false, "markdownDescription": "%config.mergeEditor%", "scope": "window" + }, + "git.optimisticUpdate": { + "type": "boolean", + "default": true, + "markdownDescription": "%config.optimisticUpdate%", + "scope": "resource", + "tags": [ + "experimental" + ] + }, + "git.openRepositoryInParentFolders": { + "type": "string", + "enum": [ + "always", + "never", + "prompt" + ], + "enumDescriptions": [ + "%config.openRepositoryInParentFolders.always%", + "%config.openRepositoryInParentFolders.never%", + "%config.openRepositoryInParentFolders.prompt%" + ], + "default": "prompt", + "markdownDescription": "%config.openRepositoryInParentFolders%", + "scope": "resource" + }, + "git.similarityThreshold": { + "type": "number", + "default": 50, + "minimum": 0, + "maximum": 100, + "description": "%config.similarityThreshold%", + "scope": "resource" } } }, @@ -2670,56 +2860,81 @@ { "view": "scm", "contents": "%view.workbench.scm.empty%", - "when": "config.git.enabled && !git.missing && workbenchState == empty", + "when": "config.git.enabled && !git.missing && workbenchState == empty && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0", "enablement": "git.state == initialized", "group": "2_open@1" }, { "view": "scm", - "contents": "%view.workbench.scm.folder%", - "when": "config.git.enabled && !git.missing && workbenchState == folder", + "contents": "%view.workbench.scm.emptyWorkspace%", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0", "enablement": "git.state == initialized", + "group": "2_open@1" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.scanFolderForRepositories%", + "when": "config.git.enabled && !git.missing && workbenchState == folder && workspaceFolderCount != 0 && git.state != initialized" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.scanWorkspaceForRepositories%", + "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0 && git.state != initialized" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.folder%", + "when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == folder && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && remoteName != 'codespaces'", "group": "5_scm@1" }, { "view": "scm", "contents": "%view.workbench.scm.workspace%", - "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0", - "enablement": "git.state == initialized", + "when": "config.git.enabled && !git.missing && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && scmRepositoryCount == 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0 && remoteName != 'codespaces'", "group": "5_scm@1" }, { "view": "scm", - "contents": "%view.workbench.scm.emptyWorkspace%", - "when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0", - "enablement": "git.state == initialized", - "group": "2_open@1" + "contents": "%view.workbench.scm.repositoryInParentFolders%", + "when": "config.git.enabled && !git.missing && git.state == initialized && git.parentRepositoryCount == 1" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.repositoriesInParentFolders%", + "when": "config.git.enabled && !git.missing && git.state == initialized && git.parentRepositoryCount > 1" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.unsafeRepository%", + "when": "config.git.enabled && !git.missing && git.state == initialized && git.unsafeRepositoryCount == 1" + }, + { + "view": "scm", + "contents": "%view.workbench.scm.unsafeRepositories%", + "when": "config.git.enabled && !git.missing && git.state == initialized && git.unsafeRepositoryCount > 1" }, { "view": "explorer", "contents": "%view.workbench.cloneRepository%", - "when": "config.git.enabled", - "enablement": "git.state == initialized", + "when": "config.git.enabled && git.state == initialized && scmRepositoryCount == 0", "group": "5_scm@1" }, { "view": "explorer", "contents": "%view.workbench.learnMore%", - "when": "config.git.enabled", - "enablement": "git.state == initialized", + "when": "config.git.enabled && git.state == initialized && scmRepositoryCount == 0", "group": "5_scm@10" } ] }, "dependencies": { - "@joaomoreno/unique-names-generator": "5.0.0", - "@vscode/extension-telemetry": "0.6.2", + "@joaomoreno/unique-names-generator": "^5.1.0", + "@vscode/extension-telemetry": "0.7.5", "@vscode/iconv-lite-umd": "0.7.0", "byline": "^5.0.0", "file-type": "16.5.4", "jschardet": "3.0.0", "picomatch": "2.3.1", - "vscode-nls": "^4.0.0", "vscode-uri": "^2.0.0", "which": "^1.3.0" }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 6710c283c4..75aaad7567 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -1,7 +1,8 @@ { "displayName": "Git", "description": "Git SCM Integration", - "command.setLogLevel": "Set Log Level...", + "command.continueInLocalClone": "Clone Repository Locally and Open on Desktop...", + "command.continueInLocalClone.qualifiedName": "Continue Working in New Local Clone", "command.clone": "Clone", "command.cloneRecursive": "Clone (Recursive)", "command.init": "Initialize Repository", @@ -58,9 +59,11 @@ "command.renameBranch": "Rename Branch...", "command.cherryPick": "Cherry Pick...", "command.merge": "Merge Branch...", + "command.mergeAbort": "Abort Merge", "command.rebase": "Rebase Branch...", "command.createTag": "Create Tag", "command.deleteTag": "Delete Tag", + "command.deleteRemoteTag": "Delete Remote Tag", "command.fetch": "Fetch", "command.fetchPrune": "Fetch (Prune)", "command.fetchAll": "Fetch From All Remotes", @@ -88,6 +91,7 @@ "command.rebaseAbort": "Abort Rebase", "command.stashIncludeUntracked": "Stash (Include Untracked)", "command.stash": "Stash", + "command.stashStaged": "Stash Staged", "command.stashPop": "Pop Stash...", "command.stashPopLatest": "Pop Latest Stash", "command.stashApply": "Apply Stash...", @@ -99,10 +103,15 @@ "command.timelineCopyCommitMessage": "Copy Commit Message", "command.timelineSelectForCompare": "Select for Compare", "command.timelineCompareWithSelected": "Compare with Selected", + "command.manageUnsafeRepositories": "Manage Unsafe Repositories", + "command.openRepositoriesInParentFolders": "Open Repositories In Parent Folders", "command.api.getRepositories": "Get Repositories", "command.api.getRepositoryState": "Get Repository State", "command.api.getRemoteSources": "Get Remote Sources", - "command.git.acceptMerge": "Accept Merge", + "command.git.acceptMerge": "Complete Merge", + "command.git.openMergeEditor": "Resolve in Merge Editor", + "command.git.runGitMerge": "Compute Conflicts With Git", + "command.git.runGitMergeDiff3": "Compute Conflicts With Git (Diff3)", "config.enabled": "Whether git is enabled.", "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows). This can also be an array of string values containing multiple paths to look up.", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", @@ -122,9 +131,10 @@ "config.checkoutType.local": "Local branches", "config.checkoutType.tags": "Tags", "config.checkoutType.remote": "Remote branches", + "config.defaultBranchName": "The name of the default branch (ex: main, trunk, development) when initializing a new git repository. When set to empty, the default branch name configured in git will be used.", "config.branchPrefix": "Prefix used when creating a new branch.", "config.branchProtection": "List of protected branches. By default, a prompt is shown before changes are committed to a protected branch. The prompt can be controlled using the `#git.branchProtectionPrompt#` setting.", - "config.branchProtectionPrompt": "Controls whether a prompt is being before changes are committed to a protected branch.", + "config.branchProtectionPrompt": "Controls whether a prompt is being shown before changes are committed to a protected branch.", "config.branchProtectionPrompt.alwaysCommit": "Always commit changes to the protected branch.", "config.branchProtectionPrompt.alwaysCommitToNewBranch": "Always commit changes to a new branch.", "config.branchProtectionPrompt.alwaysPrompt": "Always prompt before changes are committed to a protected branch.", @@ -153,7 +163,7 @@ "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.followTagsWhenSync": "Push all annotated 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.", @@ -162,10 +172,11 @@ "config.promptToSaveFilesBeforeCommit.always": "Check for any unsaved files.", "config.promptToSaveFilesBeforeCommit.staged": "Check only for unsaved staged files.", "config.promptToSaveFilesBeforeCommit.never": "Disable this check.", - "config.postCommitCommand": "Runs a git command after a successful commit.", + "config.postCommitCommand": "Run a git command after a successful commit.", "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.postCommitCommand.push": "Run 'git push' after a successful commit.", + "config.postCommitCommand.sync": "Run 'git pull' and 'git push' after a successful commit.", + "config.rememberPostCommitCommand": "Remember the last git command that ran after a 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.", @@ -195,6 +206,7 @@ "config.rebaseWhenSync": "Force git to use rebase when running the sync command.", "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.pullBeforeCheckout": "Controls whether a branch that does not have outgoing commits is fast-forwarded before it is checked out.", "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.", @@ -213,8 +225,8 @@ "config.untrackedChanges.hidden": "Untracked changes are hidden and excluded from several actions.", "config.requireGitUserConfig": "Controls whether to require explicit Git user configuration or allow Git to guess if missing.", "config.showCommitInput": "Controls whether to show the commit input in the Git source control panel.", - "config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.", - "config.terminalGitEditor": "Controls whether to enable VS Code to be git editor for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.", + "config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for Git processes spawned in the Integrated Terminal. Note: Terminals need to be restarted to pick up a change in this setting.", + "config.terminalGitEditor": "Controls whether to enable VS Code to be the Git editor for Git processes spawned in the integrated terminal. Note: Terminals need to be restarted to pick up a change in this setting.", "config.timeline.showAuthor": "Controls whether to show the commit author in the Timeline view.", "config.timeline.showUncommitted": "Controls whether to show uncommitted changes in the Timeline view.", "config.timeline.date": "Controls which date to use for items in the Timeline view.", @@ -230,22 +242,17 @@ "config.repositoryScanIgnoredFolders": "List of folders that are ignored while scanning for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`.", "config.repositoryScanMaxDepth": "Controls the depth used when scanning workspace folders for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`. Can be set to `-1` for no limit.", "config.useIntegratedAskPass": "Controls whether GIT_ASKPASS should be overwritten to use the integrated version.", - "config.logLevel": { - "message": "Specifies how much information (if any) to log to the [git output](command:git.showOutput).", - "comment": [ - "{Locked='](command:git.showOutput'}", - "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", - "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" - ] - }, - "config.logLevel.trace": "Log all information", - "config.logLevel.debug": "Log only debug, information, warning, error, and critical information", - "config.logLevel.info": "Log only information, warning, error, and critical information", - "config.logLevel.warn": "Log only warning, error, and critical information", - "config.logLevel.error": "Log only error, and critical information", - "config.logLevel.critical": "Log only critical information", - "config.logLevel.off": "Log nothing", "config.mergeEditor": "Open the merge editor for files that are currently under conflict.", + "config.optimisticUpdate": "Controls whether to optimistically update the state of the Source Control view after running git commands.", + "config.openRepositoryInParentFolders": "Control whether a repository in parent folders of workspaces or open files should be opened.", + "config.openRepositoryInParentFolders.always": "Always open a repository in parent folders of workspaces or open files.", + "config.openRepositoryInParentFolders.never": "Never open a repository in parent folders of workspaces or open files.", + "config.openRepositoryInParentFolders.prompt": "Prompt before opening a repository the parent folders of workspaces or open files.", + "config.publishBeforeContinueOn": "Controls whether to publish unpublished git state when using Continue Working On from a git repository.", + "config.publishBeforeContinueOn.always": "Always publish unpublished git state when using Continue Working On from a git repository", + "config.publishBeforeContinueOn.never": "Never publish unpublished git state when using Continue Working On from a git repository", + "config.publishBeforeContinueOn.prompt": "Prompt to publish unpublished git state when using Continue Working On from a git repository", + "config.similarityThreshold": "Controls the threshold of the similarity index (i.e. amount of additions/deletions compared to the file's size) for changes in a pair of added/deleted files to be considered a rename.", "submenu.explorer": "Git", "submenu.commit": "Commit", "submenu.commit.amend": "Amend", @@ -331,6 +338,46 @@ "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" ] }, + "view.workbench.scm.scanFolderForRepositories": { + "message": "Scanning folder for git repositories..." + }, + "view.workbench.scm.scanWorkspaceForRepositories": { + "message": "Scanning workspace for git repositories..." + }, + "view.workbench.scm.repositoryInParentFolders": { + "message": "A git repository was found in the parent folders of the workspace or the open file(s).\n[Open Repository](command:git.openRepositoriesInParentFolders)\nUse the [git.openRepositoryInParentFolders](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D) setting to control whether git repositories in parent folders of workspaces or open files are opened. To learn more [read our docs](https://aka.ms/vscode-git-repository-in-parent-folders).", + "comment": [ + "{Locked='](command:git.openRepositoriesInParentFolders'}", + "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "view.workbench.scm.repositoriesInParentFolders": { + "message": "Git repositories were found in the parent folders of the workspace or the open file(s).\n[Open Repository](command:git.openRepositoriesInParentFolders)\nUse the [git.openRepositoryInParentFolders](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D) setting to control whether git repositories in parent folders of workspace or open files are opened. To learn more [read our docs](https://aka.ms/vscode-git-repository-in-parent-folders).", + "comment": [ + "{Locked='](command:git.openRepositoriesInParentFolders'}", + "{Locked='](command:workbench.action.openSettings?%5B%22git.openRepositoryInParentFolders%22%5D'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "view.workbench.scm.unsafeRepository": { + "message": "The detected git repository is potentially unsafe as the folder is owned by someone other than the current user.\n[Manage Unsafe Repositories](command:git.manageUnsafeRepositories)\nTo learn more about unsafe repositories [read our docs](https://aka.ms/vscode-git-unsafe-repository).", + "comment": [ + "{Locked='](command:git.manageUnsafeRepositories'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, + "view.workbench.scm.unsafeRepositories": { + "message": "The detected git repositories are potentially unsafe as the folders are owned by someone other than the current user.\n[Manage Unsafe Repositories](command:git.manageUnsafeRepositories)\nTo learn more about unsafe repositories [read our docs](https://aka.ms/vscode-git-unsafe-repository).", + "comment": [ + "{Locked='](command:git.manageUnsafeRepositories'}", + "Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code", + "Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links" + ] + }, "view.workbench.cloneRepository": { "message": "You can clone a repository locally.\n[Clone Repository](command:git.clone 'Clone a repository once the git extension has activated')", "comment": [ diff --git a/extensions/git/src/actionButton.ts b/extensions/git/src/actionButton.ts index 30b6427232..b722791d0c 100644 --- a/extensions/git/src/actionButton.ts +++ b/extensions/git/src/actionButton.ts @@ -3,18 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vscode-nls'; -import { Command, Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace } from 'vscode'; -import { ApiRepository } from './api/api1'; -import { Branch, Status } from './api/git'; -import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; -import { Repository, Operation } from './repository'; +import { Command, Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace, l10n } from 'vscode'; +import { Branch, RefType, Status } from './api/git'; +import { OperationKind } from './operation'; +import { CommitCommandsCenter } from './postCommitCommands'; +import { Repository } from './repository'; import { dispose } from './util'; -const localize = nls.loadMessageBundle(); - interface ActionButtonState { readonly HEAD: Branch | undefined; + readonly isCheckoutInProgress: boolean; readonly isCommitInProgress: boolean; readonly isMergeInProgress: boolean; readonly isRebaseInProgress: boolean; @@ -39,9 +37,10 @@ export class ActionButtonCommand { constructor( readonly repository: Repository, - readonly postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry) { + readonly postCommitCommandCenter: CommitCommandsCenter) { this._state = { HEAD: undefined, + isCheckoutInProgress: false, isCommitInProgress: false, isMergeInProgress: false, isRebaseInProgress: false, @@ -52,7 +51,8 @@ export class ActionButtonCommand { repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables); repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); - this.disposables.push(postCommitCommandsProviderRegistry.onDidChangePostCommitCommandsProviders(() => this._onDidChange.fire())); + this.disposables.push(repository.onDidChangeBranchProtection(() => this._onDidChange.fire())); + this.disposables.push(postCommitCommandCenter.onDidChange(() => this._onDidChange.fire())); const root = Uri.file(repository.root); this.disposables.push(workspace.onDidChangeConfiguration(e => { @@ -62,9 +62,9 @@ export class ActionButtonCommand { this.onDidChangeSmartCommitSettings(); } - if (e.affectsConfiguration('git.branchProtection', root) || - e.affectsConfiguration('git.branchProtectionPrompt', root) || + if (e.affectsConfiguration('git.branchProtectionPrompt', root) || e.affectsConfiguration('git.postCommitCommand', root) || + e.affectsConfiguration('git.rememberPostCommitCommand', root) || e.affectsConfiguration('git.showActionButton', root)) { this._onDidChange.fire(); } @@ -92,8 +92,10 @@ export class ActionButtonCommand { // The button is disabled if (!showActionButton.commit) { return undefined; } + const primaryCommand = this.getCommitActionButtonPrimaryCommand(); + return { - command: this.getCommitActionButtonPrimaryCommand(), + command: primaryCommand, secondaryCommands: this.getCommitActionButtonSecondaryCommands(), enabled: (this.state.repositoryHasChangesToCommit || this.state.isRebaseInProgress) && !this.state.isCommitInProgress && !this.state.isMergeInProgress }; @@ -104,90 +106,28 @@ export class ActionButtonCommand { if (this.state.isRebaseInProgress) { return { command: 'git.commit', - title: localize('scm button continue title', "{0} Continue", '$(check)'), - tooltip: this.state.isCommitInProgress ? localize('scm button continuing tooltip', "Continuing Rebase...") : localize('scm button continue tooltip', "Continue Rebase"), + title: l10n.t('{0} Continue', '$(check)'), + tooltip: this.state.isCommitInProgress ? l10n.t('Continuing Rebase...') : l10n.t('Continue Rebase'), arguments: [this.repository.sourceControl, ''] }; } // Commit - const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); - const postCommitCommand = config.get('postCommitCommand'); - - // Branch protection - const isBranchProtected = this.repository.isBranchProtected(); - const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!; - const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt'; - const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch'; - - // Icon - const icon = alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined; - - let commandArg = ''; - let title = localize('scm button commit title', "{0} Commit", icon ?? '$(check)'); - let tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes"); - - // Title, tooltip - switch (postCommitCommand) { - case 'push': { - commandArg = 'git.push'; - title = localize('scm button commit and push title', "{0} Commit & Push", icon ?? '$(arrow-up)'); - if (alwaysCommitToNewBranch) { - tooltip = this.state.isCommitInProgress ? - localize('scm button committing to new branch and pushing tooltip', "Committing to New Branch & Pushing Changes...") : - localize('scm button commit to new branch and push tooltip', "Commit to New Branch & Push Changes"); - } else { - tooltip = this.state.isCommitInProgress ? - localize('scm button committing and pushing tooltip', "Committing & Pushing Changes...") : - localize('scm button commit and push tooltip', "Commit & Push Changes"); - } - break; - } - case 'sync': { - commandArg = 'git.sync'; - title = localize('scm button commit and sync title', "{0} Commit & Sync", icon ?? '$(sync)'); - if (alwaysCommitToNewBranch) { - tooltip = this.state.isCommitInProgress ? - localize('scm button committing to new branch and synching tooltip', "Committing to New Branch & Synching Changes...") : - localize('scm button commit to new branch and sync tooltip', "Commit to New Branch & Sync Changes"); - } else { - tooltip = this.state.isCommitInProgress ? - localize('scm button committing and synching tooltip', "Committing & Synching Changes...") : - localize('scm button commit and sync tooltip', "Commit & Sync Changes"); - } - break; - } - default: { - if (alwaysCommitToNewBranch) { - tooltip = this.state.isCommitInProgress ? - localize('scm button committing to new branch tooltip', "Committing Changes to New Branch...") : - localize('scm button commit to new branch tooltip', "Commit Changes to New Branch"); - } - break; - } - } - - return { command: 'git.commit', title, tooltip, arguments: [this.repository.sourceControl, commandArg] }; + return this.postCommitCommandCenter.getPrimaryCommand(); } private getCommitActionButtonSecondaryCommands(): Command[][] { + // Rebase Continue + if (this.state.isRebaseInProgress) { + return []; + } + + // Commit const commandGroups: Command[][] = []; - - if (!this.state.isRebaseInProgress) { - for (const provider of this.postCommitCommandsProviderRegistry.getPostCommitCommandsProviders()) { - const commands = provider.getCommands(new ApiRepository(this.repository)); - commandGroups.push((commands ?? []).map(c => { - return { - command: 'git.commit', - title: c.title, - arguments: [this.repository.sourceControl, c.command] - }; - })); - } - - if (commandGroups.length > 0) { - commandGroups[0].splice(0, 0, { command: 'git.commit', title: localize('scm secondary button commit', "Commit") }); - } + for (const commands of this.postCommitCommandCenter.getSecondaryCommands()) { + commandGroups.push(commands.map(c => { + return { command: 'git.commit', title: c.title, tooltip: c.tooltip, arguments: c.arguments }; + })); } return commandGroups; @@ -197,19 +137,26 @@ export class ActionButtonCommand { const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true }); - // Branch does have an upstream, commit/merge/rebase is in progress, or the button is disabled - if (this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.publish) { return undefined; } + // Not a branch (tag, detached), branch does have an upstream, commit/merge/rebase is in progress, or the button is disabled + if (this.state.HEAD?.type === RefType.Tag || !this.state.HEAD?.name || this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.publish) { return undefined; } + + // Button icon + const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(cloud-upload)'; return { command: { command: 'git.publish', - title: localize({ key: 'scm publish branch action button title', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "{0} Publish Branch", '$(cloud-upload)'), + title: l10n.t({ message: '{0} Publish Branch', args: [icon], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }), tooltip: this.state.isSyncInProgress ? - localize({ key: 'scm button publish branch running', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "Publishing Branch...") : - localize({ key: 'scm button publish branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "Publish Branch"), + (this.state.HEAD?.name ? + l10n.t({ message: 'Publishing Branch "{0}"...', args: [this.state.HEAD.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) : + l10n.t({ message: 'Publishing Branch...', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })) : + (this.repository.HEAD?.name ? + l10n.t({ message: 'Publish Branch "{0}"', args: [this.state.HEAD?.name], comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }) : + l10n.t({ message: 'Publish Branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] })), arguments: [this.repository.sourceControl], }, - enabled: !this.state.isSyncInProgress + enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress }; } @@ -228,28 +175,33 @@ export class ActionButtonCommand { return { command: { command: 'git.sync', - title: `${icon}${behind}${ahead}`, + title: l10n.t('{0} Sync Changes{1}{2}', icon, behind, ahead), tooltip: this.state.isSyncInProgress ? - localize('syncing changes', "Synchronizing Changes...") + l10n.t('Synchronizing Changes...') : this.repository.syncTooltip, arguments: [this.repository.sourceControl], }, - description: localize('scm button sync description', "{0} Sync Changes{1}{2}", icon, behind, ahead), - enabled: !this.state.isSyncInProgress + description: `${icon}${behind}${ahead}`, + enabled: !this.state.isCheckoutInProgress && !this.state.isSyncInProgress }; } private onDidChangeOperations(): void { + const isCheckoutInProgress + = this.repository.operations.isRunning(OperationKind.Checkout) || + this.repository.operations.isRunning(OperationKind.CheckoutTracking); + const isCommitInProgress = - this.repository.operations.isRunning(Operation.Commit) || - this.repository.operations.isRunning(Operation.RebaseContinue); + this.repository.operations.isRunning(OperationKind.Commit) || + this.repository.operations.isRunning(OperationKind.PostCommitCommand) || + this.repository.operations.isRunning(OperationKind.RebaseContinue); const isSyncInProgress = - this.repository.operations.isRunning(Operation.Sync) || - this.repository.operations.isRunning(Operation.Push) || - this.repository.operations.isRunning(Operation.Pull); + this.repository.operations.isRunning(OperationKind.Sync) || + this.repository.operations.isRunning(OperationKind.Push) || + this.repository.operations.isRunning(OperationKind.Pull); - this.state = { ...this.state, isCommitInProgress, isSyncInProgress }; + this.state = { ...this.state, isCheckoutInProgress, isCommitInProgress, isSyncInProgress }; } private onDidChangeSmartCommitSettings(): void { diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index e159be1b63..6164f127ba 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -5,8 +5,8 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, ICloneOptions, PostCommitCommandsProvider } from './git'; // {{SQL CARBON EDIT}} add ICloneOptions -import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; // {{SQL CARBON EDIT}} Add cancellationToken +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions } from './git'; +import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; import { combinedDisposable, mapEvent } from '../util'; import { toGitUri } from '../uri'; import { GitExtensionImpl } from './extension'; @@ -32,7 +32,10 @@ export class ApiChange implements Change { export class ApiRepositoryState implements RepositoryState { get HEAD(): Branch | undefined { return this._repository.HEAD; } - get refs(): Ref[] { return [...this._repository.refs]; } + /** + * @deprecated Use ApiRepository.getRefs() instead. + */ + get refs(): Ref[] { console.warn('Deprecated. Use ApiRepository.getRefs() instead.'); return []; } get remotes(): Remote[] { return [...this._repository.remotes]; } get submodules(): Submodule[] { return [...this._repository.submodules]; } get rebaseCommit(): Commit | undefined { return this._repository.rebaseCommit; } @@ -170,14 +173,18 @@ export class ApiRepository implements Repository { return this.repository.getBranch(name); } - getBranches(query: BranchQuery): Promise { - return this.repository.getBranches(query); + getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise { + return this.repository.getBranches(query, cancellationToken); } setBranchUpstream(name: string, upstream: string): Promise { return this.repository.setBranchUpstream(name, upstream); } + getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise { + return this.repository.getRefs(query, cancellationToken); + } + getMergeBase(ref1: string, ref2: string): Promise { return this.repository.getMergeBase(ref1, ref2); } @@ -279,7 +286,7 @@ export class ApiImpl implements API { } // {{SQL CARBON EDIT}} - async clone(url: string, options: ICloneOptions, cancellationToken?: CancellationToken): Promise { + async clone(url: string, options: any, cancellationToken?: CancellationToken): Promise { return this._model.git.clone(url, options, cancellationToken); } @@ -292,9 +299,9 @@ export class ApiImpl implements API { return result ? new ApiRepository(result) : null; } - async init(root: Uri): Promise { + async init(root: Uri, options?: InitOptions): Promise { const path = root.fsPath; - await this._model.git.init(path); + await this._model.git.init(path, options); await this._model.openRepository(path); return this.getRepository(root) || null; } @@ -331,6 +338,10 @@ export class ApiImpl implements API { return this._model.registerPushErrorHandler(handler); } + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable { + return this._model.registerBranchProtectionProvider(root, provider); + } + constructor(private _model: Model) { } } @@ -356,6 +367,7 @@ function getStatus(status: Status): string { case Status.UNTRACKED: return 'UNTRACKED'; case Status.IGNORED: return 'IGNORED'; case Status.INTENT_TO_ADD: return 'INTENT_TO_ADD'; + case Status.INTENT_TO_RENAME: return 'INTENT_TO_RENAME'; case Status.ADDED_BY_US: return 'ADDED_BY_US'; case Status.ADDED_BY_THEM: return 'ADDED_BY_THEM'; case Status.DELETED_BY_US: return 'DELETED_BY_US'; diff --git a/extensions/git/src/api/git-base.d.ts b/extensions/git/src/api/git-base.d.ts index 7512977d0d..35e61c2e14 100644 --- a/extensions/git/src/api/git-base.d.ts +++ b/extensions/git/src/api/git-base.d.ts @@ -8,6 +8,7 @@ export { ProviderResult } from 'vscode'; export interface API { pickRemoteSource(options: PickRemoteSourceOptions): Promise; + getRemoteSourceActions(url: string): Promise; registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; } @@ -31,9 +32,12 @@ export interface GitBaseExtension { export interface PickRemoteSourceOptions { readonly providerLabel?: (provider: RemoteSourceProvider) => string; - readonly urlLabel?: string; + readonly urlLabel?: string | ((url: string) => string); readonly providerName?: string; + readonly title?: string; + readonly placeholder?: string; readonly branch?: boolean; // then result is PickRemoteSourceResult + readonly showRecentSources?: boolean; } export interface PickRemoteSourceResult { @@ -41,20 +45,42 @@ export interface PickRemoteSourceResult { readonly branch?: string; } +export interface RemoteSourceAction { + readonly label: string; + /** + * Codicon name + */ + readonly icon: string; + run(branch: string): void; +} + export interface RemoteSource { readonly name: string; readonly description?: string; + readonly detail?: string; + /** + * Codicon name + */ + readonly icon?: string; readonly url: string | string[]; } +export interface RecentRemoteSource extends RemoteSource { + readonly timestamp: number; +} + export interface RemoteSourceProvider { readonly name: string; /** * Codicon name */ readonly icon?: string; + readonly label?: string; + readonly placeholder?: string; readonly supportsQuery?: boolean; getBranches?(url: string): ProviderResult; + getRemoteSourceActions?(url: string): ProviderResult; + getRecentRemoteSources?(query?: string): ProviderResult; getRemoteSources(query?: string): ProviderResult; } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index d339f8a4c2..fdb184c07a 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, Event, Disposable, ProviderResult, CancellationToken, Progress, Command } from 'vscode'; // {{SQL CARBON EDIT}} add CancellationToken +import { Uri, Event, Disposable, ProviderResult, Command, Progress, CancellationToken } from 'vscode'; // {{SQL CARBON EDIT}} - add Progress export { ProviderResult } from 'vscode'; export interface Git { @@ -78,6 +78,7 @@ export const enum Status { UNTRACKED, IGNORED, INTENT_TO_ADD, + INTENT_TO_RENAME, ADDED_BY_US, ADDED_BY_THEM, @@ -139,7 +140,13 @@ export interface CommitOptions { requireUserConfig?: boolean; useEditor?: boolean; verbose?: boolean; - postCommitCommand?: string; + /** + * string - execute the specified command after the commit operation + * undefined - execute the command specified in git.postCommitCommand + * after the commit operation + * null - do not execute any command after the commit operation + */ + postCommitCommand?: string | null; } export interface FetchOptions { @@ -150,11 +157,19 @@ export interface FetchOptions { depth?: number; } -export interface BranchQuery { - readonly remote?: boolean; - readonly pattern?: string; - readonly count?: number; +export interface InitOptions { + defaultBranch?: string; +} + +export interface RefQuery { readonly contains?: string; + readonly count?: number; + readonly pattern?: string; + readonly sort?: 'alphabetically' | 'committerdate'; +} + +export interface BranchQuery extends RefQuery { + readonly remote?: boolean; } export interface Repository { @@ -198,9 +213,11 @@ export interface Repository { createBranch(name: string, checkout: boolean, ref?: string): Promise; deleteBranch(name: string, force?: boolean): Promise; getBranch(name: string): Promise; - getBranches(query: BranchQuery): Promise; + getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise; setBranchUpstream(name: string, upstream: string): Promise; + getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise; + getMergeBase(ref1: string, ref2: string): Promise; tag(name: string, upstream: string): Promise; @@ -262,6 +279,21 @@ export interface PushErrorHandler { handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; } +export interface BranchProtection { + readonly remote: string; + readonly rules: BranchProtectionRule[]; +} + +export interface BranchProtectionRule { + readonly include?: string[]; + readonly exclude?: string[]; +} + +export interface BranchProtectionProvider { + onDidChangeBranchProtection: Event; + provideBranchProtection(): BranchProtection[]; +} + export type APIState = 'uninitialized' | 'initialized'; export interface PublishEvent { @@ -285,10 +317,10 @@ export interface API { * @param cancellationToken * @returns a promise to the string location where the repository was cloned to */ - clone(url: string, options: ICloneOptions, cancellationToken?: CancellationToken): Promise; // {{SQL CARBON EDIT}} + clone(url: string, options: any, cancellationToken?: CancellationToken): Promise; // {{SQL CARBON EDIT}} toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; - init(root: Uri): Promise; + init(root: Uri, options?: InitOptions): Promise; openRepository(root: Uri): Promise registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; @@ -296,6 +328,7 @@ export interface API { registerCredentialsProvider(provider: CredentialsProvider): Disposable; registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; registerPushErrorHandler(handler: PushErrorHandler): Disposable; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; } export interface GitExtension { @@ -352,7 +385,10 @@ export const enum GitErrorCodes { PatchDoesNotApply = 'PatchDoesNotApply', NoPathFound = 'NoPathFound', UnknownPath = 'UnknownPath', - EmptyCommitMessage = 'EmptyCommitMessage' + EmptyCommitMessage = 'EmptyCommitMessage', + BranchFastForwardRejected = 'BranchFastForwardRejected', + BranchNotYetBorn = 'BranchNotYetBorn', + TagConflict = 'TagConflict' } // {{SQL CARBON EDIT}} move ICloneOptions from git.ts to here since it's used in clone() @@ -360,4 +396,5 @@ export interface ICloneOptions { readonly parentPath: string; readonly progress: Progress<{ increment: number }>; readonly recursive?: boolean; + readonly ref?: string; } diff --git a/extensions/git/src/askpass-main.ts b/extensions/git/src/askpass-main.ts index a03546a29b..efe715101e 100644 --- a/extensions/git/src/askpass-main.ts +++ b/extensions/git/src/askpass-main.ts @@ -4,36 +4,58 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import * as nls from 'vscode-nls'; import { IPCClient } from './ipc/ipcClient'; -const localize = nls.loadMessageBundle(); - function fatal(err: any): void { - console.error(localize('missOrInvalid', "Missing or invalid credentials.")); + console.error('Missing or invalid credentials.'); console.error(err); process.exit(1); } function main(argv: string[]): void { - if (argv.length !== 5) { - return fatal('Wrong number of arguments'); - } - if (!process.env['VSCODE_GIT_ASKPASS_PIPE']) { return fatal('Missing pipe'); } + if (!process.env['VSCODE_GIT_ASKPASS_TYPE']) { + return fatal('Missing type'); + } + + if (process.env['VSCODE_GIT_ASKPASS_TYPE'] !== 'https' && process.env['VSCODE_GIT_ASKPASS_TYPE'] !== 'ssh') { + return fatal(`Invalid type: ${process.env['VSCODE_GIT_ASKPASS_TYPE']}`); + } + if (process.env['VSCODE_GIT_COMMAND'] === 'fetch' && !!process.env['VSCODE_GIT_FETCH_SILENT']) { return fatal('Skip silent fetch commands'); } const output = process.env['VSCODE_GIT_ASKPASS_PIPE'] as string; - const request = argv[2]; - const host = argv[4].replace(/^["']+|["':]+$/g, ''); - const ipcClient = new IPCClient('askpass'); + const askpassType = process.env['VSCODE_GIT_ASKPASS_TYPE'] as 'https' | 'ssh'; - ipcClient.call({ request, host }).then(res => { + // HTTPS (username | password), SSH (passphrase | authenticity) + const request = askpassType === 'https' ? argv[2] : argv[3]; + + let host: string | undefined, + file: string | undefined, + fingerprint: string | undefined; + + if (askpassType === 'https') { + host = argv[4].replace(/^["']+|["':]+$/g, ''); + } + + if (askpassType === 'ssh') { + if (/passphrase/i.test(request)) { + // passphrase + file = argv[6].replace(/^["']+|["':]+$/g, ''); + } else { + // authenticity + host = argv[6].replace(/^["']+|["':]+$/g, ''); + fingerprint = argv[15]; + } + } + + const ipcClient = new IPCClient('askpass'); + ipcClient.call({ askpassType, request, host, file, fingerprint }).then(res => { fs.writeFileSync(output, res + '\n'); setTimeout(() => process.exit(0), 0); }).catch(err => fatal(err)); diff --git a/extensions/git/src/askpass.sh b/extensions/git/src/askpass.sh index 4536224764..93a08c3896 100644 --- a/extensions/git/src/askpass.sh +++ b/extensions/git/src/askpass.sh @@ -1,5 +1,5 @@ #!/bin/sh VSCODE_GIT_ASKPASS_PIPE=`mktemp` -ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $VSCODE_GIT_ASKPASS_EXTRA_ARGS $* +ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" VSCODE_GIT_ASKPASS_TYPE="https" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $VSCODE_GIT_ASKPASS_EXTRA_ARGS $* cat $VSCODE_GIT_ASKPASS_PIPE rm $VSCODE_GIT_ASKPASS_PIPE diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 87e6a21a8a..40c854d6f1 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, InputBoxOptions, Uri, Disposable, workspace } from 'vscode'; +import { window, InputBoxOptions, Uri, Disposable, workspace, QuickPickOptions, l10n } from 'vscode'; import { IDisposable, EmptyDisposable, toDisposable } from './util'; import * as path from 'path'; import { IIPCHandler, IIPCServer } from './ipc/ipcServer'; @@ -13,6 +13,7 @@ import { ITerminalEnvironmentProvider } from './terminal'; export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider { private env: { [key: string]: string }; + private sshEnv: { [key: string]: string }; private disposable: IDisposable = EmptyDisposable; private cache = new Map(); private credentialsProviders = new Set(); @@ -23,14 +24,25 @@ export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider { } this.env = { + // GIT_ASKPASS GIT_ASKPASS: path.join(__dirname, this.ipc ? 'askpass.sh' : 'askpass-empty.sh'), + // VSCODE_GIT_ASKPASS VSCODE_GIT_ASKPASS_NODE: process.execPath, VSCODE_GIT_ASKPASS_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '', VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js'), }; + + this.sshEnv = { + // SSH_ASKPASS + SSH_ASKPASS: path.join(__dirname, this.ipc ? 'ssh-askpass.sh' : 'ssh-askpass-empty.sh'), + SSH_ASKPASS_REQUIRE: 'force', + }; } - async handle({ request, host }: { request: string; host: string }): Promise { + async handle(payload: + { askpassType: 'https'; request: string; host: string } | + { askpassType: 'ssh'; request: string; host?: string; file?: string; fingerprint?: string } + ): Promise { const config = workspace.getConfiguration('git', null); const enabled = config.get('enabled'); @@ -38,6 +50,16 @@ export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider { return ''; } + // https + if (payload.askpassType === 'https') { + return await this.handleAskpass(payload.request, payload.host); + } + + // ssh + return await this.handleSSHAskpass(payload.request, payload.host, payload.file, payload.fingerprint); + } + + async handleAskpass(request: string, host: string): Promise { const uri = Uri.parse(host); const authority = uri.authority.replace(/^.*@/, ''); const password = /password/i.test(request); @@ -72,9 +94,33 @@ export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider { return await window.showInputBox(options) || ''; } + async handleSSHAskpass(request: string, host?: string, file?: string, fingerprint?: string): Promise { + // passphrase + if (/passphrase/i.test(request)) { + const options: InputBoxOptions = { + password: true, + placeHolder: l10n.t('Passphrase'), + prompt: `SSH Key: ${file}`, + ignoreFocusOut: true + }; + + return await window.showInputBox(options) || ''; + } + + // authenticity + const options: QuickPickOptions = { + canPickMany: false, + ignoreFocusOut: true, + placeHolder: l10n.t('Are you sure you want to continue connecting?'), + title: l10n.t('"{0}" has fingerprint "{1}"', host ?? '', fingerprint ?? '') + }; + const items = [l10n.t('yes'), l10n.t('no')]; + return await window.showQuickPick(items, options) ?? ''; + } + getEnv(): { [key: string]: string } { const config = workspace.getConfiguration('git'); - return config.get('useIntegratedAskPass') ? this.env : {}; + return config.get('useIntegratedAskPass') ? { ...this.env, ...this.sshEnv } : {}; } getTerminalEnv(): { [key: string]: string } { diff --git a/extensions/git/src/autofetch.ts b/extensions/git/src/autofetch.ts index fc73e3952f..e4e6f037c8 100644 --- a/extensions/git/src/autofetch.ts +++ b/extensions/git/src/autofetch.ts @@ -3,18 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, Uri, ConfigurationChangeEvent } from 'vscode'; -import { Repository, Operation } from './repository'; +import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, Uri, ConfigurationChangeEvent, l10n, env } from 'vscode'; +import { Repository } from './repository'; import { eventToPromise, filterEvent, onceEvent } from './util'; -import * as nls from 'vscode-nls'; import { GitErrorCodes } from './api/git'; -const localize = nls.loadMessageBundle(); - -function isRemoteOperation(operation: Operation): boolean { - return operation === Operation.Pull || operation === Operation.Push || operation === Operation.Sync || operation === Operation.Fetch; -} - export class AutoFetcher { private static DidInformUser = 'autofetch.didInformUser'; @@ -33,7 +26,7 @@ export class AutoFetcher { workspace.onDidChangeConfiguration(this.onConfiguration, this, this.disposables); this.onConfiguration(); - const onGoodRemoteOperation = filterEvent(repository.onDidRunOperation, ({ operation, error }) => !error && isRemoteOperation(operation)); + const onGoodRemoteOperation = filterEvent(repository.onDidRunOperation, ({ operation, error }) => !error && operation.remote); const onFirstGoodRemoteOperation = onceEvent(onGoodRemoteOperation); onFirstGoodRemoteOperation(this.onFirstGoodRemoteOperation, this, this.disposables); } @@ -51,11 +44,10 @@ export class AutoFetcher { return; } - const yes: MessageItem = { title: localize('yes', "Yes") }; - const no: MessageItem = { isCloseAffordance: true, title: localize('no', "No") }; - const askLater: MessageItem = { title: localize('not now', "Ask Me Later") }; - // {{SQL CARBON EDIT}} - const result = await window.showInformationMessage(localize('suggest auto fetch', "Would you like Azure Data Studio to [periodically run 'git fetch']({0})?", 'https://go.microsoft.com/fwlink/?linkid=865294'), yes, no, askLater); + const yes: MessageItem = { title: l10n.t('Yes') }; + const no: MessageItem = { isCloseAffordance: true, title: l10n.t('No') }; + const askLater: MessageItem = { title: l10n.t('Ask Me Later') }; + const result = await window.showInformationMessage(l10n.t('Would you like {0} to [periodically run "git fetch"]({1})?', env.appName, 'https://go.microsoft.com/fwlink/?linkid=865294'), yes, no, askLater); if (result === askLater) { return; @@ -115,7 +107,7 @@ export class AutoFetcher { try { if (this._fetchAll) { - await this.repository.fetchAll(); + await this.repository.fetchAll({ silent: true }); } else { await this.repository.fetchDefault({ silent: true }); } diff --git a/extensions/git/src/branchProtection.ts b/extensions/git/src/branchProtection.ts new file mode 100644 index 0000000000..97b82b91da --- /dev/null +++ b/extensions/git/src/branchProtection.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable, Event, EventEmitter, Uri, workspace } from 'vscode'; +import { BranchProtection, BranchProtectionProvider } from './api/git'; +import { dispose, filterEvent } from './util'; + +export interface IBranchProtectionProviderRegistry { + readonly onDidChangeBranchProtectionProviders: Event; + + getBranchProtectionProviders(root: Uri): BranchProtectionProvider[]; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; +} + +export class GitBranchProtectionProvider implements BranchProtectionProvider { + + private readonly _onDidChangeBranchProtection = new EventEmitter(); + onDidChangeBranchProtection = this._onDidChangeBranchProtection.event; + + private branchProtection!: BranchProtection; + + private disposables: Disposable[] = []; + + constructor(private readonly repositoryRoot: Uri) { + const onDidChangeBranchProtectionEvent = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchProtection', repositoryRoot)); + onDidChangeBranchProtectionEvent(this.updateBranchProtection, this, this.disposables); + this.updateBranchProtection(); + } + + provideBranchProtection(): BranchProtection[] { + return [this.branchProtection]; + } + + private updateBranchProtection(): void { + const scopedConfig = workspace.getConfiguration('git', this.repositoryRoot); + const branchProtectionConfig = scopedConfig.get('branchProtection') ?? []; + const branchProtectionValues = Array.isArray(branchProtectionConfig) ? branchProtectionConfig : [branchProtectionConfig]; + + const branches = branchProtectionValues + .map(bp => typeof bp === 'string' ? bp.trim() : '') + .filter(bp => bp !== ''); + + this.branchProtection = { remote: '', rules: [{ include: branches }] }; + this._onDidChangeBranchProtection.fire(this.repositoryRoot); + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 5ac9475237..6e6839bc84 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,41 +5,43 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge } from 'vscode'; +import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; -import * as nls from 'vscode-nls'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; -import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher } from './api/git'; +import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git'; import { Git, Stash } from './git'; import { Model } from './model'; import { Repository, Resource, ResourceGroupType } from './repository'; import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri'; import { grep, isDescendant, pathEquals, relativePath } from './util'; -import { LogLevel, OutputChannelLogger } from './log'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; -import { pickRemoteSource } from './remoteSource'; - -const localize = nls.loadMessageBundle(); +import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; +import { RemoteSourceAction } from './api/git-base'; class CheckoutItem implements QuickPickItem { protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); } - get label(): string { return `${this.repository.isBranchProtected(this.ref.name ?? '') ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } + get label(): string { return `${this.repository.isBranchProtected(this.ref) ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; } get description(): string { return this.shortCommit; } get refName(): string | undefined { return this.ref.name; } + get refRemote(): string | undefined { return this.ref.remote; } + get buttons(): QuickInputButton[] | undefined { return this._buttons; } + set buttons(newButtons: QuickInputButton[] | undefined) { this._buttons = newButtons; } - constructor(protected repository: Repository, protected ref: Ref) { } + constructor(protected repository: Repository, protected ref: Ref, protected _buttons?: QuickInputButton[]) { } async run(opts?: { detached?: boolean }): Promise { - const ref = this.ref.name; - - if (!ref) { + if (!this.ref.name) { return; } - await this.repository.checkout(ref, opts); + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const pullBeforeCheckout = config.get('pullBeforeCheckout', false) === true; + + const treeish = opts?.detached ? this.ref.commit ?? this.ref.name : this.ref.name; + await this.repository.checkout(treeish, { ...opts, pullBeforeCheckout }); } } @@ -47,7 +49,15 @@ class CheckoutTagItem extends CheckoutItem { override get label(): string { return `$(tag) ${this.ref.name || this.shortCommit}`; } override get description(): string { - return localize('tag at', "Tag at {0}", this.shortCommit); + return l10n.t('Tag at {0}', this.shortCommit); + } + + override async run(opts?: { detached?: boolean }): Promise { + if (!this.ref.name) { + return; + } + + await this.repository.checkout(this.ref.name, opts); } } @@ -55,7 +65,7 @@ class CheckoutRemoteHeadItem extends CheckoutItem { override get label(): string { return `$(cloud) ${this.ref.name || this.shortCommit}`; } override get description(): string { - return localize('remote branch at', "Remote branch at {0}", this.shortCommit); + return l10n.t('Remote branch at {0}', this.shortCommit); } override async run(opts?: { detached?: boolean }): Promise { @@ -63,6 +73,11 @@ class CheckoutRemoteHeadItem extends CheckoutItem { return; } + if (opts?.detached) { + await this.repository.checkout(this.ref.commit ?? this.ref.name, opts); + return; + } + const branches = await this.repository.findTrackingBranches(this.ref.name); if (branches.length > 0) { @@ -117,19 +132,19 @@ class RebaseItem implements QuickPickItem { } class CreateBranchItem implements QuickPickItem { - get label(): string { return '$(plus) ' + localize('create branch', 'Create new branch...'); } + get label(): string { return '$(plus) ' + l10n.t('Create new branch...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } } class CreateBranchFromItem implements QuickPickItem { - get label(): string { return '$(plus) ' + localize('create branch from', 'Create new branch from...'); } + get label(): string { return '$(plus) ' + l10n.t('Create new branch from...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } } class CheckoutDetachedItem implements QuickPickItem { - get label(): string { return '$(debug-disconnect) ' + localize('checkout detached', 'Checkout detached...'); } + get label(): string { return '$(debug-disconnect) ' + l10n.t('Checkout detached...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } } @@ -148,7 +163,7 @@ class AddRemoteItem implements QuickPickItem { constructor(private cc: CommandCenter) { } - get label(): string { return '$(plus) ' + localize('add remote', 'Add a new remote...'); } + get label(): string { return '$(plus) ' + l10n.t('Add a new remote...'); } get description(): string { return ''; } get alwaysShow(): boolean { return true; } @@ -158,6 +173,36 @@ class AddRemoteItem implements QuickPickItem { } } +class RemoteItem implements QuickPickItem { + get label() { return `$(cloud) ${this.remote.name}`; } + get description(): string | undefined { return this.remote.fetchUrl; } + get remoteName(): string { return this.remote.name; } + + constructor(private readonly repository: Repository, private readonly remote: Remote) { } + + async run(): Promise { + await this.repository.fetch({ remote: this.remote.name }); + } +} + +class FetchAllRemotesItem implements QuickPickItem { + get label(): string { return l10n.t('{0} Fetch all remotes', '$(cloud-download)'); } + + constructor(private readonly repository: Repository) { } + + async run(): Promise { + await this.repository.fetch({ all: true }); + } +} + +class RepositoryItem implements QuickPickItem { + get label(): string { + return `$(repo) ${this.path}`; + } + + constructor(public readonly path: string) { } +} + interface ScmCommandOptions { repository?: boolean; diff?: boolean; @@ -209,7 +254,7 @@ async function categorizeResourceByResolution(resources: Resource[]): Promise<{ return { merge, resolved, unresolved, deletionConflicts }; } -function createCheckoutItems(repository: Repository): CheckoutItem[] { +async function createCheckoutItems(repository: Repository, detached = false): Promise { const config = workspace.getConfiguration('git'); const checkoutTypeConfig = config.get('checkoutType'); let checkoutTypes: string[]; @@ -222,16 +267,69 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] { checkoutTypes = checkoutTypeConfig; } + if (detached) { + // Remove tags when in detached mode + checkoutTypes = checkoutTypes.filter(t => t !== 'tags'); + } + + const refs = await repository.getRefs(); const processors = checkoutTypes.map(type => getCheckoutProcessor(repository, type)) .filter(p => !!p) as CheckoutProcessor[]; - for (const ref of repository.refs) { + for (const ref of refs) { for (const processor of processors) { processor.onRef(ref); } } - return processors.reduce((r, p) => r.concat(...p.items), []); + const buttons = await getRemoteRefItemButtons(repository); + let fallbackRemoteButtons: RemoteSourceActionButton[] | undefined = []; + const remote = repository.remotes.find(r => r.pushUrl === repository.HEAD?.remote || r.fetchUrl === repository.HEAD?.remote) ?? repository.remotes[0]; + const remoteUrl = remote?.pushUrl ?? remote?.fetchUrl; + if (remoteUrl) { + fallbackRemoteButtons = buttons.get(remoteUrl); + } + + return processors.reduce((r, p) => r.concat(...p.items.map((item) => { + if (item.refRemote) { + const matchingRemote = repository.remotes.find((remote) => remote.name === item.refRemote); + const remoteUrl = matchingRemote?.pushUrl ?? matchingRemote?.fetchUrl; + if (remoteUrl) { + item.buttons = buttons.get(item.refRemote); + } + } + + item.buttons = fallbackRemoteButtons; + return item; + })), []); +} + +type RemoteSourceActionButton = { + iconPath: ThemeIcon; + tooltip: string; + actual: RemoteSourceAction; +}; + +async function getRemoteRefItemButtons(repository: Repository) { + // Compute actions for all known remotes + const remoteUrlsToActions = new Map(); + + const getButtons = async (remoteUrl: string) => (await getRemoteSourceActions(remoteUrl)).map((action) => ({ iconPath: new ThemeIcon(action.icon), tooltip: action.label, actual: action })); + + for (const remote of repository.remotes) { + if (remote.fetchUrl) { + const actions = remoteUrlsToActions.get(remote.fetchUrl) ?? []; + actions.push(...await getButtons(remote.fetchUrl)); + remoteUrlsToActions.set(remote.fetchUrl, actions); + } + if (remote.pushUrl && remote.pushUrl !== remote.fetchUrl) { + const actions = remoteUrlsToActions.get(remote.pushUrl) ?? []; + actions.push(...await getButtons(remote.pushUrl)); + remoteUrlsToActions.set(remote.pushUrl, actions); + } + } + + return remoteUrlsToActions; } class CheckoutProcessor { @@ -260,13 +358,17 @@ function getCheckoutProcessor(repository: Repository, type: string): CheckoutPro return undefined; } +function sanitizeBranchName(name: string, whitespaceChar: string): string { + return name ? name.trim().replace(/^-+/, '').replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, whitespaceChar) : name; +} + function sanitizeRemoteName(name: string) { name = name.trim(); return name && name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, '-'); } class TagItem implements QuickPickItem { - get label(): string { return this.ref.name ?? ''; } + get label(): string { return `$(tag) ${this.ref.name ?? ''}`; } get description(): string { return this.ref.commit?.substr(0, 8) ?? ''; } constructor(readonly ref: Ref) { } } @@ -315,7 +417,8 @@ export class CommandCenter { constructor( private git: Git, private model: Model, - private outputChannelLogger: OutputChannelLogger, + private globalState: Memento, + private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter ) { this.disposables = Commands.map(({ commandId, key, method, options }) => { @@ -331,47 +434,9 @@ export class CommandCenter { this.disposables.push(workspace.registerTextDocumentContentProvider('git-output', this.commandErrors)); } - @command('git.setLogLevel') - async setLogLevel(): Promise { - const createItem = (logLevel: LogLevel) => { - let description: string | undefined; - const defaultDescription = localize('default', "Default"); - const currentDescription = localize('current', "Current"); - - if (logLevel === this.outputChannelLogger.defaultLogLevel && logLevel === this.outputChannelLogger.currentLogLevel) { - description = `${defaultDescription} & ${currentDescription} `; - } else if (logLevel === this.outputChannelLogger.defaultLogLevel) { - description = defaultDescription; - } else if (logLevel === this.outputChannelLogger.currentLogLevel) { - description = currentDescription; - } - - return { - label: LogLevel[logLevel], - logLevel, - description - }; - }; - - const items = [ - createItem(LogLevel.Trace), - createItem(LogLevel.Debug), - createItem(LogLevel.Info), - createItem(LogLevel.Warning), - createItem(LogLevel.Error), - createItem(LogLevel.Critical), - createItem(LogLevel.Off) - ]; - - const choice = await window.showQuickPick(items, { - placeHolder: localize('select log level', "Select log level") - }); - - if (!choice) { - return; - } - - this.outputChannelLogger.currentLogLevel = choice.logLevel; + @command('git.showOutput') + showOutput(): void { + this.logger.show(); } @command('git.refresh', { repository: true }) @@ -408,8 +473,14 @@ export class CommandCenter { } } - @command('_git.openMergeEditor') + @command('git.openMergeEditor') async openMergeEditor(uri: unknown) { + if (uri === undefined) { + // fallback to active editor... + if (window.tabGroups.activeTabGroup.activeTab?.input instanceof TabInputText) { + uri = window.tabGroups.activeTabGroup.activeTab.input.uri; + } + } if (!(uri instanceof Uri)) { return; } @@ -422,8 +493,23 @@ export class CommandCenter { type InputData = { uri: Uri; title?: string; detail?: string; description?: string }; const mergeUris = toMergeUris(uri); - const ours: InputData = { uri: mergeUris.ours, title: localize('Yours', 'Yours') }; - const theirs: InputData = { uri: mergeUris.theirs, title: localize('Theirs', 'Theirs') }; + + let isStashConflict = false; + try { + // Look at the conflict markers to check if this is a stash conflict + const document = await workspace.openTextDocument(uri); + const firstConflictInfo = findFirstConflictMarker(document); + isStashConflict = firstConflictInfo?.incomingChangeLabel === 'Stashed changes'; + } catch (error) { + console.error(error); + } + + const current: InputData = { uri: mergeUris.ours, title: l10n.t('Current') }; + const incoming: InputData = { uri: mergeUris.theirs, title: l10n.t('Incoming') }; + + if (isStashConflict) { + incoming.title = l10n.t('Stashed Changes'); + } try { const [head, rebaseOrMergeHead] = await Promise.all([ @@ -431,12 +517,14 @@ export class CommandCenter { isRebasing ? repo.getCommit('REBASE_HEAD') : repo.getCommit('MERGE_HEAD') ]); // ours (current branch and commit) - ours.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', '); - ours.description = '$(git-commit) ' + head.hash.substring(0, 7); + current.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', '); + current.description = '$(git-commit) ' + head.hash.substring(0, 7); + current.uri = toGitUri(uri, head.hash); // theirs - theirs.detail = rebaseOrMergeHead.refNames.join(', '); - theirs.description = '$(git-commit) ' + rebaseOrMergeHead.hash.substring(0, 7); + incoming.detail = rebaseOrMergeHead.refNames.join(', '); + incoming.description = '$(git-commit) ' + rebaseOrMergeHead.hash.substring(0, 7); + incoming.uri = toGitUri(uri, rebaseOrMergeHead.hash); } catch (error) { // not so bad, can continue with just uris @@ -446,8 +534,8 @@ export class CommandCenter { const options = { base: mergeUris.base, - input1: isRebasing ? ours : theirs, - input2: isRebasing ? theirs : ours, + input1: isRebasing ? current : incoming, + input2: isRebasing ? incoming : current, output: uri }; @@ -455,13 +543,46 @@ export class CommandCenter { '_open.mergeEditor', options ); + + function findFirstConflictMarker(doc: TextDocument): { currentChangeLabel: string; incomingChangeLabel: string } | undefined { + const conflictMarkerStart = '<<<<<<<'; + const conflictMarkerEnd = '>>>>>>>'; + let inConflict = false; + let currentChangeLabel: string = ''; + let incomingChangeLabel: string = ''; + let hasConflict = false; + + for (let lineIdx = 0; lineIdx < doc.lineCount; lineIdx++) { + const lineStr = doc.lineAt(lineIdx).text; + if (!inConflict) { + if (lineStr.startsWith(conflictMarkerStart)) { + currentChangeLabel = lineStr.substring(conflictMarkerStart.length).trim(); + inConflict = true; + hasConflict = true; + } + } else { + if (lineStr.startsWith(conflictMarkerEnd)) { + incomingChangeLabel = lineStr.substring(conflictMarkerStart.length).trim(); + inConflict = false; + break; + } + } + } + if (hasConflict) { + return { + currentChangeLabel, + incomingChangeLabel + }; + } + return undefined; + } } - async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise { + async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string } = {}): Promise { if (!url || typeof url !== 'string') { url = await pickRemoteSource({ - providerLabel: provider => localize('clonefrom', "Clone from {0}", provider.name), - urlLabel: localize('repourl', "Clone from URL") + providerLabel: provider => l10n.t('Clone from {0}', provider.name), + urlLabel: l10n.t('Clone from URL') }); } @@ -488,7 +609,8 @@ export class CommandCenter { canSelectFolders: true, canSelectMany: false, defaultUri: Uri.file(defaultCloneDirectory), - openLabel: localize('selectFolder', "Select Repository Location") + title: l10n.t('Choose a folder to clone {0} into', url), + openLabel: l10n.t('Select as Repository Destination') }); if (!uris || uris.length === 0) { @@ -509,13 +631,13 @@ export class CommandCenter { try { const opts = { location: ProgressLocation.Notification, - title: localize('cloning', "Cloning git repository '{0}'...", url), + title: l10n.t('Cloning git repository "{0}"...', url), cancellable: true }; const repositoryPath = await window.withProgress( opts, - (progress, token) => this.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive }, token) + (progress, token) => this.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) ); const config = workspace.getConfiguration('git'); @@ -533,18 +655,18 @@ export class CommandCenter { } 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"); + let message = l10n.t('Would you like to open the cloned repository?'); + const open = l10n.t('Open'); + const openNewWindow = l10n.t('Open in New Window'); const choices = [open, openNewWindow]; - const addToWorkspace = localize('add', "Add to Workspace"); + const addToWorkspace = l10n.t('Add to Workspace'); if (workspace.workspaceFolders) { - message = localize('proposeopen2', "Would you like to open the cloned repository, or add it to the current workspace?"); + message = l10n.t('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); + const result = await window.showInformationMessage(message, { modal: true }, ...choices); action = result === open ? PostCloneAction.Open : result === openNewWindow ? PostCloneAction.OpenNewWindow @@ -594,9 +716,47 @@ export class CommandCenter { } } + @command('git.continueInLocalClone') + async continueInLocalClone(): Promise { + if (this.model.repositories.length === 0) { return; } + + // Pick a single repository to continue working on in a local clone if there's more than one + const items = this.model.repositories.reduce<(QuickPickItem & { repository: Repository })[]>((items, repository) => { + const remote = repository.remotes.find((r) => r.name === repository.HEAD?.upstream?.remote); + if (remote?.pushUrl) { + items.push({ repository: repository, label: remote.pushUrl }); + } + return items; + }, []); + + let selection = items[0]; + if (items.length > 1) { + const pick = await window.showQuickPick(items, { canPickMany: false, placeHolder: l10n.t('Choose which repository to clone') }); + if (pick === undefined) { return; } + selection = pick; + } + + const uri = selection.label; + const ref = selection.repository.HEAD?.upstream?.name; + + if (uri !== undefined) { + // Launch desktop client if currently in web + if (env.uiKind === UIKind.Web) { + let target = `${env.uriScheme}://vscode.git/clone?url=${encodeURIComponent(uri)}`; + if (ref !== undefined) { + target += `&ref=${encodeURIComponent(ref)}`; + } + return Uri.parse(target); + } + + // If already in desktop client, directly clone + void this.clone(uri, undefined, { ref: ref }); + } + } + @command('git.clone') - async clone(url?: string, parentPath?: string): Promise { - await this.cloneRepository(url, parentPath); + async clone(url?: string, parentPath?: string, options?: { ref?: string }): Promise { + await this.cloneRepository(url, parentPath, options); } @command('git.cloneRecursive') @@ -614,8 +774,8 @@ export class CommandCenter { repositoryPath = workspace.workspaceFolders[0].uri.fsPath; askToOpen = false; } else { - const placeHolder = localize('init', "Pick workspace folder to initialize git repo in"); - const pick = { label: localize('choose', "Choose Folder...") }; + const placeHolder = l10n.t('Pick workspace folder to initialize git repo in'); + const pick = { label: l10n.t('Choose Folder...') }; const items: { label: string; folder?: WorkspaceFolder }[] = [ ...workspace.workspaceFolders.map(folder => ({ label: folder.name, description: folder.uri.fsPath, folder })), pick @@ -642,7 +802,7 @@ export class CommandCenter { canSelectFolders: true, canSelectMany: false, defaultUri, - openLabel: localize('init repo', "Initialize Repository") + openLabel: l10n.t('Initialize Repository') }); if (!result || result.length === 0) { @@ -652,8 +812,8 @@ export class CommandCenter { const uri = result[0]; if (homeUri.toString().startsWith(uri.toString())) { - const yes = localize('create repo', "Initialize Repository"); - const answer = await window.showWarningMessage(localize('are you sure', "This will create a Git repository in '{0}'. Are you sure you want to continue?", uri.fsPath), yes); + const yes = l10n.t('Initialize Repository'); + const answer = await window.showWarningMessage(l10n.t('This will create a Git repository in "{0}". Are you sure you want to continue?', uri.fsPath), yes); if (answer !== yes) { return; @@ -667,20 +827,24 @@ export class CommandCenter { } } - await this.git.init(repositoryPath); + const config = workspace.getConfiguration('git'); + const defaultBranchName = config.get('defaultBranchName', 'main'); + const branchWhitespaceChar = config.get('branchWhitespaceChar', '-'); - let message = localize('proposeopen init', "Would you like to open the initialized repository?"); - const open = localize('openrepo', "Open"); - const openNewWindow = localize('openreponew', "Open in New Window"); + await this.git.init(repositoryPath, { defaultBranch: sanitizeBranchName(defaultBranchName, branchWhitespaceChar) }); + + let message = l10n.t('Would you like to open the initialized repository?'); + const open = l10n.t('Open'); + const openNewWindow = l10n.t('Open in New Window'); const choices = [open, openNewWindow]; if (!askToOpen) { return; } - const addToWorkspace = localize('add', "Add to Workspace"); + const addToWorkspace = l10n.t('Add to Workspace'); if (workspace.workspaceFolders) { - message = localize('proposeopen2 init', "Would you like to open the initialized repository, or add it to the current workspace?"); + message = l10n.t('Would you like to open the initialized repository, or add it to the current workspace?'); choices.push(addToWorkspace); } @@ -706,7 +870,7 @@ export class CommandCenter { canSelectFolders: true, canSelectMany: false, defaultUri: Uri.file(os.homedir()), - openLabel: localize('open repo', "Open Repository") + openLabel: l10n.t('Open Repository') }); if (!result || result.length === 0) { @@ -778,7 +942,7 @@ export class CommandCenter { const document = window.activeTextEditor?.document; // If the document doesn't match what we opened then don't attempt to select the range - // Additioanlly if there was no previous document we don't have information to select a range + // Additionally if there was no previous document we don't have information to select a range if (document?.uri.toString() !== uri.toString() || !activeTextEditor || !previousURI || !previousSelection) { continue; } @@ -824,7 +988,7 @@ export class CommandCenter { const title = `${basename} (HEAD)`; if (!HEAD) { - window.showWarningMessage(localize('HEAD not available', "HEAD version of '{0}' is not available.", path.basename(resource.resourceUri.fsPath))); + window.showWarningMessage(l10n.t('HEAD version of "{0}" is not available.', path.basename(resource.resourceUri.fsPath))); return; } @@ -892,14 +1056,14 @@ export class CommandCenter { @command('git.stage') async stage(...resourceStates: SourceControlResourceState[]): Promise { - this.outputChannelLogger.logDebug(`git.stage ${resourceStates.length} `); + this.logger.debug(`git.stage ${resourceStates.length} `); resourceStates = resourceStates.filter(s => !!s); if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) { const resource = this.getSCMResource(); - this.outputChannelLogger.logDebug(`git.stage.getSCMResource ${resource ? resource.resourceUri.toString() : null} `); + this.logger.debug(`git.stage.getSCMResource ${resource ? resource.resourceUri.toString() : null} `); if (!resource) { return; @@ -913,10 +1077,10 @@ export class CommandCenter { if (unresolved.length > 0) { const message = unresolved.length > 1 - ? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", unresolved.length) - : localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(unresolved[0].resourceUri.fsPath)); + ? l10n.t('Are you sure you want to stage {0} files with merge conflicts?', unresolved.length) + : l10n.t('Are you sure you want to stage {0} with merge conflicts?', path.basename(unresolved[0].resourceUri.fsPath)); - const yes = localize('yes', "Yes"); + const yes = l10n.t('Yes'); const pick = await window.showWarningMessage(message, { modal: true }, yes); if (pick !== yes) { @@ -942,7 +1106,7 @@ export class CommandCenter { const untracked = selection.filter(s => s.resourceGroupType === ResourceGroupType.Untracked); const scmResources = [...workingTree, ...untracked, ...resolved, ...unresolved]; - this.outputChannelLogger.logDebug(`git.stage.scmResources ${scmResources.length} `); + this.logger.debug(`git.stage.scmResources ${scmResources.length} `); if (!scmResources.length) { return; } @@ -972,9 +1136,9 @@ export class CommandCenter { } if (resource.type === Status.DELETED_BY_THEM) { - const keepIt = localize('keep ours', "Keep Our Version"); - const deleteIt = localize('delete', "Delete File"); - const result = await window.showInformationMessage(localize('deleted by them', "File '{0}' was deleted by them and modified by us.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt); + const keepIt = l10n.t('Keep Our Version'); + const deleteIt = l10n.t('Delete File'); + const result = await window.showInformationMessage(l10n.t('File "{0}" was deleted by them and modified by us.\n\nWhat would you like to do?', path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt); if (result === keepIt) { await repository.add([uri]); @@ -984,9 +1148,9 @@ export class CommandCenter { throw new Error('Cancelled'); } } else if (resource.type === Status.DELETED_BY_US) { - const keepIt = localize('keep theirs', "Keep Their Version"); - const deleteIt = localize('delete', "Delete File"); - const result = await window.showInformationMessage(localize('deleted by us', "File '{0}' was deleted by us and modified by them.\n\nWhat would you like to do?", path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt); + const keepIt = l10n.t('Keep Their Version'); + const deleteIt = l10n.t('Delete File'); + const result = await window.showInformationMessage(l10n.t('File "{0}" was deleted by us and modified by them.\n\nWhat would you like to do?', path.basename(uri.fsPath)), { modal: true }, keepIt, deleteIt); if (result === keepIt) { await repository.add([uri]); @@ -1035,10 +1199,10 @@ export class CommandCenter { if (unresolved.length > 0) { const message = unresolved.length > 1 - ? localize('confirm stage files with merge conflicts', "Are you sure you want to stage {0} files with merge conflicts?", merge.length) - : localize('confirm stage file with merge conflicts', "Are you sure you want to stage {0} with merge conflicts?", path.basename(merge[0].resourceUri.fsPath)); + ? l10n.t('Are you sure you want to stage {0} files with merge conflicts?', merge.length) + : l10n.t('Are you sure you want to stage {0} with merge conflicts?', path.basename(merge[0].resourceUri.fsPath)); - const yes = localize('yes', "Yes"); + const yes = l10n.t('Yes'); const pick = await window.showWarningMessage(message, { modal: true }, yes); if (pick !== yes) { @@ -1067,7 +1231,7 @@ export class CommandCenter { await this._stageChanges(textEditor, [changes[index]]); - const firstStagedLine = changes[index].modifiedStartLineNumber - 1; + const firstStagedLine = changes[index].modifiedStartLineNumber; textEditor.selections = [new Selection(firstStagedLine, 0, firstStagedLine, 0)]; } @@ -1086,6 +1250,7 @@ export class CommandCenter { .filter(d => !!d) as LineChange[]; if (!selectedChanges.length) { + window.showInformationMessage(l10n.t('The selection range does not contain any changes.')); return; } @@ -1093,25 +1258,42 @@ export class CommandCenter { } @command('git.acceptMerge') - async acceptMerge(uri: Uri | unknown): Promise { - if (!(uri instanceof Uri)) { - return; - } - const repository = this.model.getRepository(uri); - if (!repository) { - console.log(`FAILED to accept merge because uri ${uri.toString()} doesn't belong to any repository`); - return; - } - + async acceptMerge(_uri: Uri | unknown): Promise { const { activeTab } = window.tabGroups.activeTabGroup; if (!activeTab) { return; } + if (!(activeTab.input instanceof TabInputTextMerge)) { + return; + } + + const uri = activeTab.input.result; + + const repository = this.model.getRepository(uri); + if (!repository) { + console.log(`FAILED to complete merge because uri ${uri.toString()} doesn't belong to any repository`); + return; + } + + const result = await commands.executeCommand('mergeEditor.acceptMerge') as { successful: boolean }; + if (result.successful) { + await repository.add([uri]); + await commands.executeCommand('workbench.view.scm'); + } + + /* + if (!(uri instanceof Uri)) { + return; + } + + + + // make sure to save the merged document const doc = workspace.textDocuments.find(doc => doc.uri.toString() === uri.toString()); if (!doc) { - console.log(`FAILED to accept merge because uri ${uri.toString()} doesn't match a document`); + console.log(`FAILED to complete merge because uri ${uri.toString()} doesn't match a document`); return; } if (doc.isDirty) { @@ -1130,7 +1312,52 @@ export class CommandCenter { if (didCloseTab) { await repository.add([uri]); await commands.executeCommand('workbench.view.scm'); + }*/ + } + + @command('git.runGitMerge') + async runGitMergeNoDiff3(): Promise { + await this.runGitMerge(false); + } + + @command('git.runGitMergeDiff3') + async runGitMergeDiff3(): Promise { + await this.runGitMerge(true); + } + + private async runGitMerge(diff3: boolean): Promise { + const { activeTab } = window.tabGroups.activeTabGroup; + if (!activeTab) { + return; } + + const input = activeTab.input; + if (!(input instanceof TabInputTextMerge)) { + return; + } + + const result = await this.git.mergeFile({ + basePath: input.base.fsPath, + input1Path: input.input1.fsPath, + input2Path: input.input2.fsPath, + diff3, + }); + + const doc = workspace.textDocuments.find(doc => doc.uri.toString() === input.result.toString()); + if (!doc) { + return; + } + const e = new WorkspaceEdit(); + + e.replace( + input.result, + new Range( + new Position(0, 0), + new Position(doc.lineCount, 0), + ), + result + ); + await workspace.applyEdit(e); } private async _stageChanges(textEditor: TextEditor, changes: LineChange[]): Promise { @@ -1162,7 +1389,7 @@ export class CommandCenter { await this._revertChanges(textEditor, [...changes.slice(0, index), ...changes.slice(index + 1)]); - const firstStagedLine = changes[index].modifiedStartLineNumber - 1; + const firstStagedLine = changes[index].modifiedStartLineNumber; textEditor.selections = [new Selection(firstStagedLine, 0, firstStagedLine, 0)]; } @@ -1182,6 +1409,7 @@ export class CommandCenter { }); if (selectedChanges.length === changes.length) { + window.showInformationMessage(l10n.t('The selection range does not contain any changes.')); return; } @@ -1271,6 +1499,7 @@ export class CommandCenter { .filter(d => !!d) as LineChange[]; if (!selectedDiffs.length) { + window.showInformationMessage(l10n.t('The selection range does not contain any changes.')); return; } @@ -1282,7 +1511,20 @@ export class CommandCenter { @command('git.clean') async clean(...resourceStates: SourceControlResourceState[]): Promise { - resourceStates = resourceStates.filter(s => !!s); + // Remove duplicate resources + const resourceUris = new Set(); + resourceStates = resourceStates.filter(s => { + if (s === undefined) { + return false; + } + + if (resourceUris.has(s.resourceUri.toString())) { + return false; + } + + resourceUris.add(s.resourceUri.toString()); + return true; + }); if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) { const resource = this.getSCMResource(); @@ -1303,30 +1545,30 @@ export class CommandCenter { const untrackedCount = scmResources.reduce((s, r) => s + (r.type === Status.UNTRACKED ? 1 : 0), 0); let message: string; - let yes = localize('discard', "Discard Changes"); + let yes = l10n.t('Discard Changes'); if (scmResources.length === 1) { if (untrackedCount > 0) { - message = localize('confirm delete', "Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST if you proceed.", path.basename(scmResources[0].resourceUri.fsPath)); - yes = localize('delete file', "Delete file"); + message = l10n.t('Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST if you proceed.', path.basename(scmResources[0].resourceUri.fsPath)); + yes = l10n.t('Delete file'); } else { if (scmResources[0].type === Status.DELETED) { - yes = localize('restore file', "Restore file"); - message = localize('confirm restore', "Are you sure you want to restore {0}?", path.basename(scmResources[0].resourceUri.fsPath)); + yes = l10n.t('Restore file'); + message = l10n.t('Are you sure you want to restore {0}?', path.basename(scmResources[0].resourceUri.fsPath)); } else { - message = localize('confirm discard', "Are you sure you want to discard changes in {0}?", path.basename(scmResources[0].resourceUri.fsPath)); + message = l10n.t('Are you sure you want to discard changes in {0}?', path.basename(scmResources[0].resourceUri.fsPath)); } } } else { if (scmResources.every(resource => resource.type === Status.DELETED)) { - yes = localize('restore files', "Restore files"); - message = localize('confirm restore multiple', "Are you sure you want to restore {0} files?", scmResources.length); + yes = l10n.t('Restore files'); + message = l10n.t('Are you sure you want to restore {0} files?', scmResources.length); } else { - message = localize('confirm discard multiple', "Are you sure you want to discard changes in {0} files?", scmResources.length); + message = l10n.t('Are you sure you want to discard changes in {0} files?', scmResources.length); } if (untrackedCount > 0) { - message = `${message}\n\n${localize('warn untracked', "This will DELETE {0} untracked files!\nThis is IRREVERSIBLE!\nThese files will be FOREVER LOST.", untrackedCount)}`; + message = `${message}\n\n${l10n.t('This will DELETE {0} untracked files!\nThis is IRREVERSIBLE!\nThese files will be FOREVER LOST.', untrackedCount)}`; } } @@ -1359,16 +1601,16 @@ export class CommandCenter { await this._cleanUntrackedChanges(repository, resources); } else { // resources.length > 1 && untrackedResources.length > 0 && trackedResources.length > 0 const untrackedMessage = untrackedResources.length === 1 - ? localize('there are untracked files single', "The following untracked file will be DELETED FROM DISK if discarded: {0}.", path.basename(untrackedResources[0].resourceUri.fsPath)) - : localize('there are untracked files', "There are {0} untracked files which will be DELETED FROM DISK if discarded.", untrackedResources.length); + ? l10n.t('The following untracked file will be DELETED FROM DISK if discarded: {0}.', path.basename(untrackedResources[0].resourceUri.fsPath)) + : l10n.t('There are {0} untracked files which will be DELETED FROM DISK if discarded.', untrackedResources.length); - const message = localize('confirm discard all 2', "{0}\n\nThis is IRREVERSIBLE, your current working set will be FOREVER LOST.", untrackedMessage, resources.length); + const message = l10n.t('{0}\n\nThis is IRREVERSIBLE, your current working set will be FOREVER LOST.', untrackedMessage, resources.length); const yesTracked = trackedResources.length === 1 - ? localize('yes discard tracked', "Discard 1 Tracked File", trackedResources.length) - : localize('yes discard tracked multiple', "Discard {0} Tracked Files", trackedResources.length); + ? l10n.t('Discard 1 Tracked File', trackedResources.length) + : l10n.t('Discard {0} Tracked Files', trackedResources.length); - const yesAll = localize('discardAll', "Discard All {0} Files", resources.length); + const yesAll = l10n.t('Discard All {0} Files', resources.length); const pick = await window.showWarningMessage(message, { modal: true }, yesTracked, yesAll); if (pick === yesTracked) { @@ -1411,11 +1653,11 @@ export class CommandCenter { private async _cleanTrackedChanges(repository: Repository, resources: Resource[]): Promise { const message = resources.length === 1 - ? localize('confirm discard all single', "Are you sure you want to discard changes in {0}?", path.basename(resources[0].resourceUri.fsPath)) - : localize('confirm discard all', "Are you sure you want to discard ALL changes in {0} files?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST if you proceed.", resources.length); + ? l10n.t('Are you sure you want to discard changes in {0}?', path.basename(resources[0].resourceUri.fsPath)) + : l10n.t('Are you sure you want to discard ALL changes in {0} files?\nThis is IRREVERSIBLE!\nYour current working set will be FOREVER LOST if you proceed.', resources.length); const yes = resources.length === 1 - ? localize('discardAll multiple', "Discard 1 File") - : localize('discardAll', "Discard All {0} Files", resources.length); + ? l10n.t('Discard 1 File') + : l10n.t('Discard All {0} Files', resources.length); const pick = await window.showWarningMessage(message, { modal: true }, yes); if (pick !== yes) { @@ -1426,8 +1668,8 @@ export class CommandCenter { } private async _cleanUntrackedChange(repository: Repository, resource: Resource): Promise { - const message = localize('confirm delete', "Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST if you proceed.", path.basename(resource.resourceUri.fsPath)); - const yes = localize('delete file', "Delete file"); + const message = l10n.t('Are you sure you want to DELETE {0}?\nThis is IRREVERSIBLE!\nThis file will be FOREVER LOST if you proceed.', path.basename(resource.resourceUri.fsPath)); + const yes = l10n.t('Delete file'); const pick = await window.showWarningMessage(message, { modal: true }, yes); if (pick !== yes) { @@ -1438,8 +1680,8 @@ export class CommandCenter { } private async _cleanUntrackedChanges(repository: Repository, resources: Resource[]): Promise { - const message = localize('confirm delete multiple', "Are you sure you want to DELETE {0} files?\nThis is IRREVERSIBLE!\nThese files will be FOREVER LOST if you proceed.", resources.length); - const yes = localize('delete files', "Delete Files"); + const message = l10n.t('Are you sure you want to DELETE {0} files?\nThis is IRREVERSIBLE!\nThese files will be FOREVER LOST if you proceed.', resources.length); + const yes = l10n.t('Delete Files'); const pick = await window.showWarningMessage(message, { modal: true }, yes); if (pick !== yes) { @@ -1453,7 +1695,7 @@ export class CommandCenter { repository: Repository, getCommitMessage: () => Promise, opts: CommitOptions - ): Promise { + ): Promise { const config = workspace.getConfiguration('git', Uri.file(repository.root)); let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit'); @@ -1480,10 +1722,10 @@ export class CommandCenter { if (documents.length > 0) { const message = documents.length === 1 - ? 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 Staged Changes"); + ? l10n.t('The following file has unsaved changes which won\'t be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?', path.basename(documents[0].uri.fsPath)) + : l10n.t('There are {0} unsaved files.\n\nWould you like to save them before committing?', documents.length); + const saveAndCommit = l10n.t('Save All & Commit'); + const commit = l10n.t('Commit Staged Changes'); const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit); if (pick === saveAndCommit) { @@ -1493,7 +1735,7 @@ export class CommandCenter { noStagedChanges = repository.indexGroup.resourceStates.length === 0; noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; } else if (pick !== commit) { - return false; // do not commit on cancel + return; // do not commit on cancel } } } @@ -1503,23 +1745,23 @@ export class CommandCenter { const suggestSmartCommit = config.get('suggestSmartCommit') === true; if (!suggestSmartCommit) { - return false; + return; } // prompt the user if we want to commit all or not - const message = localize('no staged changes', "There are no staged changes to commit.\n\nWould you like to stage all your changes and commit them directly?"); - const yes = localize('yes', "Yes"); - const always = localize('always', "Always"); - const never = localize('never', "Never"); + const message = l10n.t('There are no staged changes to commit.\n\nWould you like to stage all your changes and commit them directly?'); + const yes = l10n.t('Yes'); + const always = l10n.t('Always'); + const never = l10n.t('Never'); const pick = await window.showWarningMessage(message, { modal: true }, yes, always, never); if (pick === always) { config.update('enableSmartCommit', true, true); } else if (pick === never) { config.update('suggestSmartCommit', false, true); - return false; + return; } else if (pick !== yes) { - return false; // do not commit on cancel + return; // do not commit on cancel } } @@ -1561,11 +1803,11 @@ export class CommandCenter { // rebase not in progress && repository.rebaseCommit === undefined ) { - const commitAnyway = localize('commit anyway', "Create Empty Commit"); - const answer = await window.showInformationMessage(localize('no changes', "There are no changes to commit."), commitAnyway); + const commitAnyway = l10n.t('Create Empty Commit'); + const answer = await window.showInformationMessage(l10n.t('There are no changes to commit.'), commitAnyway); if (answer !== commitAnyway) { - return false; + return; } opts.empty = true; @@ -1573,20 +1815,20 @@ export class CommandCenter { if (opts.noVerify) { if (!config.get('allowNoVerifyCommit')) { - await window.showErrorMessage(localize('no verify commit not allowed', "Commits without verification are not allowed, please enable them with the 'git.allowNoVerifyCommit' setting.")); - return false; + await window.showErrorMessage(l10n.t('Commits without verification are not allowed, please enable them with the "git.allowNoVerifyCommit" setting.')); + return; } if (config.get('confirmNoVerifyCommit')) { - const message = localize('confirm no verify commit', "You are about to commit your changes without verification, this skips pre-commit hooks and can be undesirable.\n\nAre you sure to continue?"); - const yes = localize('ok', "OK"); - const neverAgain = localize('never ask again', "OK, Don't Ask Again"); + const message = l10n.t('You are about to commit your changes without verification, this skips pre-commit hooks and can be undesirable.\n\nAre you sure to continue?'); + const yes = l10n.t('OK'); + const neverAgain = l10n.t('OK, Don\'t Ask Again'); const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); if (pick === neverAgain) { config.update('confirmNoVerifyCommit', false, true); } else if (pick !== yes) { - return false; + return; } } } @@ -1594,7 +1836,7 @@ export class CommandCenter { const message = await getCommitMessage(); if (!message && !opts.amend && !opts.useEditor) { - return false; + return; } if (opts.all && smartCommitChanges === 'tracked') { @@ -1608,24 +1850,24 @@ export class CommandCenter { // Branch protection const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!; if (repository.isBranchProtected() && (branchProtectionPrompt === 'alwaysPrompt' || branchProtectionPrompt === 'alwaysCommitToNewBranch')) { - const commitToNewBranch = localize('commit to branch', "Commit to a New Branch"); + const commitToNewBranch = l10n.t('Commit to a New Branch'); let pick: string | undefined = commitToNewBranch; if (branchProtectionPrompt === 'alwaysPrompt') { - const message = localize('confirm branch protection commit', "You are trying to commit to a protected branch and you might not have permission to push your commits to the remote.\n\nHow would you like to proceed?"); - const commit = localize('commit changes', "Commit Anyway"); + const message = l10n.t('You are trying to commit to a protected branch and you might not have permission to push your commits to the remote.\n\nHow would you like to proceed?'); + const commit = l10n.t('Commit Anyway'); pick = await window.showWarningMessage(message, { modal: true }, commitToNewBranch, commit); } if (!pick) { - return false; + return; } else if (pick === commitToNewBranch) { const branchName = await this.promptForBranchName(repository); if (!branchName) { - return false; + return; } await repository.branch(branchName, true); @@ -1633,15 +1875,6 @@ export class CommandCenter { } await repository.commit(message, opts); - - // Execute post commit command - if (opts.postCommitCommand?.length) { - await commands.executeCommand( - opts.postCommitCommand, - new ApiRepository(repository)); - } - - return true; } private async commitWithAnyInput(repository: Repository, opts: CommitOptions): Promise { @@ -1663,15 +1896,15 @@ export class CommandCenter { let placeHolder: string; if (branchName) { - placeHolder = localize('commitMessageWithHeadLabel2', "Message (commit on '{0}')", branchName); + placeHolder = l10n.t('Message (commit on "{0}")', branchName); } else { - placeHolder = localize('commit message', "Commit message"); + placeHolder = l10n.t('Commit message'); } _message = await window.showInputBox({ value, placeHolder, - prompt: localize('provide commit message', "Please provide a commit message"), + prompt: l10n.t('Please provide a commit message'), ignoreFocusOut: true }); } @@ -1679,15 +1912,11 @@ export class CommandCenter { return _message; }; - const didCommit = await this.smartCommit(repository, getCommitMessage, opts); - - if (message && didCommit) { - repository.inputBox.value = await repository.getInputTemplate(); - } + await this.smartCommit(repository, getCommitMessage, opts); } @command('git.commit', { repository: true }) - async commit(repository: Repository, postCommitCommand?: string): Promise { + async commit(repository: Repository, postCommitCommand?: string | null): Promise { await this.commitWithAnyInput(repository, { postCommitCommand }); } @@ -1723,7 +1952,8 @@ export class CommandCenter { @command('git.commitMessageAccept') async commitMessageAccept(arg?: Uri): Promise { - if (!arg) { return; } + if (!arg && !window.activeTextEditor) { return; } + arg ??= window.activeTextEditor!.document.uri; // Close the tab this._closeEditorTab(arg); @@ -1731,11 +1961,12 @@ export class CommandCenter { @command('git.commitMessageDiscard') async commitMessageDiscard(arg?: Uri): Promise { - if (!arg) { return; } + if (!arg && !window.activeTextEditor) { return; } + arg ??= window.activeTextEditor!.document.uri; // Clear the contents of the editor const editors = window.visibleTextEditors - .filter(e => e.document.languageId === 'git-commit' && e.document.uri.toString() === arg.toString()); + .filter(e => e.document.languageId === 'git-commit' && e.document.uri.toString() === arg!.toString()); if (editors.length !== 1) { return; } @@ -1772,9 +2003,9 @@ export class CommandCenter { const shouldPrompt = config.get('confirmEmptyCommits') === true; if (shouldPrompt) { - const message = localize('confirm empty commit', "Are you sure you want to create an empty commit?"); - const yes = localize('yes', "Yes"); - const neverAgain = localize('yes never again', "Yes, Don't Show Again"); + const message = l10n.t('Are you sure you want to create an empty commit?'); + const yes = l10n.t('Yes'); + const neverAgain = l10n.t('Yes, Don\'t Show Again'); const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); if (pick === neverAgain) { @@ -1842,15 +2073,15 @@ export class CommandCenter { const HEAD = repository.HEAD; if (!HEAD || !HEAD.commit) { - window.showWarningMessage(localize('no more', "Can't undo because HEAD doesn't point to any commit.")); + window.showWarningMessage(l10n.t('Can\'t undo because HEAD doesn\'t point to any commit.')); return; } const commit = await repository.getCommit('HEAD'); if (commit.parents.length > 1) { - const yes = localize('undo commit', "Undo merge commit"); - const result = await window.showWarningMessage(localize('merge commit', "The last commit was a merge commit. Are you sure you want to undo it?"), { modal: true }, yes); + const yes = l10n.t('Undo merge commit'); + const result = await window.showWarningMessage(l10n.t('The last commit was a merge commit. Are you sure you want to undo it?'), { modal: true }, yes); if (result !== yes) { return; @@ -1889,20 +2120,32 @@ export class CommandCenter { const picks: QuickPickItem[] = []; if (!opts?.detached) { - picks.push(createBranch, createBranchFrom, checkoutDetached); + picks.push(createBranch, createBranchFrom, checkoutDetached, { label: '', kind: QuickPickItemKind.Separator }); } - picks.push(...createCheckoutItems(repository)); - const quickpick = window.createQuickPick(); - quickpick.items = picks; + quickpick.busy = true; 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'); + ? l10n.t('Select a branch to checkout in detached mode') + : l10n.t('Select a branch or tag to checkout'); quickpick.show(); - const choice = await new Promise(c => quickpick.onDidAccept(() => c(quickpick.activeItems[0]))); + picks.push(... await createCheckoutItems(repository, opts?.detached)); + quickpick.items = picks; + quickpick.busy = false; + + const choice = await new Promise(c => { + quickpick.onDidAccept(() => c(quickpick.activeItems[0])); + quickpick.onDidTriggerItemButton((e) => { + quickpick.hide(); + const button = e.button as QuickInputButton & { actual: RemoteSourceAction }; + const item = e.item as CheckoutItem; + if (button.actual && item.refName) { + button.actual.run(item.refRemote ? item.refName.substring(item.refRemote.length + 1) : item.refName); + } + }); + }); quickpick.hide(); if (!choice) { @@ -1925,17 +2168,22 @@ export class CommandCenter { 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); + const stash = l10n.t('Stash & Checkout'); + const migrate = l10n.t('Migrate Changes'); + const force = l10n.t('Force Checkout'); + const choice = await window.showWarningMessage(l10n.t('Your local changes would be overwritten by checkout.'), { modal: true }, stash, migrate, force); if (choice === force) { await this.cleanAll(repository); await item.run(opts); - } else if (choice === stash) { - await this.stash(repository); - await item.run(opts); - await this.stashPopLatest(repository); + } else if (choice === stash || choice === migrate) { + if (await this._stash(repository)) { + await item.run(opts); + + if (choice === migrate) { + await this.stashPopLatest(repository); + } + } } } } @@ -1953,7 +2201,7 @@ export class CommandCenter { await this._branch(repository, undefined, true); } - private generateRandomBranchName(repository: Repository, separator: string): string { + private async generateRandomBranchName(repository: Repository, separator: string): Promise { const config = workspace.getConfiguration('git'); const branchRandomNameDictionary = config.get('branchRandomName.dictionary')!; @@ -1986,7 +2234,8 @@ export class CommandCenter { }); // Check for local ref conflict - if (!repository.refs.find(r => r.type === RefType.Head && r.name === randomName)) { + const refs = await repository.getRefs({ pattern: `refs/heads/${randomName}` }); + if (refs.length === 0) { return randomName; } } @@ -1999,9 +2248,6 @@ export class CommandCenter { const branchPrefix = config.get('branchPrefix')!; const branchWhitespaceChar = config.get('branchWhitespaceChar')!; const branchValidationRegex = config.get('branchValidationRegex')!; - const sanitize = (name: string) => name ? - name.trim().replace(/^-+/, '').replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar) - : name; let rawBranchName = defaultName; @@ -2009,7 +2255,9 @@ export class CommandCenter { // Branch name if (!initialValue) { const branchRandomNameEnabled = config.get('branchRandomName.enable', false); - initialValue = `${branchPrefix}${branchRandomNameEnabled ? this.generateRandomBranchName(repository, branchWhitespaceChar) : ''}`; + const branchName = branchRandomNameEnabled ? await this.generateRandomBranchName(repository, branchWhitespaceChar) : ''; + + initialValue = `${branchPrefix}${branchName}`; } // Branch name selection @@ -2017,14 +2265,14 @@ export class CommandCenter { initialValue.startsWith(branchPrefix) ? [branchPrefix.length, initialValue.length] : undefined; rawBranchName = await window.showInputBox({ - placeHolder: localize('branch name', "Branch name"), - prompt: localize('provide branch name', "Please provide a new branch name"), + placeHolder: l10n.t('Branch name'), + prompt: l10n.t('Please provide a new branch name'), value: initialValue, valueSelection: initialValueSelection, ignoreFocusOut: true, validateInput: (name: string) => { const validateName = new RegExp(branchValidationRegex); - const sanitizedName = sanitize(name); + const sanitizedName = sanitizeBranchName(name, branchWhitespaceChar); if (validateName.test(sanitizedName)) { // If the sanitized name that we will use is different than what is // in the input box, show an info message to the user informing them @@ -2032,32 +2280,29 @@ export class CommandCenter { return name === sanitizedName ? null : { - message: localize('branch name does not match sanitized', "The new branch will be '{0}'", sanitizedName), + message: l10n.t('The new branch will be "{0}"', sanitizedName), severity: InputBoxValidationSeverity.Info }; } - return localize('branch name format invalid', "Branch name needs to match regex: {0}", branchValidationRegex); + return l10n.t('Branch name needs to match regex: {0}', branchValidationRegex); } }); } - return sanitize(rawBranchName || ''); + return sanitizeBranchName(rawBranchName || '', branchWhitespaceChar); } private async _branch(repository: Repository, defaultName?: string, from = false): Promise { - const branchName = await this.promptForBranchName(repository, defaultName); - - if (!branchName) { - return; - } - let target = 'HEAD'; if (from) { - const picks = [new HEADItem(repository), ...createCheckoutItems(repository)]; - const placeHolder = localize('select a ref to create a new branch from', 'Select a ref to create the \'{0}\' branch from', branchName); - const choice = await window.showQuickPick(picks, { placeHolder }); + const getRefPicks = async () => { + return [new HEADItem(repository), ...await createCheckoutItems(repository)]; + }; + + const placeHolder = l10n.t('Select a ref to create the branch from'); + const choice = await window.showQuickPick(getRefPicks(), { placeHolder }); if (!choice) { return; @@ -2068,6 +2313,12 @@ export class CommandCenter { } } + const branchName = await this.promptForBranchName(repository, defaultName); + + if (!branchName) { + return; + } + await repository.branch(branchName, true, target); } @@ -2077,12 +2328,15 @@ export class CommandCenter { if (typeof name === 'string') { run = force => repository.deleteBranch(name, force); } else { - const currentHead = repository.HEAD && repository.HEAD.name; - const heads = repository.refs.filter(ref => ref.type === RefType.Head && ref.name !== currentHead) - .map(ref => new BranchDeleteItem(ref)); + const getBranchPicks = async () => { + const refs = await repository.getRefs({ pattern: 'refs/heads' }); + const currentHead = repository.HEAD && repository.HEAD.name; - const placeHolder = localize('select branch to delete', 'Select a branch to delete'); - const choice = await window.showQuickPick(heads, { placeHolder }); + return refs.filter(ref => ref.name !== currentHead).map(ref => new BranchDeleteItem(ref)); + }; + + const placeHolder = l10n.t('Select a branch to delete'); + const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); if (!choice || !choice.branchName) { return; @@ -2098,8 +2352,8 @@ export class CommandCenter { throw err; } - const message = localize('confirm force delete branch', "The branch '{0}' is not fully merged. Delete anyway?", name); - const yes = localize('delete branch', "Delete Branch"); + const message = l10n.t('The branch "{0}" is not fully merged. Delete anyway?', name); + const yes = l10n.t('Delete Branch'); const pick = await window.showWarningMessage(message, { modal: true }, yes); if (pick === yes) { @@ -2122,10 +2376,10 @@ export class CommandCenter { } catch (err) { switch (err.gitErrorCode) { case GitErrorCodes.InvalidBranchName: - window.showErrorMessage(localize('invalid branch name', 'Invalid branch name')); + window.showErrorMessage(l10n.t('Invalid branch name')); return; case GitErrorCodes.BranchAlreadyExists: - window.showErrorMessage(localize('branch already exists', "A branch named '{0}' already exists", branchName)); + window.showErrorMessage(l10n.t('A branch named "{0}" already exists', branchName)); return; default: throw err; @@ -2139,17 +2393,22 @@ export class CommandCenter { 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) - .map(ref => new MergeItem(ref as Branch)); + const getBranchPicks = async (): Promise => { + const refs = await repository.getRefs(); - const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .filter(ref => ref.name || ref.commit) - .map(ref => new MergeItem(ref as Branch)); + const heads = refs.filter(ref => ref.type === RefType.Head) + .filter(ref => ref.name || ref.commit) + .map(ref => new MergeItem(ref as Branch)); - const picks = [...heads, ...remoteHeads]; - const placeHolder = localize('select a branch to merge from', 'Select a branch to merge from'); - const choice = await window.showQuickPick(picks, { placeHolder }); + const remoteHeads = (includeRemotes ? refs.filter(ref => ref.type === RefType.RemoteHead) : []) + .filter(ref => ref.name || ref.commit) + .map(ref => new MergeItem(ref as Branch)); + + return [...heads, ...remoteHeads]; + }; + + const placeHolder = l10n.t('Select a branch to merge from'); + const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); if (!choice) { return; @@ -2158,36 +2417,46 @@ export class CommandCenter { await choice.run(repository); } + @command('git.mergeAbort', { repository: true }) + async abortMerge(repository: Repository): Promise { + await repository.mergeAbort(); + } + @command('git.rebase', { repository: true }) async rebase(repository: Repository): Promise { const config = workspace.getConfiguration('git'); 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) - .filter(ref => ref.name || ref.commit); + const getBranchPicks = async () => { + const refs = await repository.getRefs(); - const remoteHeads = (includeRemotes ? repository.refs.filter(ref => ref.type === RefType.RemoteHead) : []) - .filter(ref => ref.name || ref.commit); + const heads = refs.filter(ref => ref.type === RefType.Head) + .filter(ref => ref.name !== repository.HEAD?.name) + .filter(ref => ref.name || ref.commit); - const picks = [...heads, ...remoteHeads] - .map(ref => new RebaseItem(ref)); + const remoteHeads = (includeRemotes ? refs.filter(ref => ref.type === RefType.RemoteHead) : []) + .filter(ref => ref.name || ref.commit); - // set upstream branch as first - if (repository.HEAD?.upstream) { - const upstreamName = `${repository.HEAD?.upstream.remote}/${repository.HEAD?.upstream.name}`; - const index = picks.findIndex(e => e.ref.name === upstreamName); + const picks = [...heads, ...remoteHeads].map(ref => new RebaseItem(ref)); - if (index > -1) { - const [ref] = picks.splice(index, 1); - ref.description = '(upstream)'; - picks.unshift(ref); + // set upstream branch as first + if (repository.HEAD?.upstream) { + const upstreamName = `${repository.HEAD?.upstream.remote}/${repository.HEAD?.upstream.name}`; + const index = picks.findIndex(e => e.ref.name === upstreamName); + + if (index > -1) { + const [ref] = picks.splice(index, 1); + ref.description = '(upstream)'; + picks.unshift(ref); + } } - } - const placeHolder = localize('select a branch to rebase onto', 'Select a branch to rebase onto'); - const choice = await window.showQuickPick(picks, { placeHolder }); + return picks; + }; + + const placeHolder = l10n.t('Select a branch to rebase onto'); + const choice = await window.showQuickPick(getBranchPicks(), { placeHolder }); if (!choice) { return; @@ -2199,8 +2468,8 @@ export class CommandCenter { @command('git.createTag', { repository: true }) async createTag(repository: Repository): Promise { const inputTagName = await window.showInputBox({ - placeHolder: localize('tag name', "Tag name"), - prompt: localize('provide tag name', "Please provide a tag name"), + placeHolder: l10n.t('Tag name'), + prompt: l10n.t('Please provide a tag name'), ignoreFocusOut: true }); @@ -2209,8 +2478,8 @@ export class CommandCenter { } const inputMessage = await window.showInputBox({ - placeHolder: localize('tag message', "Message"), - prompt: localize('provide tag message', "Please provide a message to annotate the tag"), + placeHolder: l10n.t('Message'), + prompt: l10n.t('Please provide a message to annotate the tag'), ignoreFocusOut: true }); @@ -2220,38 +2489,115 @@ export class CommandCenter { @command('git.deleteTag', { repository: true }) async deleteTag(repository: Repository): Promise { - const picks = repository.refs.filter(ref => ref.type === RefType.Tag) - .map(ref => new TagItem(ref)); + const tagPicks = async (): Promise => { + const remoteTags = await repository.getRefs({ pattern: 'refs/tags' }); + return remoteTags.length === 0 ? [{ label: l10n.t('$(info) This repository has no tags.') }] : remoteTags.map(ref => new TagItem(ref)); + }; - if (picks.length === 0) { - window.showWarningMessage(localize('no tags', "This repository has no tags.")); + const placeHolder = l10n.t('Select a tag to delete'); + const choice = await window.showQuickPick(tagPicks(), { placeHolder }); + + if (choice && choice instanceof TagItem && choice.ref.name) { + await repository.deleteTag(choice.ref.name); + } + } + + @command('git.deleteRemoteTag', { repository: true }) + async deleteRemoteTag(repository: Repository): Promise { + const remotePicks = repository.remotes + .filter(r => r.pushUrl !== undefined) + .map(r => new RemoteItem(repository, r)); + + if (remotePicks.length === 0) { + window.showErrorMessage(l10n.t("Your repository has no remotes configured to push to.")); return; } - const placeHolder = localize('select a tag to delete', 'Select a tag to delete'); - const choice = await window.showQuickPick(picks, { placeHolder }); + let remoteName = remotePicks[0].remoteName; + if (remotePicks.length > 1) { + const remotePickPlaceholder = l10n.t('Select a remote to delete a tag from'); + const remotePick = await window.showQuickPick(remotePicks, { placeHolder: remotePickPlaceholder }); - if (!choice) { - return; + if (!remotePick) { + return; + } + + remoteName = remotePick.remoteName; } - await repository.deleteTag(choice.label); + const remoteTagPicks = async (): Promise => { + const remoteTagsRaw = await repository.getRemoteRefs(remoteName, { tags: true }); + + // Deduplicate annotated and lightweight tags + const remoteTagNames = new Set(); + const remoteTags: Ref[] = []; + + for (const tag of remoteTagsRaw) { + const tagName = (tag.name ?? '').replace(/\^{}$/, ''); + if (!remoteTagNames.has(tagName)) { + remoteTags.push({ ...tag, name: tagName }); + remoteTagNames.add(tagName); + } + } + + return remoteTags.length === 0 ? [{ label: l10n.t('$(info) Remote "{0}" has no tags.', remoteName) }] : remoteTags.map(ref => new TagItem(ref)); + }; + + const tagPickPlaceholder = l10n.t('Select a tag to delete'); + const remoteTagPick = await window.showQuickPick(remoteTagPicks(), { placeHolder: tagPickPlaceholder }); + + if (remoteTagPick && remoteTagPick instanceof TagItem && remoteTagPick.ref.name) { + await repository.deleteRemoteTag(remoteName, remoteTagPick.ref.name); + } } @command('git.fetch', { repository: true }) async fetch(repository: Repository): Promise { if (repository.remotes.length === 0) { - window.showWarningMessage(localize('no remotes to fetch', "This repository has no remotes configured to fetch from.")); + window.showWarningMessage(l10n.t('This repository has no remotes configured to fetch from.')); return; } - await repository.fetchDefault(); + if (repository.remotes.length === 1) { + await repository.fetchDefault(); + return; + } + + const remoteItems: RemoteItem[] = repository.remotes.map(r => new RemoteItem(repository, r)); + + if (repository.HEAD?.upstream?.remote) { + // Move default remote to the top + const defaultRemoteIndex = remoteItems + .findIndex(r => r.remoteName === repository.HEAD!.upstream!.remote); + + if (defaultRemoteIndex !== -1) { + remoteItems.splice(0, 0, ...remoteItems.splice(defaultRemoteIndex, 1)); + } + } + + const quickpick = window.createQuickPick(); + quickpick.placeholder = l10n.t('Select a remote to fetch'); + quickpick.canSelectMany = false; + quickpick.items = [...remoteItems, { label: '', kind: QuickPickItemKind.Separator }, new FetchAllRemotesItem(repository)]; + + quickpick.show(); + const remoteItem = await new Promise(resolve => { + quickpick.onDidAccept(() => resolve(quickpick.activeItems[0] as RemoteItem | FetchAllRemotesItem)); + quickpick.onDidHide(() => resolve(undefined)); + }); + quickpick.hide(); + + if (!remoteItem) { + return; + } + + await remoteItem.run(); } @command('git.fetchPrune', { repository: true }) async fetchPrune(repository: Repository): Promise { if (repository.remotes.length === 0) { - window.showWarningMessage(localize('no remotes to fetch', "This repository has no remotes configured to fetch from.")); + window.showWarningMessage(l10n.t('This repository has no remotes configured to fetch from.')); return; } @@ -2262,7 +2608,7 @@ export class CommandCenter { @command('git.fetchAll', { repository: true }) async fetchAll(repository: Repository): Promise { if (repository.remotes.length === 0) { - window.showWarningMessage(localize('no remotes to fetch', "This repository has no remotes configured to fetch from.")); + window.showWarningMessage(l10n.t('This repository has no remotes configured to fetch from.')); return; } @@ -2274,31 +2620,38 @@ export class CommandCenter { const remotes = repository.remotes; if (remotes.length === 0) { - window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); + window.showWarningMessage(l10n.t('Your repository has no remotes configured to pull from.')); return; } - const remotePicks = remotes.filter(r => r.fetchUrl !== undefined).map(r => ({ label: r.name, description: r.fetchUrl! })); - const placeHolder = localize('pick remote pull repo', "Pick a remote to pull the branch from"); - const remotePick = await window.showQuickPick(remotePicks, { placeHolder }); + let remoteName = remotes[0].name; + if (remotes.length > 1) { + const remotePicks = remotes.filter(r => r.fetchUrl !== undefined).map(r => ({ label: r.name, description: r.fetchUrl! })); + const placeHolder = l10n.t('Pick a remote to pull the branch from'); + const remotePick = await window.showQuickPick(remotePicks, { placeHolder }); - if (!remotePick) { - return; + if (!remotePick) { + return; + } + + remoteName = remotePick.label; } - const remoteRefs = repository.refs; - const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remotePick.label)); - const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name! })); - const branchPlaceHolder = localize('pick branch pull', "Pick a branch to pull from"); - const branchPick = await window.showQuickPick(branchPicks, { placeHolder: branchPlaceHolder }); + const getBranchPicks = async (): Promise => { + const remoteRefs = await repository.getRefs(); + const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remoteName)); + return remoteRefsFiltered.map(r => ({ label: r.name! })); + }; + + const branchPlaceHolder = l10n.t('Pick a branch to pull from'); + const branchPick = await window.showQuickPick(getBranchPicks(), { placeHolder: branchPlaceHolder }); if (!branchPick) { return; } - const remoteCharCnt = remotePick.label.length; - - await repository.pullFrom(false, remotePick.label, branchPick.label.slice(remoteCharCnt + 1)); + const remoteCharCnt = remoteName.length; + await repository.pullFrom(false, remoteName, branchPick.label.slice(remoteCharCnt + 1)); } @command('git.pull', { repository: true }) @@ -2306,7 +2659,7 @@ export class CommandCenter { const remotes = repository.remotes; if (remotes.length === 0) { - window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); + window.showWarningMessage(l10n.t('Your repository has no remotes configured to pull from.')); return; } @@ -2318,7 +2671,7 @@ export class CommandCenter { const remotes = repository.remotes; if (remotes.length === 0) { - window.showWarningMessage(localize('no remotes to pull', "Your repository has no remotes configured to pull from.")); + window.showWarningMessage(l10n.t('Your repository has no remotes configured to pull from.')); return; } @@ -2333,8 +2686,8 @@ export class CommandCenter { return; } - const addRemote = localize('addremote', 'Add Remote'); - const result = await window.showWarningMessage(localize('no remotes to push', "Your repository has no remotes configured to push to."), addRemote); + const addRemote = l10n.t('Add Remote'); + const result = await window.showWarningMessage(l10n.t('Your repository has no remotes configured to push to.'), addRemote); if (result === addRemote) { await this.addRemote(repository); @@ -2348,16 +2701,16 @@ export class CommandCenter { if (pushOptions.forcePush) { if (!config.get('allowForcePush')) { - await window.showErrorMessage(localize('force push not allowed', "Force push is not allowed, please enable it with the 'git.allowForcePush' setting.")); + await window.showErrorMessage(l10n.t('Force push is not allowed, please enable it with the "git.allowForcePush" setting.')); return; } 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 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 message = l10n.t('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 = l10n.t('OK'); + const neverAgain = l10n.t('OK, Don\'t Ask Again'); const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); if (pick === neverAgain) { @@ -2379,7 +2732,7 @@ export class CommandCenter { if (!repository.HEAD || !repository.HEAD.name) { if (!pushOptions.silent) { - window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote.")); + window.showWarningMessage(l10n.t('Please check out a branch to push to a remote.')); } return; } @@ -2396,12 +2749,20 @@ export class CommandCenter { return; } - const branchName = repository.HEAD.name; - const message = localize('confirm publish branch', "The branch '{0}' has no remote branch. Would you like to publish this branch?", branchName); - const yes = localize('ok', "OK"); - const pick = await window.showWarningMessage(message, { modal: true }, yes); + if (this.globalState.get('confirmBranchPublish', true)) { + const branchName = repository.HEAD.name; + const message = l10n.t('The branch "{0}" has no remote branch. Would you like to publish this branch?', branchName); + const yes = l10n.t('OK'); + const neverAgain = l10n.t('OK, Don\'t Ask Again'); + const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); - if (pick === yes) { + if (pick === yes || pick === neverAgain) { + if (pick === neverAgain) { + this.globalState.update('confirmBranchPublish', false); + } + await this.publish(repository); + } + } else { await this.publish(repository); } } @@ -2410,7 +2771,7 @@ export class CommandCenter { if (!pushOptions.pushTo?.remote) { const addRemote = new AddRemoteItem(this); const picks = [...remotes.filter(r => r.pushUrl !== undefined).map(r => ({ label: r.name, description: r.pushUrl })), addRemote]; - const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); + const placeHolder = l10n.t('Pick a remote to publish the branch "{0}" to:', branchName); const choice = await window.showQuickPick(picks, { placeHolder }); if (!choice) { @@ -2455,8 +2816,8 @@ export class CommandCenter { @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"), + placeHolder: l10n.t('Commit Hash'), + prompt: l10n.t('Please provide the commit hash'), ignoreFocusOut: true }); @@ -2485,8 +2846,8 @@ export class CommandCenter { @command('git.addRemote', { repository: true }) async addRemote(repository: Repository): Promise { const url = await pickRemoteSource({ - providerLabel: provider => localize('addfrom', "Add remote from {0}", provider.name), - urlLabel: localize('addFrom', "Add remote from URL") + providerLabel: provider => l10n.t('Add remote from {0}', provider.name), + urlLabel: l10n.t('Add remote from URL') }); if (!url) { @@ -2494,14 +2855,14 @@ export class CommandCenter { } const resultName = await window.showInputBox({ - placeHolder: localize('remote name', "Remote name"), - prompt: localize('provide remote name', "Please provide a remote name"), + placeHolder: l10n.t('Remote name'), + prompt: l10n.t('Please provide a remote name'), ignoreFocusOut: true, validateInput: (name: string) => { if (!sanitizeRemoteName(name)) { - return localize('remote name format invalid', "Remote name format invalid"); + return l10n.t('Remote name format invalid'); } else if (repository.remotes.find(r => r.name === name)) { - return localize('remote already exists', "Remote '{0}' already exists.", name); + return l10n.t('Remote "{0}" already exists.', name); } return null; @@ -2524,20 +2885,20 @@ export class CommandCenter { const remotes = repository.remotes; if (remotes.length === 0) { - window.showErrorMessage(localize('no remotes added', "Your repository has no remotes.")); + window.showErrorMessage(l10n.t('Your repository has no remotes.')); return; } - const picks = remotes.map(r => r.name); - const placeHolder = localize('remove remote', "Pick a remote to remove"); + const picks: RemoteItem[] = repository.remotes.map(r => new RemoteItem(repository, r)); + const placeHolder = l10n.t('Pick a remote to remove'); - const remoteName = await window.showQuickPick(picks, { placeHolder }); + const remote = await window.showQuickPick(picks, { placeHolder }); - if (!remoteName) { + if (!remote) { return; } - await repository.removeRemote(remoteName); + await repository.removeRemote(remote.remoteName); } private async _sync(repository: Repository, rebase: boolean): Promise { @@ -2546,14 +2907,7 @@ export class CommandCenter { if (!HEAD) { return; } else if (!HEAD.upstream) { - const branchName = HEAD.name; - const message = localize('confirm publish branch', "The branch '{0}' has no remote branch. Would you like to publish this branch?", branchName); - const yes = localize('ok', "OK"); - const pick = await window.showWarningMessage(message, { modal: true }, yes); - - if (pick === yes) { - await this.publish(repository); - } + this._push(repository, { pushType: PushType.Push }); return; } @@ -2565,9 +2919,9 @@ export class CommandCenter { const shouldPrompt = !isReadonly && config.get('confirmSync') === true; if (shouldPrompt) { - const message = localize('sync is unpredictable', "This action will pull and push commits from and to '{0}/{1}'.", HEAD.upstream.remote, HEAD.upstream.name); - const yes = localize('ok', "OK"); - const neverAgain = localize('never again', "OK, Don't Show Again"); + const message = l10n.t('This action will pull and push commits from and to "{0}/{1}".', HEAD.upstream.remote, HEAD.upstream.name); + const yes = l10n.t('OK'); + const neverAgain = l10n.t('OK, Don\'t Show Again'); const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain); if (pick === neverAgain) { @@ -2634,7 +2988,7 @@ export class CommandCenter { const publishers = this.model.getRemoteSourcePublishers(); if (publishers.length === 0) { - window.showWarningMessage(localize('no remotes to publish', "Your repository has no remotes configured to publish to.")); + window.showWarningMessage(l10n.t('Your repository has no remotes configured to publish to.')); return; } @@ -2644,8 +2998,8 @@ export class CommandCenter { publisher = publishers[0]; } else { const picks = publishers - .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + localize('publish to', "Publish to {0}", provider.name), alwaysShow: true, provider })); - const placeHolder = localize('pick provider', "Pick a provider to publish the branch '{0}' to:", branchName); + .map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + l10n.t('Publish to {0}', provider.name), alwaysShow: true, provider })); + const placeHolder = l10n.t('Pick a provider to publish the branch "{0}" to:', branchName); const choice = await window.showQuickPick(picks, { placeHolder }); if (!choice) { @@ -2670,7 +3024,7 @@ export class CommandCenter { const addRemote = new AddRemoteItem(this); const picks = [...repository.remotes.map(r => ({ label: r.name, description: r.pushUrl })), addRemote]; - const placeHolder = localize('pick remote', "Pick a remote to publish the branch '{0}' to:", branchName); + const placeHolder = l10n.t('Pick a remote to publish the branch "{0}" to:', branchName); const choice = await window.showQuickPick(picks, { placeHolder }); if (!choice) { @@ -2745,14 +3099,21 @@ export class CommandCenter { await commands.executeCommand('revealFileInOS', resourceState.resourceUri); } - private async _stash(repository: Repository, includeUntracked = false): Promise { + private async _stash(repository: Repository, includeUntracked = false, staged = false): Promise { const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0 && (!includeUntracked || repository.untrackedGroup.resourceStates.length === 0); const noStagedChanges = repository.indexGroup.resourceStates.length === 0; - if (noUnstagedChanges && noStagedChanges) { - window.showInformationMessage(localize('no changes stash', "There are no changes to stash.")); - return; + if (staged) { + if (noStagedChanges) { + window.showInformationMessage(l10n.t('There are no staged changes to stash.')); + return false; + } + } else { + if (noUnstagedChanges && noStagedChanges) { + window.showInformationMessage(l10n.t('There are no changes to stash.')); + return false; + } } const config = workspace.getConfiguration('git', Uri.file(repository.root)); @@ -2769,16 +3130,16 @@ export class CommandCenter { 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"); + ? l10n.t('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)) + : l10n.t('There are {0} unsaved files.\n\nWould you like to save them before stashing?', documents.length); + const saveAndStash = l10n.t('Save All & Stash'); + const stash = l10n.t('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 + return false; // do not stash on cancel } } } @@ -2791,30 +3152,45 @@ export class CommandCenter { message = await window.showInputBox({ value: message, - prompt: localize('provide stash message', "Optionally provide a stash message"), - placeHolder: localize('stash message', "Stash message") + prompt: l10n.t('Optionally provide a stash message'), + placeHolder: l10n.t('Stash message') }); if (typeof message === 'undefined') { - return; + return false; } - await repository.createStash(message, includeUntracked); + try { + await repository.createStash(message, includeUntracked, staged); + return true; + } catch (err) { + if (/You do not have the initial commit yet/.test(err.stderr || '')) { + window.showInformationMessage(l10n.t('The repository does not have any commits. Please make an initial commit before creating a stash.')); + return false; + } + + throw err; + } } @command('git.stash', { repository: true }) - stash(repository: Repository): Promise { - return this._stash(repository); + async stash(repository: Repository): Promise { + await this._stash(repository); + } + + @command('git.stashStaged', { repository: true }) + async stashStaged(repository: Repository): Promise { + await this._stash(repository, false, true); } @command('git.stashIncludeUntracked', { repository: true }) - stashIncludeUntracked(repository: Repository): Promise { - return this._stash(repository, true); + async stashIncludeUntracked(repository: Repository): Promise { + await this._stash(repository, true); } @command('git.stashPop', { repository: true }) async stashPop(repository: Repository): Promise { - const placeHolder = localize('pick stash to pop', "Pick a stash to pop"); + const placeHolder = l10n.t('Pick a stash to pop'); const stash = await this.pickStash(repository, placeHolder); if (!stash) { @@ -2829,7 +3205,7 @@ export class CommandCenter { const stashes = await repository.getStashes(); if (stashes.length === 0) { - window.showInformationMessage(localize('no stashes', "There are no stashes in the repository.")); + window.showInformationMessage(l10n.t('There are no stashes in the repository.')); return; } @@ -2838,7 +3214,7 @@ export class CommandCenter { @command('git.stashApply', { repository: true }) async stashApply(repository: Repository): Promise { - const placeHolder = localize('pick stash to apply', "Pick a stash to apply"); + const placeHolder = l10n.t('Pick a stash to apply'); const stash = await this.pickStash(repository, placeHolder); if (!stash) { @@ -2853,7 +3229,7 @@ export class CommandCenter { const stashes = await repository.getStashes(); if (stashes.length === 0) { - window.showInformationMessage(localize('no stashes', "There are no stashes in the repository.")); + window.showInformationMessage(l10n.t('There are no stashes in the repository.')); return; } @@ -2862,7 +3238,7 @@ export class CommandCenter { @command('git.stashDrop', { repository: true }) async stashDrop(repository: Repository): Promise { - const placeHolder = localize('pick stash to drop', "Pick a stash to drop"); + const placeHolder = l10n.t('Pick a stash to drop'); const stash = await this.pickStash(repository, placeHolder); if (!stash) { @@ -2870,9 +3246,10 @@ export class CommandCenter { } // request confirmation for the operation - const yes = localize('yes', "Yes"); + const yes = l10n.t('Yes'); const result = await window.showWarningMessage( - localize('sure drop', "Are you sure you want to drop the stash: {0}?", stash.description), + l10n.t('Are you sure you want to drop the stash: {0}?', stash.description), + { modal: true }, yes ); if (result !== yes) { @@ -2887,17 +3264,17 @@ export class CommandCenter { const stashes = await repository.getStashes(); if (stashes.length === 0) { - window.showInformationMessage(localize('no stashes', "There are no stashes in the repository.")); + window.showInformationMessage(l10n.t('There are no stashes in the repository.')); return; } // request confirmation for the operation - const yes = localize('yes', "Yes"); + const yes = l10n.t('Yes'); const question = stashes.length === 1 ? - localize('drop one stash', "Are you sure you want to drop ALL stashes? There is 1 stash that will be subject to pruning, and MAY BE IMPOSSIBLE TO RECOVER.") : - localize('drop all stashes', "Are you sure you want to drop ALL stashes? There are {0} stashes that will be subject to pruning, and MAY BE IMPOSSIBLE TO RECOVER.", stashes.length); + l10n.t('Are you sure you want to drop ALL stashes? There is 1 stash that will be subject to pruning, and MAY BE IMPOSSIBLE TO RECOVER.') : + l10n.t('Are you sure you want to drop ALL stashes? There are {0} stashes that will be subject to pruning, and MAY BE IMPOSSIBLE TO RECOVER.', stashes.length); - const result = await window.showWarningMessage(question, yes); + const result = await window.showWarningMessage(question, { modal: true }, yes); if (result !== yes) { return; } @@ -2909,7 +3286,7 @@ export class CommandCenter { const stashes = await repository.getStashes(); if (stashes.length === 0) { - window.showInformationMessage(localize('no stashes', "There are no stashes in the repository.")); + window.showInformationMessage(l10n.t('There are no stashes in the repository.')); return; } @@ -2944,17 +3321,17 @@ export class CommandCenter { let title; if ((item.previousRef === 'HEAD' || item.previousRef === '~') && item.ref === '') { - title = localize('git.title.workingTree', '{0} (Working Tree)', basename); + title = l10n.t('{0} (Working Tree)', basename); } else if (item.previousRef === 'HEAD' && item.ref === '~') { - title = localize('git.title.index', '{0} (Index)', basename); + title = l10n.t('{0} (Index)', basename); } else { - title = localize('git.title.diffRefs', '{0} ({1}) ↔ {0} ({2})', basename, item.shortPreviousRef, item.shortRef); + title = l10n.t('{0} ({1}) ↔ {0} ({2})', basename, item.shortPreviousRef, item.shortRef); } return { command: 'vscode.diff', - title: localize('git.timeline.openDiffCommand', "Open Comparison"), + title: l10n.t('Open Comparison'), arguments: [toGitUri(uri, item.previousRef), item.ref === '' ? uri : toGitUri(uri, item.ref), title, options] }; } @@ -3000,26 +3377,26 @@ export class CommandCenter { const basename = path.basename(uri.fsPath); let leftTitle; if ((selected.previousRef === 'HEAD' || selected.previousRef === '~') && selected.ref === '') { - leftTitle = localize('git.title.workingTree', '{0} (Working Tree)', basename); + leftTitle = l10n.t('{0} (Working Tree)', basename); } else if (selected.previousRef === 'HEAD' && selected.ref === '~') { - leftTitle = localize('git.title.index', '{0} (Index)', basename); + leftTitle = l10n.t('{0} (Index)', basename); } else { - leftTitle = localize('git.title.ref', '{0} ({1})', basename, selected.shortRef); + leftTitle = l10n.t('{0} ({1})', basename, selected.shortRef); } let rightTitle; if ((item.previousRef === 'HEAD' || item.previousRef === '~') && item.ref === '') { - rightTitle = localize('git.title.workingTree', '{0} (Working Tree)', basename); + rightTitle = l10n.t('{0} (Working Tree)', basename); } else if (item.previousRef === 'HEAD' && item.ref === '~') { - rightTitle = localize('git.title.index', '{0} (Index)', basename); + rightTitle = l10n.t('{0} (Index)', basename); } else { - rightTitle = localize('git.title.ref', '{0} ({1})', basename, item.shortRef); + rightTitle = l10n.t('{0} ({1})', basename, item.shortRef); } - const title = localize('git.title.diff', '{0} ↔ {1}', leftTitle, rightTitle); + const title = l10n.t('{0} ↔ {1}', leftTitle, rightTitle); await commands.executeCommand('vscode.diff', selected.ref === '' ? uri : toGitUri(uri, selected.ref), item.ref === '' ? uri : toGitUri(uri, item.ref), title); } @@ -3028,7 +3405,7 @@ export class CommandCenter { if (repository.rebaseCommit) { await repository.rebaseAbort(); } else { - await window.showInformationMessage(localize('no rebase', "No rebase in progress.")); + await window.showInformationMessage(l10n.t('No rebase in progress.')); } } @@ -3037,6 +3414,83 @@ export class CommandCenter { repository.closeDiffEditors(undefined, undefined, true); } + @command('git.openRepositoriesInParentFolders') + async openRepositoriesInParentFolders(): Promise { + const parentRepositories: string[] = []; + + const title = l10n.t('Open Repositories In Parent Folders'); + const placeHolder = l10n.t('Pick a repository to open'); + + const allRepositoriesLabel = l10n.t('All Repositories'); + const allRepositoriesQuickPickItem: QuickPickItem = { label: allRepositoriesLabel }; + const repositoriesQuickPickItems: QuickPickItem[] = Array.from(this.model.parentRepositories.keys()).sort().map(r => new RepositoryItem(r)); + + const items = this.model.parentRepositories.size === 1 ? [...repositoriesQuickPickItems] : + [...repositoriesQuickPickItems, { label: '', kind: QuickPickItemKind.Separator }, allRepositoriesQuickPickItem]; + + const repositoryItem = await window.showQuickPick(items, { title, placeHolder }); + if (!repositoryItem) { + return; + } + + if (repositoryItem === allRepositoriesQuickPickItem) { + // All Repositories + parentRepositories.push(...this.model.parentRepositories.keys()); + } else { + // One Repository + parentRepositories.push((repositoryItem as RepositoryItem).path); + } + + for (const parentRepository of parentRepositories) { + await this.model.openParentRepository(parentRepository); + } + } + + @command('git.manageUnsafeRepositories') + async manageUnsafeRepositories(): Promise { + const unsafeRepositories: string[] = []; + + const quickpick = window.createQuickPick(); + quickpick.title = l10n.t('Manage Unsafe Repositories'); + quickpick.placeholder = l10n.t('Pick a repository to mark as safe and open'); + + const allRepositoriesLabel = l10n.t('All Repositories'); + const allRepositoriesQuickPickItem: QuickPickItem = { label: allRepositoriesLabel }; + const repositoriesQuickPickItems: QuickPickItem[] = Array.from(this.model.unsafeRepositories.keys()).sort().map(r => new RepositoryItem(r)); + + quickpick.items = this.model.unsafeRepositories.size === 1 ? [...repositoriesQuickPickItems] : + [...repositoriesQuickPickItems, { label: '', kind: QuickPickItemKind.Separator }, allRepositoriesQuickPickItem]; + + quickpick.show(); + const repositoryItem = await new Promise( + resolve => { + quickpick.onDidAccept(() => resolve(quickpick.activeItems[0])); + quickpick.onDidHide(() => resolve(undefined)); + }); + quickpick.hide(); + + if (!repositoryItem) { + return; + } + + if (repositoryItem.label === allRepositoriesLabel) { + // All Repositories + unsafeRepositories.push(...this.model.unsafeRepositories.keys()); + } else { + // One Repository + unsafeRepositories.push((repositoryItem as RepositoryItem).path); + } + + for (const unsafeRepository of unsafeRepositories) { + // Mark as Safe + await this.git.addSafeDirectory(this.model.unsafeRepositories.get(unsafeRepository)!); + + // Open Repository + await this.model.openRepository(unsafeRepository); + this.model.unsafeRepositories.delete(unsafeRepository); + } + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; @@ -3073,7 +3527,7 @@ export class CommandCenter { */ this.telemetryReporter.sendTelemetryEvent('git.command', { command: id }); - return result.catch(async err => { + return result.catch(err => { const options: MessageOptions = { modal: true }; @@ -3082,11 +3536,11 @@ export class CommandCenter { let type: 'error' | 'warning' | 'information' = 'error'; const choices = new Map void>(); - const openOutputChannelChoice = localize('open git log', "Open Git Log"); - const outputChannelLogger = this.outputChannelLogger; - choices.set(openOutputChannelChoice, () => outputChannelLogger.showOutputChannel()); + const openOutputChannelChoice = l10n.t('Open Git Log'); + const outputChannelLogger = this.logger; + choices.set(openOutputChannelChoice, () => outputChannelLogger.show()); - const showCommandOutputChoice = localize('show command output', "Show Command Output"); + const showCommandOutputChoice = l10n.t('Show Command Output'); if (err.stderr) { choices.set(showCommandOutputChoice, async () => { const timestamp = new Date().getTime(); @@ -3113,18 +3567,20 @@ export class CommandCenter { switch (err.gitErrorCode) { case GitErrorCodes.DirtyWorkTree: - message = localize('clean repo', "Please clean your repository working tree before checkout."); + message = l10n.t('Please clean your repository working tree before checkout.'); break; case GitErrorCodes.PushRejected: - message = localize('cant push', "Can't push refs to remote. Try running 'Pull' first to integrate your changes."); + message = l10n.t('Can\'t push refs to remote. Try running "Pull" first to integrate your changes.'); break; case GitErrorCodes.Conflict: - message = localize('merge conflicts', "There are merge conflicts. Resolve them before committing."); + message = l10n.t('There are merge conflicts. Resolve them before committing.'); type = 'warning'; + choices.set(l10n.t('Show Changes'), () => commands.executeCommand('workbench.view.scm')); options.modal = false; break; case GitErrorCodes.StashConflict: - message = localize('stash merge conflicts', "There were merge conflicts while applying the stash."); + message = l10n.t('There were merge conflicts while applying the stash.'); + choices.set(l10n.t('Show Changes'), () => commands.executeCommand('workbench.view.scm')); type = 'warning'; options.modal = false; break; @@ -3133,17 +3589,17 @@ export class CommandCenter { const match = regex.exec(err.stderr || String(err)); message = match - ? localize('auth failed specific', "Failed to authenticate to git remote:\n\n{0}", match[1]) - : localize('auth failed', "Failed to authenticate to git remote."); + ? l10n.t('Failed to authenticate to git remote:\n\n{0}', match[1]) + : l10n.t('Failed to authenticate to git remote.'); break; } case GitErrorCodes.NoUserNameConfigured: case GitErrorCodes.NoUserEmailConfigured: - message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git."); - choices.set(localize('learn more', "Learn More"), () => commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-setup-git'))); + message = l10n.t('Make sure you configure your "user.name" and "user.email" in git.'); + choices.set(l10n.t('Learn More'), () => commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-setup-git'))); break; case GitErrorCodes.EmptyCommitMessage: - message = localize('empty commit', "Commit operation was cancelled due to empty commit message."); + message = l10n.t('Commit operation was cancelled due to empty commit message.'); choices.clear(); type = 'information'; options.modal = false; @@ -3157,8 +3613,8 @@ export class CommandCenter { [0]; message = hint - ? localize('git error details', "Git: {0}", hint) - : localize('git error', "Git error"); + ? l10n.t('Git: {0}', hint) + : l10n.t('Git error'); break; } @@ -3169,26 +3625,10 @@ export class CommandCenter { return; } - let result: string | undefined; - const allChoices = Array.from(choices.keys()); - - switch (type) { - case 'error': - result = await window.showErrorMessage(message, options, ...allChoices); - break; - case 'warning': - result = await window.showWarningMessage(message, options, ...allChoices); - break; - case 'information': - result = await window.showInformationMessage(message, options, ...allChoices); - break; - } - - if (result) { - const resultFn = choices.get(result); - - resultFn?.(); - } + // We explicitly do not await this promise, because we do not + // want the command execution to be stuck waiting for the user + // to take action on the notification. + this.showErrorNotification(type, message, options, choices); }); }; @@ -3198,13 +3638,36 @@ export class CommandCenter { return result; } + private async showErrorNotification(type: 'error' | 'warning' | 'information', message: string, options: MessageOptions, choices: Map void>): Promise { + let result: string | undefined; + const allChoices = Array.from(choices.keys()); + + switch (type) { + case 'error': + result = await window.showErrorMessage(message, options, ...allChoices); + break; + case 'warning': + result = await window.showWarningMessage(message, options, ...allChoices); + break; + case 'information': + result = await window.showInformationMessage(message, options, ...allChoices); + break; + } + + if (result) { + const resultFn = choices.get(result); + + resultFn?.(); + } + } + private getSCMResource(uri?: Uri): Resource | undefined { uri = uri ? uri : (window.activeTextEditor && window.activeTextEditor.document.uri); - this.outputChannelLogger.logDebug(`git.getSCMResource.uri ${uri && uri.toString()}`); + this.logger.debug(`git.getSCMResource.uri ${uri && uri.toString()}`); for (const r of this.model.repositories.map(r => r.root)) { - this.outputChannelLogger.logDebug(`repo root ${r}`); + this.logger.debug(`repo root ${r}`); } if (!uri) { @@ -3225,7 +3688,8 @@ export class CommandCenter { } return repository.workingTreeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0] - || repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]; + || repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0] + || repository.mergeGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString)[0]; } return undefined; } diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index eacb45fdc5..23dc0f7564 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -127,7 +127,11 @@ class GitDecorationProvider implements FileDecorationProvider { // not deleted and has a decoration bucket.set(r.original.toString(), decoration); - if (r.type === Status.INDEX_RENAMED) { + if (r.type === Status.DELETED && r.rightUri) { + bucket.set(r.rightUri.toString(), decoration); + } + + if (r.type === Status.INDEX_RENAMED || r.type === Status.INTENT_TO_RENAME) { bucket.set(r.resourceUri.toString(), decoration); } } diff --git a/extensions/git/src/editSessionIdentityProvider.ts b/extensions/git/src/editSessionIdentityProvider.ts new file mode 100644 index 0000000000..d46a522124 --- /dev/null +++ b/extensions/git/src/editSessionIdentityProvider.ts @@ -0,0 +1,115 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as vscode from 'vscode'; +import { RefType } from './api/git'; +import { Model } from './model'; + +export class GitEditSessionIdentityProvider implements vscode.EditSessionIdentityProvider, vscode.Disposable { + + private providerRegistration: vscode.Disposable; + + constructor(private model: Model) { + this.providerRegistration = vscode.workspace.registerEditSessionIdentityProvider('file', this); + + vscode.workspace.onWillCreateEditSessionIdentity((e) => { + e.waitUntil(this._onWillCreateEditSessionIdentity(e.workspaceFolder)); + }); + } + + dispose() { + this.providerRegistration.dispose(); + } + + async provideEditSessionIdentity(workspaceFolder: vscode.WorkspaceFolder, token: vscode.CancellationToken): Promise { + await this.model.openRepository(path.dirname(workspaceFolder.uri.fsPath)); + + const repository = this.model.getRepository(workspaceFolder.uri); + await repository?.status(); + + if (!repository || !repository?.HEAD?.upstream) { + return undefined; + } + + const remoteUrl = repository.remotes.find((remote) => remote.name === repository.HEAD?.upstream?.remote)?.pushUrl?.replace(/^(git@[^\/:]+)(:)/i, 'ssh://$1/'); + const remote = remoteUrl ? await vscode.workspace.getCanonicalUri(vscode.Uri.parse(remoteUrl), { targetScheme: 'https' }, token) : null; + + return JSON.stringify({ + remote: remote?.toString() ?? remoteUrl, + ref: repository.HEAD?.upstream?.name ?? null, + sha: repository.HEAD?.commit ?? null, + }); + } + + provideEditSessionIdentityMatch(identity1: string, identity2: string): vscode.EditSessionIdentityMatch { + try { + const normalizedIdentity1 = normalizeEditSessionIdentity(identity1); + const normalizedIdentity2 = normalizeEditSessionIdentity(identity2); + + if (normalizedIdentity1.remote === normalizedIdentity2.remote && + normalizedIdentity1.ref === normalizedIdentity2.ref && + normalizedIdentity1.sha === normalizedIdentity2.sha) { + // This is a perfect match + return vscode.EditSessionIdentityMatch.Complete; + } else if (normalizedIdentity1.remote === normalizedIdentity2.remote && + normalizedIdentity1.ref === normalizedIdentity2.ref && + normalizedIdentity1.sha !== normalizedIdentity2.sha) { + // Same branch and remote but different SHA + return vscode.EditSessionIdentityMatch.Partial; + } else { + return vscode.EditSessionIdentityMatch.None; + } + } catch (ex) { + return vscode.EditSessionIdentityMatch.Partial; + } + } + + private async _onWillCreateEditSessionIdentity(workspaceFolder: vscode.WorkspaceFolder): Promise { + await this._doPublish(workspaceFolder); + } + + private async _doPublish(workspaceFolder: vscode.WorkspaceFolder) { + await this.model.openRepository(path.dirname(workspaceFolder.uri.fsPath)); + + const repository = this.model.getRepository(workspaceFolder.uri); + if (!repository) { + return; + } + + await repository.status(); + + // If this branch hasn't been published to the remote yet, + // ensure that it is published before Continue On is invoked + if (!repository.HEAD?.upstream && repository.HEAD?.type === RefType.Head) { + + const publishBranch = vscode.l10n.t('Publish Branch'); + const selection = await vscode.window.showInformationMessage( + vscode.l10n.t('The current branch is not published to the remote. Would you like to publish it to access your changes elsewhere?'), + { modal: true }, + publishBranch + ); + if (selection !== publishBranch) { + throw new vscode.CancellationError(); + } + + await vscode.commands.executeCommand('git.publish'); + } + } +} + +function normalizeEditSessionIdentity(identity: string) { + let { remote, ref, sha } = JSON.parse(identity); + + if (typeof remote === 'string' && remote.endsWith('.git')) { + remote = remote.slice(0, remote.length - 4); + } + + return { + remote, + ref, + sha + }; +} diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 28008d24ca..9eb3e4c381 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -12,10 +12,10 @@ import * as which from 'which'; import { EventEmitter } from 'events'; import * as iconv from '@vscode/iconv-lite-umd'; import * as filetype from 'file-type'; -import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows } from './util'; -import { CancellationToken, ConfigurationChangeEvent, Uri, workspace } from 'vscode'; // {{SQL CARBON EDIT}} remove Progress +import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals } from './util'; +import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Uri, workspace } from 'vscode'; // {{SQL CARBON EDIT}} remove Progress import { detectEncoding } from './encoding'; -import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, BranchQuery, ICloneOptions } from './api/git'; // {{SQL CARBON EDIT}} add ICloneOptions +import { Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery, InitOptions, ICloneOptions } from './api/git'; // {{SQL CARBON EDIT}} add ICloneOptions import * as byline from 'byline'; import { StringDecoder } from 'string_decoder'; @@ -198,7 +198,7 @@ async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToke } if (cancellationToken && cancellationToken.isCancellationRequested) { - throw new GitError({ message: 'Cancelled' }); + throw new CancellationError(); } const disposables: IDisposable[] = []; @@ -239,7 +239,7 @@ async function exec(child: cp.ChildProcess, cancellationToken?: CancellationToke // noop } - e(new GitError({ message: 'Cancelled' })); + e(new CancellationError()); }); }); @@ -360,6 +360,7 @@ const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B'; readonly parentPath: string; readonly progress: Progress<{ increment: number }>; readonly recursive?: boolean; + readonly ref?: string; }*/ export class Git { @@ -396,13 +397,18 @@ export class Git { return Versions.compare(Versions.fromString(this.version), Versions.fromString(version)); } - open(repository: string, dotGit: { path: string; commonPath?: string }): Repository { - return new Repository(this, repository, dotGit); + open(repository: string, dotGit: { path: string; commonPath?: string }, logger: LogOutputChannel): Repository { + return new Repository(this, repository, dotGit, logger); } - async init(repository: string): Promise { - await this.exec(repository, ['init']); - return; + async init(repository: string, options: InitOptions = {}): Promise { + const args = ['init']; + + if (options.defaultBranch && options.defaultBranch !== '') { + args.push('-b', options.defaultBranch); + } + + await this.exec(repository, args); } async clone(url: string, options: ICloneOptions, cancellationToken?: CancellationToken): Promise { @@ -427,7 +433,7 @@ export class Git { let previousProgress = 0; lineStream.on('data', (line: string) => { - let match: RegExpMatchArray | null = null; + let match: RegExpExecArray | null = null; if (match = /Counting objects:\s*(\d+)%/i.exec(line)) { totalProgress = Math.floor(parseInt(match[1]) * 0.1); @@ -451,6 +457,9 @@ export class Git { if (options.recursive) { command.push('--recursive'); } + if (options.ref) { + command.push('--branch', options.ref); + } await this.exec(options.parentPath, command, { cancellationToken, env: { 'GIT_HTTP_USER_AGENT': this.userAgent }, @@ -481,7 +490,7 @@ export class Git { const repoUri = Uri.file(repoPath); const pathUri = Uri.file(repositoryPath); if (repoUri.authority.length !== 0 && pathUri.authority.length === 0) { - // eslint-disable-next-line code-no-look-behind-regex + // eslint-disable-next-line local/code-no-look-behind-regex const match = /(?<=^\/?)([a-zA-Z])(?=:\/)/.exec(pathUri.path); if (match !== null) { const [, letter] = match; @@ -493,10 +502,14 @@ export class Git { ), ); if (networkPath !== undefined) { + // If the repository is at the root of the mapped drive then we + // have to append `\` (ex: D:\) otherwise the path is not valid. + const isDriveRoot = pathEquals(repoUri.fsPath, networkPath); + return path.normalize( repoUri.fsPath.replace( networkPath, - `${letter.toLowerCase()}:${networkPath.endsWith('\\') ? '\\' : ''}` + `${letter.toLowerCase()}:${isDriveRoot || networkPath.endsWith('\\') ? '\\' : ''}` ), ); } @@ -505,13 +518,6 @@ export class Git { return path.normalize(pathUri.fsPath); } - - // On Windows, there are cases in which the normalized path for a mapped folder contains a trailing `\` - // character (ex: \\server\folder\) due to the implementation of `path.normalize()`. This behaviour is - // by design as documented in https://github.com/nodejs/node/issues/1765. - if (repoUri.authority.length !== 0) { - return repoPath.replace(/\\$/, ''); - } } return repoPath; @@ -554,7 +560,7 @@ export class Git { if (options.log !== false) { const startTime = Date.now(); child.on('exit', (_) => { - this.log(`> git ${args.join(' ')} [${Date.now() - startTime}ms]\n`); + this.log(`> git ${args.join(' ')} [${Date.now() - startTime}ms]${child.killed ? ' (cancelled)' : ''}\n`); }); } @@ -570,12 +576,22 @@ export class Git { child.stdin!.end(options.input, 'utf8'); } - const startTime = Date.now(); - const bufferResult = await exec(child, options.cancellationToken); + const startExec = Date.now(); + let bufferResult: IExecutionResult; + + try { + bufferResult = await exec(child, options.cancellationToken); + } catch (ex) { + if (ex instanceof CancellationError) { + this.log(`> git ${args.join(' ')} [${Date.now() - startExec}ms] (cancelled)\n`); + } + + throw ex; + } if (options.log !== false) { // command - this.log(`> git ${args.join(' ')} [${Date.now() - startTime}ms]\n`); + this.log(`> git ${args.join(' ')} [${Date.now() - startExec}ms]\n`); // stdout if (bufferResult.stdout.length > 0 && args.find(a => this.commandsToLog.includes(a))) { @@ -656,6 +672,32 @@ export class Git { private log(output: string): void { this._onOutput.emit('log', output); } + + async mergeFile(options: { input1Path: string; input2Path: string; basePath: string; diff3?: boolean }): Promise { + const args = ['merge-file', '-p', options.input1Path, options.basePath, options.input2Path]; + if (options.diff3) { + args.push('--diff3'); + } else { + args.push('--no-diff3'); + } + + try { + const result = await this.exec(os.homedir(), args); + return result.stdout; + } catch (err) { + if (typeof err.stdout === 'string') { + // The merge had conflicts, stdout still contains the merged result (with conflict markers) + return err.stdout; + } else { + throw err; + } + } + } + + async addSafeDirectory(repositoryPath: string): Promise { + await this.exec(os.homedir(), ['config', '--global', '--add', 'safe.directory', repositoryPath]); + return; + } } export interface Commit { @@ -669,6 +711,50 @@ export interface Commit { refNames: string[]; } +interface GitConfigSection { + name: string; + subSectionName?: string; + properties: { [key: string]: string }; +} + +class GitConfigParser { + private static readonly _lineSeparator = /\r?\n/; + + private static readonly _propertyRegex = /^\s*(\w+)\s*=\s*"?([^"]+)"?$/; + private static readonly _sectionRegex = /^\s*\[\s*([^\]]+?)\s*(\"[^"]+\")*\]\s*$/; + + static parse(raw: string): GitConfigSection[] { + const config: { sections: GitConfigSection[] } = { sections: [] }; + let section: GitConfigSection = { name: 'DEFAULT', properties: {} }; + + const addSection = (section?: GitConfigSection) => { + if (!section) { return; } + config.sections.push(section); + }; + + for (const line of raw.split(GitConfigParser._lineSeparator)) { + // Section + const sectionMatch = line.match(GitConfigParser._sectionRegex); + if (sectionMatch?.length === 3) { + addSection(section); + section = { name: sectionMatch[1], subSectionName: sectionMatch[2]?.replaceAll('"', ''), properties: {} }; + + continue; + } + + // Property + const propertyMatch = line.match(GitConfigParser._propertyRegex); + if (propertyMatch?.length === 3 && !Object.keys(section.properties).includes(propertyMatch[1])) { + section.properties[propertyMatch[1]] = propertyMatch[2]; + } + } + + addSection(section); + + return config.sections; + } +} + export class GitStatusParser { private lastRaw = ''; @@ -707,7 +793,7 @@ export class GitStatusParser { // space i++; - if (entry.x === 'R' || entry.x === 'C') { + if (entry.x === 'R' || entry.y === 'R' || entry.x === 'C') { lastIndex = raw.indexOf('\0', i); if (lastIndex === -1) { @@ -742,61 +828,38 @@ export interface Submodule { } export function parseGitmodules(raw: string): Submodule[] { - const regex = /\r?\n/g; - let position = 0; - let match: RegExpExecArray | null = null; - const result: Submodule[] = []; - let submodule: Partial = {}; - function parseLine(line: string): void { - const sectionMatch = /^\s*\[submodule "([^"]+)"\]\s*$/.exec(line); - - if (sectionMatch) { - if (submodule.name && submodule.path && submodule.url) { - result.push(submodule as Submodule); - } - - const name = sectionMatch[1]; - - if (name) { - submodule = { name }; - return; - } + for (const submoduleSection of GitConfigParser.parse(raw).filter(s => s.name === 'submodule')) { + if (submoduleSection.subSectionName && submoduleSection.properties['path'] && submoduleSection.properties['url']) { + result.push({ + name: submoduleSection.subSectionName, + path: submoduleSection.properties['path'], + url: submoduleSection.properties['url'] + }); } - - if (!submodule) { - return; - } - - const propertyMatch = /^\s*(\w+)\s*=\s*(.*)$/.exec(line); - - if (!propertyMatch) { - return; - } - - const [, key, value] = propertyMatch; - - switch (key) { - case 'path': submodule.path = value; break; - case 'url': submodule.url = value; break; - } - } - - while (match = regex.exec(raw)) { - parseLine(raw.substring(position, match.index)); - position = match.index + match[0].length; - } - - parseLine(raw.substring(position)); - - if (submodule.name && submodule.path && submodule.url) { - result.push(submodule as Submodule); } return result; } +export function parseGitRemotes(raw: string): MutableRemote[] { + const remotes: MutableRemote[] = []; + + for (const remoteSection of GitConfigParser.parse(raw).filter(s => s.name === 'remote')) { + if (remoteSection.subSectionName) { + remotes.push({ + name: remoteSection.subSectionName, + fetchUrl: remoteSection.properties['url'], + pushUrl: remoteSection.properties['pushurl'] ?? remoteSection.properties['url'], + isReadOnly: false + }); + } + } + + return remotes; +} + const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm; export function parseGitCommits(data: string): Commit[] { @@ -882,7 +945,8 @@ export class Repository { constructor( private _git: Git, private repositoryRoot: string, - readonly dotGit: { path: string; commonPath?: string } + readonly dotGit: { path: string; commonPath?: string }, + private logger: LogOutputChannel ) { } get git(): Git { @@ -1399,6 +1463,8 @@ export class Repository { if (/Please,? commit your changes or stash them/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.DirtyWorkTree; err.gitTreeish = treeish; + } else if (/You are on a branch yet to be born/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.BranchNotYetBorn; } throw err; @@ -1556,6 +1622,10 @@ export class Repository { } } + async mergeAbort(): Promise { + await this.exec(['merge', '--abort']); + } + async tag(name: string, message?: string): Promise { let args = ['tag']; @@ -1573,6 +1643,11 @@ export class Repository { await this.exec(args); } + async deleteRemoteTag(remoteName: string, tagName: string): Promise { + const args = ['push', '--delete', remoteName, tagName]; + await this.exec(args); + } + async clean(paths: string[]): Promise { const pathsByGroup = groupBy(paths.map(sanitizePath), p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); @@ -1690,12 +1765,34 @@ export class Repository { err.gitErrorCode = GitErrorCodes.NoRemoteRepositorySpecified; } else if (/Could not read from remote repository/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.RemoteConnectionError; + } else if (/! \[rejected\].*\(non-fast-forward\)/m.test(err.stderr || '')) { + // The local branch has outgoing changes and it cannot be fast-forwarded. + err.gitErrorCode = GitErrorCodes.BranchFastForwardRejected; } throw err; } } + async fetchTags(options: { remote: string; tags: string[]; force?: boolean }): Promise { + const args = ['fetch']; + const spawnOptions: SpawnOptions = { + env: { 'GIT_HTTP_USER_AGENT': this.git.userAgent } + }; + + args.push(options.remote); + + for (const tag of options.tags) { + args.push(`refs/tags/${tag}:refs/tags/${tag}`); + } + + if (options.force) { + args.push('--force'); + } + + await this.exec(args, spawnOptions); + } + async pull(rebase?: boolean, remote?: string, branch?: string, options: PullOptions = {}): Promise { const args = ['pull']; @@ -1735,6 +1832,8 @@ export class Repository { err.gitErrorCode = GitErrorCodes.CantLockRef; } else if (/cannot rebase onto multiple branches/i.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.CantRebaseMultipleBranches; + } else if (/! \[rejected\].*\(would clobber existing tag\)/m.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.TagConflict; } throw err; @@ -1824,7 +1923,7 @@ export class Repository { } } - async createStash(message?: string, includeUntracked?: boolean): Promise { + async createStash(message?: string, includeUntracked?: boolean, staged?: boolean): Promise { try { const args = ['stash', 'push']; @@ -1832,6 +1931,10 @@ export class Repository { args.push('-u'); } + if (staged) { + args.push('-S'); + } + if (message) { args.push('-m', message); } @@ -1897,25 +2000,37 @@ export class Repository { } } - getStatus(opts?: { limit?: number; ignoreSubmodules?: boolean; untrackedChanges?: 'mixed' | 'separate' | 'hidden' }): Promise<{ status: IFileStatus[]; statusLength: number; didHitLimit: boolean }> { - return new Promise<{ status: IFileStatus[]; statusLength: number; didHitLimit: boolean }>((c, e) => { + async getStatus(opts?: { limit?: number; ignoreSubmodules?: boolean; similarityThreshold?: number; untrackedChanges?: 'mixed' | 'separate' | 'hidden'; cancellationToken?: CancellationToken }): Promise<{ status: IFileStatus[]; statusLength: number; didHitLimit: boolean }> { + if (opts?.cancellationToken && opts?.cancellationToken.isCancellationRequested) { + throw new CancellationError(); + } + + const disposables: IDisposable[] = []; + + const env = { GIT_OPTIONAL_LOCKS: '0' }; + const args = ['status', '-z']; + + if (opts?.untrackedChanges === 'hidden') { + args.push('-uno'); + } else { + args.push('-uall'); + } + + if (opts?.ignoreSubmodules) { + args.push('--ignore-submodules'); + } + + // --find-renames option is only available starting with git 2.18.0 + if (opts?.similarityThreshold && opts.similarityThreshold !== 50 && this._git.compareGitVersionTo('2.18.0') !== -1) { + args.push(`--find-renames=${opts.similarityThreshold}%`); + } + + const child = this.stream(args, { env }); + + let result = new Promise<{ status: IFileStatus[]; statusLength: number; didHitLimit: boolean }>((c, e) => { const parser = new GitStatusParser(); - const env = { GIT_OPTIONAL_LOCKS: '0' }; - const args = ['status', '-z']; - if (opts?.untrackedChanges === 'hidden') { - args.push('-uno'); - } else { - args.push('-uall'); - } - - if (opts?.ignoreSubmodules) { - args.push('--ignore-submodules'); - } - - const child = this.stream(args, { env }); - - const onExit = (exitCode: number) => { + const onClose = (exitCode: number) => { if (exitCode !== 0) { const stderr = stderrData.join(''); return e(new GitError({ @@ -1936,7 +2051,7 @@ export class Repository { parser.update(raw); if (limit !== 0 && parser.status.length > limit) { - child.removeListener('exit', onExit); + child.removeListener('close', onClose); child.stdout!.removeListener('data', onStdoutData); child.kill(); @@ -1952,12 +2067,71 @@ export class Repository { child.stderr!.on('data', raw => stderrData.push(raw as string)); child.on('error', cpErrorHandler(e)); - child.on('exit', onExit); + child.on('close', onClose); }); + + if (opts?.cancellationToken) { + const cancellationPromise = new Promise<{ status: IFileStatus[]; statusLength: number; didHitLimit: boolean }>((_, e) => { + disposables.push(onceEvent(opts.cancellationToken!.onCancellationRequested)(() => { + try { + child.kill(); + } catch (err) { + // noop + } + + e(new CancellationError()); + })); + }); + + result = Promise.race([result, cancellationPromise]); + } + + try { + const { status, statusLength, didHitLimit } = await result; + return { status, statusLength, didHitLimit }; + } + finally { + dispose(disposables); + } + } + + async getHEADRef(): Promise { + let HEAD: Branch | undefined; + + try { + HEAD = await this.getHEAD(); + + if (HEAD.name) { + // Branch + HEAD = await this.getBranch(HEAD.name); + } else if (HEAD.commit) { + // Tag || Commit + const tags = await this.getRefs({ pattern: 'refs/tags' }); + const tag = tags.find(tag => tag.commit === HEAD!.commit); + + if (tag) { + HEAD = { ...HEAD, name: tag.name, type: RefType.Tag }; + } + } + } catch (err) { + // noop + } + + return HEAD; } async getHEAD(): Promise { try { + // Attempt to parse the HEAD file + const result = await this.getHEADFS(); + return result; + } + catch (err) { + this.logger.warn(err.message); + } + + try { + // Fallback to using git to determine HEAD const result = await this.exec(['symbolic-ref', '--short', 'HEAD']); if (!result.stdout) { @@ -1965,15 +2139,35 @@ export class Repository { } return { name: result.stdout.trim(), commit: undefined, type: RefType.Head }; - } catch (err) { - const result = await this.exec(['rev-parse', 'HEAD']); - - if (!result.stdout) { - throw new Error('Error parsing HEAD'); - } - - return { name: undefined, commit: result.stdout.trim(), type: RefType.Head }; } + catch (err) { } + + // Detached HEAD + const result = await this.exec(['rev-parse', 'HEAD']); + + if (!result.stdout) { + throw new Error('Error parsing HEAD'); + } + + return { name: undefined, commit: result.stdout.trim(), type: RefType.Head }; + } + + async getHEADFS(): Promise { + const raw = await fs.readFile(path.join(this.dotGit.path, 'HEAD'), 'utf8'); + + // Branch + const branchMatch = raw.match(/^ref: refs\/heads\/(?.*)$/m); + if (branchMatch?.groups?.name) { + return { name: branchMatch.groups.name, commit: undefined, type: RefType.Head }; + } + + // Detached + const commitMatch = raw.match(/^(?[0-9a-f]{40})$/m); + if (commitMatch?.groups?.commit) { + return { name: undefined, commit: commitMatch.groups.commit, type: RefType.Head }; + } + + throw new Error(`Unable to parse HEAD file. HEAD file contents: ${raw}.`); } async findTrackingBranches(upstreamBranch: string): Promise { @@ -1984,28 +2178,32 @@ export class Repository { .map(([ref]) => ({ name: ref, type: RefType.Head } as Branch)); } - async getRefs(opts?: { sort?: 'alphabetically' | 'committerdate'; contains?: string; pattern?: string; count?: number }): Promise { - const args = ['for-each-ref']; - - if (opts?.count) { - args.push(`--count=${opts.count}`); + async getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise { + if (cancellationToken && cancellationToken.isCancellationRequested) { + throw new CancellationError(); } - if (opts && opts.sort && opts.sort !== 'alphabetically') { - args.push('--sort', `-${opts.sort}`); + const args = ['for-each-ref']; + + if (query.count) { + args.push(`--count=${query.count}`); + } + + if (query.sort && query.sort !== 'alphabetically') { + args.push('--sort', `-${query.sort}`); } args.push('--format', '%(refname) %(objectname) %(*objectname)'); - if (opts?.pattern) { - args.push(opts.pattern); + if (query.pattern) { + args.push(query.pattern.startsWith('refs/') ? query.pattern : `refs/${query.pattern}`); } - if (opts?.contains) { - args.push('--contains', opts.contains); + if (query.contains) { + args.push('--contains', query.contains); } - const result = await this.exec(args); + const result = await this.exec(args, { cancellationToken }); const fn = (line: string): Ref | null => { let match: RegExpExecArray | null; @@ -2027,6 +2225,43 @@ export class Repository { .filter(ref => !!ref) as Ref[]; } + async getRemoteRefs(remote: string, opts?: { heads?: boolean; tags?: boolean; cancellationToken?: CancellationToken }): Promise { + if (opts?.cancellationToken && opts?.cancellationToken.isCancellationRequested) { + throw new CancellationError(); + } + + const args = ['ls-remote']; + + if (opts?.heads) { + args.push('--heads'); + } + + if (opts?.tags) { + args.push('--tags'); + } + + args.push(remote); + + const result = await this.exec(args, { cancellationToken: opts?.cancellationToken }); + + const fn = (line: string): Ref | null => { + let match: RegExpExecArray | null; + + if (match = /^([0-9a-f]{40})\trefs\/heads\/([^ ]+)$/.exec(line)) { + return { name: match[1], commit: match[2], type: RefType.Head }; + } else if (match = /^([0-9a-f]{40})\trefs\/tags\/([^ ]+)$/.exec(line)) { + return { name: match[2], commit: match[1], type: RefType.Tag }; + } + + return null; + }; + + return result.stdout.split('\n') + .filter(line => !!line) + .map(fn) + .filter(ref => !!ref) as Ref[]; + } + async getStashes(): Promise { const result = await this.exec(['stash', 'list']); const regex = /^stash@{(\d+)}:(.+)$/; @@ -2040,9 +2275,41 @@ export class Repository { } async getRemotes(): Promise { + const remotes: MutableRemote[] = []; + + try { + // Attempt to parse the config file + remotes.push(...await this.getRemotesFS()); + + if (remotes.length === 0) { + this.logger.info('No remotes found in the git config file.'); + } + } + catch (err) { + this.logger.warn(`getRemotes() - ${err.message}`); + + // Fallback to using git to get the remotes + remotes.push(...await this.getRemotesGit()); + } + + for (const remote of remotes) { + // https://github.com/microsoft/vscode/issues/45271 + remote.isReadOnly = remote.pushUrl === undefined || remote.pushUrl === 'no_push'; + } + + return remotes; + } + + private async getRemotesFS(): Promise { + const raw = await fs.readFile(path.join(this.dotGit.commonPath ?? this.dotGit.path, 'config'), 'utf8'); + return parseGitRemotes(raw); + } + + private async getRemotesGit(): Promise { + const remotes: MutableRemote[] = []; + const result = await this.exec(['remote', '--verbose']); const lines = result.stdout.trim().split('\n').filter(l => !!l); - const remotes: MutableRemote[] = []; for (const line of lines) { const parts = line.split(/\s/); @@ -2063,9 +2330,6 @@ export class Repository { remote.fetchUrl = url; remote.pushUrl = url; } - - // https://github.com/microsoft/vscode/issues/45271 - remote.isReadOnly = remote.pushUrl === undefined || remote.pushUrl === 'no_push'; } return remotes; @@ -2154,11 +2418,6 @@ export class Repository { return Promise.reject(new Error('No such branch')); } - async getBranches(query: BranchQuery): Promise { - const refs = await this.getRefs({ contains: query.contains, pattern: query.pattern ? `refs/${query.pattern}` : undefined, count: query.count }); - return refs.filter(value => (value.type !== RefType.Tag) && (query.remote || !value.remote)); - } - // TODO: Support core.commentChar stripCommitMessageComments(message: string): string { return message.replace(/^\s*#.*$\n?/gm, '').trim(); diff --git a/extensions/git/src/ipc/ipcServer.ts b/extensions/git/src/ipc/ipcServer.ts index 977b31eb78..131ccb3a0e 100644 --- a/extensions/git/src/ipc/ipcServer.ts +++ b/extensions/git/src/ipc/ipcServer.ts @@ -17,7 +17,7 @@ function getIPCHandlePath(id: string): string { return `\\\\.\\pipe\\vscode-git-${id}-sock`; } - if (process.env['XDG_RUNTIME_DIR']) { + if (process.platform !== 'darwin' && process.env['XDG_RUNTIME_DIR']) { return path.join(process.env['XDG_RUNTIME_DIR'] as string, `vscode-git-${id}.sock`); } diff --git a/extensions/git/src/log.ts b/extensions/git/src/log.ts deleted file mode 100644 index 17e3eefc95..0000000000 --- a/extensions/git/src/log.ts +++ /dev/null @@ -1,115 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vscode-nls'; -const localize = nls.loadMessageBundle(); - -import { commands, Disposable, Event, EventEmitter, OutputChannel, window, workspace } from 'vscode'; -import { dispose } from './util'; - -/** - * The severity level of a log message - */ -export enum LogLevel { - Trace = 1, - Debug = 2, - Info = 3, - Warning = 4, - Error = 5, - Critical = 6, - Off = 7 -} - -/** - * Output channel logger - */ -export class OutputChannelLogger { - - private _onDidChangeLogLevel = new EventEmitter(); - readonly onDidChangeLogLevel: Event = this._onDidChangeLogLevel.event; - - private _currentLogLevel!: LogLevel; - get currentLogLevel(): LogLevel { - return this._currentLogLevel; - } - set currentLogLevel(value: LogLevel) { - if (this._currentLogLevel === value) { - return; - } - - this._currentLogLevel = value; - this._onDidChangeLogLevel.fire(value); - - this.log(localize('gitLogLevel', "Log level: {0}", LogLevel[value])); - } - - private _defaultLogLevel!: LogLevel; - get defaultLogLevel(): LogLevel { - return this._defaultLogLevel; - } - - private _outputChannel: OutputChannel; - private _disposables: Disposable[] = []; - - constructor() { - // Output channel - this._outputChannel = window.createOutputChannel('Git'); - commands.registerCommand('git.showOutput', () => this.showOutputChannel()); - this._disposables.push(this._outputChannel); - - this._disposables.push(workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('git.logLevel')) { - this.onLogLevelChange(); - } - })); - this.onLogLevelChange(); - } - - private onLogLevelChange(): void { - const config = workspace.getConfiguration('git'); - const logLevel: keyof typeof LogLevel = config.get('logLevel', 'Info'); - this.currentLogLevel = this._defaultLogLevel = LogLevel[logLevel] ?? LogLevel.Info; - } - - log(message: string, logLevel?: LogLevel): void { - if (logLevel && logLevel < this._currentLogLevel) { - return; - } - - this._outputChannel.appendLine(`[${new Date().toISOString()}]${logLevel ? ` [${LogLevel[logLevel].toLowerCase()}]` : ''} ${message}`); - } - - logCritical(message: string): void { - this.log(message, LogLevel.Critical); - } - - logDebug(message: string): void { - this.log(message, LogLevel.Debug); - } - - logError(message: string): void { - this.log(message, LogLevel.Error); - } - - logInfo(message: string): void { - this.log(message, LogLevel.Info); - } - - logTrace(message: string): void { - this.log(message, LogLevel.Trace); - } - - logWarning(message: string): void { - this.log(message, LogLevel.Warning); - } - - showOutputChannel(): void { - this._outputChannel.show(); - } - - dispose(): void { - this._disposables = dispose(this._disposables); - } -} diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 33c102ebc3..578ab5c9f5 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -3,11 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vscode-nls'; -const localize = nls.loadMessageBundle(); - -import { ExtensionContext, workspace, Disposable, commands } from 'vscode'; -import { findGit, Git } from './git'; +import { ExtensionContext, workspace, window, Disposable, commands, LogOutputChannel, l10n, LogLevel } from 'vscode'; // {{SQL CARBON EDIT}} - remove unused +import { findGit, Git } from './git'; // {{SQL CARBON EDIT}} - remove unused import { Model } from './model'; import { CommandCenter } from './commands'; import { GitFileSystemProvider } from './fileSystemProvider'; @@ -19,14 +16,15 @@ import { GitExtension } from './api/git'; import { GitProtocolHandler } from './protocolHandler'; import { GitExtensionImpl } from './api/extension'; import * as path from 'path'; -// import * as fs from 'fs'; +// import * as fs from 'fs'; // {{SQL CARBON EDIT}} - remove unused import * as os from 'os'; import { GitTimelineProvider } from './timelineProvider'; import { registerAPICommands } from './api/api1'; import { TerminalEnvironmentManager } from './terminal'; -import { OutputChannelLogger } from './log'; import { createIPCServer, IPCServer } from './ipc/ipcServer'; import { GitEditor } from './gitEditor'; +// import { GitPostCommitCommandsProvider } from './postCommitCommands'; // {{SQL CARBON EDIT}} - remove unused +import { GitEditSessionIdentityProvider } from './editSessionIdentityProvider'; const deactivateTasks: { (): Promise }[] = []; @@ -36,7 +34,7 @@ export async function deactivate(): Promise { } } -async function createModel(context: ExtensionContext, outputChannelLogger: OutputChannelLogger, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise { +async function createModel(context: ExtensionContext, logger: LogOutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise { const pathValue = workspace.getConfiguration('git').get('path'); let pathHints = Array.isArray(pathValue) ? pathValue : pathValue ? [pathValue] : []; @@ -49,7 +47,7 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu } const info = await findGit(pathHints, gitPath => { - outputChannelLogger.logInfo(localize('validating', "Validating found git in: {0}", gitPath)); + logger.info(l10n.t('Validating found git in: "{0}"', gitPath)); if (excludes.length === 0) { return true; } @@ -57,7 +55,7 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu const normalized = path.normalize(gitPath).replace(/[\r\n]+$/, ''); const skip = excludes.some(e => normalized.startsWith(e)); if (skip) { - outputChannelLogger.logInfo(localize('skipped', "Skipped found git in: {0}", gitPath)); + logger.info(l10n.t('Skipped found git in: "{0}"', gitPath)); } return !skip; }); @@ -67,7 +65,7 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu try { ipcServer = await createIPCServer(context.storagePath); } catch (err) { - outputChannelLogger.logError(`Failed to create git IPC: ${err}`); + logger.error(`Failed to create git IPC: ${err}`); } const askpass = new Askpass(ipcServer); @@ -80,15 +78,15 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu const terminalEnvironmentManager = new TerminalEnvironmentManager(context, [askpass, gitEditor, ipcServer]); disposables.push(terminalEnvironmentManager); - outputChannelLogger.logInfo(localize('using git', "Using git {0} from {1}", info.version, info.path)); + logger.info(l10n.t('Using git "{0}" from "{1}"', info.version, info.path)); const git = new Git({ gitPath: info.path, - userAgent: `git/${info.version} (${(os as any).version?.() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) azuredatudio`, + userAgent: `git/${info.version} (${(os as any).version?.() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) azuredatudio`, // {{SQL CARBON EDIT}} - update product name version: info.version, env: environment, }); - const model = new Model(git, askpass, context.globalState, outputChannelLogger, telemetryReporter); + const model = new Model(git, askpass, context.globalState, logger, telemetryReporter); disposables.push(model); const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`); @@ -103,24 +101,25 @@ async function createModel(context: ExtensionContext, outputChannelLogger: Outpu lines.pop(); } - outputChannelLogger.log(lines.join('\n')); + logger.appendLine(lines.join('\n')); }; git.onOutput.addListener('log', onOutput); disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); - const cc = new CommandCenter(git, model, outputChannelLogger, telemetryReporter); + const cc = new CommandCenter(git, model, context.globalState, logger, telemetryReporter); disposables.push( cc, new GitFileSystemProvider(model), new GitDecorations(model), - new GitProtocolHandler(), - new GitTimelineProvider(model, cc) + new GitTimelineProvider(model, cc), + new GitEditSessionIdentityProvider(model) ); // const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); {{SQL CARBON TODO}} lewissanchez - Do we need this? // model.registerPostCommitCommandsProvider(postCommitCommandsProvider); {{SQL CARBON TODO}} lewissanchez - Do we need this? // checkGitVersion(info); {{SQL CARBON EDIT}} Don't check git version + // commands.executeCommand('setContext', 'gitVersion2.35', git.compareGitVersionTo('2.35') >= 0); return model; } @@ -159,10 +158,10 @@ async function warnAboutMissingGit(): Promise { return; } - const download = localize('downloadgit', "Download Git"); - const neverShowAgain = localize('neverShowAgain', "Don't Show Again"); + const download = l10n.t('Download Git'); + const neverShowAgain = l10n.t('Don\'t Show Again'); const choice = await window.showWarningMessage( - localize('notfound', "Git not found. Install it or configure it using the 'git.path' setting."), + l10n.t('Git not found. Install it or configure it using the "git.path" setting.'), download, neverShowAgain ); @@ -172,17 +171,23 @@ async function warnAboutMissingGit(): Promise { } else if (choice === neverShowAgain) { await config.update('ignoreMissingGitWarning', true, true); } -}*/ +}*/ // {{SQl CARBON EDIT}} - end comment block export async function _activate(context: ExtensionContext): Promise { const disposables: Disposable[] = []; context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose())); - const outputChannelLogger = new OutputChannelLogger(); - disposables.push(outputChannelLogger); + const logger = window.createOutputChannel('Git', { log: true }); + disposables.push(logger); - const { name, version, aiKey } = require('../package.json') as { name: string; version: string; aiKey: string }; - const telemetryReporter = new TelemetryReporter(name, version, aiKey); + const onDidChangeLogLevel = (logLevel: LogLevel) => { + logger.appendLine(l10n.t('Log level: {0}', LogLevel[logLevel])); + }; + disposables.push(logger.onDidChangeLogLevel(onDidChangeLogLevel)); + onDidChangeLogLevel(logger.logLevel); + + const { aiKey } = require('../package.json') as { aiKey: string }; + const telemetryReporter = new TelemetryReporter(aiKey); deactivateTasks.push(() => telemetryReporter.dispose()); const config = workspace.getConfiguration('git', null); @@ -193,12 +198,12 @@ export async function _activate(context: ExtensionContext): Promise workspace.getConfiguration('git', null).get('enabled') === true); const result = new GitExtensionImpl(); - eventToPromise(onEnabled).then(async () => result.model = await createModel(context, outputChannelLogger, telemetryReporter, disposables)); + eventToPromise(onEnabled).then(async () => result.model = await createModel(context, logger, telemetryReporter, disposables)); return result; } try { - const model = await createModel(context, outputChannelLogger, telemetryReporter, disposables); + const model = await createModel(context, logger, telemetryReporter, disposables); return new GitExtensionImpl(model); } catch (err) { if (!/Git installation not found/.test(err.message || '')) { @@ -206,7 +211,7 @@ export async function _activate(context: ExtensionContext): Promise extends Map { + constructor() { + super(); + this.updateContextKey(); + } + + override set(key: string, value: T): this { + const result = super.set(key, value); + this.updateContextKey(); + + return result; + } + + override delete(key: string): boolean { + const result = super.delete(key); + this.updateContextKey(); + + return result; + } + + abstract updateContextKey(): void; +} + +/** + * Key - normalized path used in user interface + * Value - path extracted from the output of the `git status` command + * used when calling `git config --global --add safe.directory` + */ +class UnsafeRepositoryMap extends RepositoryMap { + updateContextKey(): void { + commands.executeCommand('setContext', 'git.unsafeRepositoryCount', this.size); + } +} + +/** + * Key - normalized path used in user interface + * Value - value indicating whether the repository should be opened + */ +class ParentRepositoryMap extends RepositoryMap { + updateContextKey(): void { + commands.executeCommand('setContext', 'git.parentRepositoryCount', this.size); + } +} + export interface ModelChangeEvent { repository: Repository; uri: Uri; @@ -51,7 +92,7 @@ interface OpenRepository extends Disposable { repository: Repository; } -export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry { +export class Model implements IBranchProtectionProviderRegistry, IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry { private _onDidOpenRepository = new EventEmitter(); readonly onDidOpenRepository: Event = this._onDidOpenRepository.event; @@ -111,12 +152,36 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand private _onDidChangePostCommitCommandsProviders = new EventEmitter(); readonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event; - private showRepoOnHomeDriveRootWarning = true; + private branchProtectionProviders = new Map>(); + + private _onDidChangeBranchProtectionProviders = new EventEmitter(); + readonly onDidChangeBranchProtectionProviders = this._onDidChangeBranchProtectionProviders.event; + private pushErrorHandlers = new Set(); + private _unsafeRepositories = new UnsafeRepositoryMap(); + get unsafeRepositories(): UnsafeRepositoryMap { + return this._unsafeRepositories; + } + + private _parentRepositories = new ParentRepositoryMap(); + get parentRepositories(): ParentRepositoryMap { + return this._parentRepositories; + } + + /** + * We maintain a map containing both the path and the canonical path of the + * workspace folders. We are doing this as `git.exe` expands the symbolic links + * while there are scenarios in which VS Code does not. + * + * Key - path of the workspace folder + * Value - canonical path of the workspace folder + */ + private _workspaceFolders = new Map(); + private disposables: Disposable[] = []; - constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private outputChannelLogger: OutputChannelLogger, private telemetryReporter: TelemetryReporter) { + constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) { workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables); window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables); workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); @@ -134,11 +199,40 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand } private async doInitialScan(): Promise { - await Promise.all([ + const config = workspace.getConfiguration('git'); + const autoRepositoryDetection = config.get('autoRepositoryDetection'); + const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt'); + + // Initial repository scan function + const initialScanFn = () => Promise.all([ this.onDidChangeWorkspaceFolders({ added: workspace.workspaceFolders || [], removed: [] }), this.onDidChangeVisibleTextEditors(window.visibleTextEditors), this.scanWorkspaceFolders() ]); + + if (config.get('showProgress', true)) { + await window.withProgress({ location: ProgressLocation.SourceControl }, initialScanFn); + } else { + await initialScanFn(); + } + + if (this._parentRepositories.size !== 0 && + parentRepositoryConfig === 'prompt') { + // Parent repositories notification + this.showParentRepositoryNotification(); + } else if (this._unsafeRepositories.size !== 0) { + // Unsafe repositories notification + this.showUnsafeRepositoryNotification(); + } + + /* __GDPR__ + "git.repositoryInitialScan" : { + "owner": "lszomoru", + "autoRepositoryDetection": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Setting that controls the initial repository scan" }, + "repositoryCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Number of repositories opened during initial repository scan" } + } + */ + this.telemetryReporter.sendTelemetryEvent('git.repositoryInitialScan', { autoRepositoryDetection: String(autoRepositoryDetection) }, { repositoryCount: this.openRepositories.length }); } /** @@ -149,7 +243,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand private async scanWorkspaceFolders(): Promise { const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); - this.outputChannelLogger.logTrace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`); + this.logger.trace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`); if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') { return; @@ -157,7 +251,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand await Promise.all((workspace.workspaceFolders || []).map(async folder => { const root = folder.uri.fsPath; - this.outputChannelLogger.logTrace(`[swsf] Workspace folder: ${root}`); + this.logger.trace(`[swsf] Workspace folder: ${root}`); // Workspace folder children const repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('repositoryScanMaxDepth', 1); @@ -167,17 +261,17 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand // Repository scan folders const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get('scanRepositories') || []; - this.outputChannelLogger.logTrace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`); + this.logger.trace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`); for (const scanPath of scanPaths) { if (scanPath === '.git') { - this.outputChannelLogger.logTrace('[swsf] \'.git\' not supported in \'git.scanRepositories\' setting.'); + this.logger.trace('[swsf] \'.git\' not supported in \'git.scanRepositories\' setting.'); continue; } if (path.isAbsolute(scanPath)) { - const notSupportedMessage = localize('not supported', "Absolute paths not supported in 'git.scanRepositories' setting."); - this.outputChannelLogger.logWarning(notSupportedMessage); + const notSupportedMessage = l10n.t('Absolute paths not supported in "git.scanRepositories" setting.'); + this.logger.warn(notSupportedMessage); console.warn(notSupportedMessage); continue; } @@ -185,7 +279,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand subfolders.add(path.join(root, scanPath)); } - this.outputChannelLogger.logTrace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`); + this.logger.trace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`); await Promise.all([...subfolders].map(f => this.openRepository(f))); })); } @@ -256,7 +350,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand .filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[]; openRepositoriesToDispose.forEach(r => r.dispose()); - this.outputChannelLogger.logTrace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); + this.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath))); } @@ -270,20 +364,20 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand .filter(({ root }) => workspace.getConfiguration('git', root).get('enabled') !== true) .map(({ repository }) => repository); - this.outputChannelLogger.logTrace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); + this.logger.trace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`); possibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath)); openRepositoriesToDispose.forEach(r => r.dispose()); } private async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise { if (!workspace.isTrusted) { - this.outputChannelLogger.logTrace('[svte] Workspace is not trusted.'); + this.logger.trace('[svte] Workspace is not trusted.'); return; } const config = workspace.getConfiguration('git'); const autoRepositoryDetection = config.get('autoRepositoryDetection'); - this.outputChannelLogger.logTrace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`); + this.logger.trace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`); if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') { return; @@ -299,20 +393,20 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand const repository = this.getRepository(uri); if (repository) { - this.outputChannelLogger.logTrace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`); + this.logger.trace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`); return; } - this.outputChannelLogger.logTrace(`[svte] Open repository for editor resource ${uri.fsPath}`); + this.logger.trace(`[svte] Open repository for editor resource ${uri.fsPath}`); await this.openRepository(path.dirname(uri.fsPath)); })); } @sequentialize async openRepository(repoPath: string): Promise { - this.outputChannelLogger.logTrace(`Opening repository: ${repoPath}`); - if (this.getRepository(repoPath)) { - this.outputChannelLogger.logTrace(`Repository for path ${repoPath} already exists`); + this.logger.trace(`Opening repository: ${repoPath}`); + if (this.getRepositoryExact(repoPath)) { + this.logger.trace(`Repository for path ${repoPath} already exists`); return; } @@ -320,7 +414,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand const enabled = config.get('enabled') === true; if (!enabled) { - this.outputChannelLogger.logTrace('Git is not enabled'); + this.logger.trace('Git is not enabled'); return; } @@ -330,7 +424,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand fs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK); const result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']); if (result.stderr.trim() === '' && result.stdout.trim() === '') { - this.outputChannelLogger.logTrace(`Bare repository: ${repoPath}`); + this.logger.trace(`Bare repository: ${repoPath}`); return; } } catch { @@ -339,49 +433,88 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand } try { - const rawRoot = await this.git.getRepositoryRoot(repoPath); + const { repositoryRoot, unsafeRepositoryMatch } = await this.getRepositoryRoot(repoPath); + this.logger.trace(`Repository root for path ${repoPath} is: ${repositoryRoot}`); - // This can happen whenever `path` has the wrong case sensitivity in - // case insensitive file systems - // https://github.com/microsoft/vscode/issues/33498 - const repositoryRoot = Uri.file(rawRoot).fsPath; - this.outputChannelLogger.logTrace(`Repository root: ${repositoryRoot}`); - - if (this.getRepository(repositoryRoot)) { - this.outputChannelLogger.logTrace(`Repository for path ${repositoryRoot} already exists`); + if (this.getRepositoryExact(repositoryRoot)) { + this.logger.trace(`Repository for path ${repositoryRoot} already exists`); return; } - if (this.shouldRepositoryBeIgnored(rawRoot)) { - this.outputChannelLogger.logTrace(`Repository for path ${repositoryRoot} is ignored`); + if (this.shouldRepositoryBeIgnored(repositoryRoot)) { + this.logger.trace(`Repository for path ${repositoryRoot} is ignored`); return; } - // On Window, opening a git repository from the root of the HOMEDRIVE poses a security risk. - // We will only a open git repository from the root of the HOMEDRIVE if the user explicitly - // opens the HOMEDRIVE as a folder. Only show the warning once during repository discovery. - if (process.platform === 'win32' && process.env.HOMEDRIVE && pathEquals(`${process.env.HOMEDRIVE}\\`, repositoryRoot)) { - const isRepoInWorkspaceFolders = (workspace.workspaceFolders ?? []).find(f => pathEquals(f.uri.fsPath, repositoryRoot))!!; + // Handle git repositories that are in parent folders + const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt'); + if (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) { + const isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot); + if (isRepositoryOutsideWorkspace) { + this.logger.trace(`Repository in parent folder: ${repositoryRoot}`); - if (!isRepoInWorkspaceFolders) { - if (this.showRepoOnHomeDriveRootWarning) { - window.showWarningMessage(localize('repoOnHomeDriveRootWarning', "Unable to automatically open the git repository at '{0}'. To open that git repository, open it directly as a folder in VS Code.", repositoryRoot)); - this.showRepoOnHomeDriveRootWarning = false; + if (!this._parentRepositories.has(repositoryRoot)) { + // Show a notification if the parent repository is opened after the initial scan + if (this.state === 'initialized' && parentRepositoryConfig === 'prompt') { + this.showParentRepositoryNotification(); + } + + this._parentRepositories.set(repositoryRoot); } - this.outputChannelLogger.logTrace(`Repository for path ${repositoryRoot} is on the root of the HOMEDRIVE`); return; } } + // Handle unsafe repositories + if (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) { + this.logger.trace(`Unsafe repository: ${repositoryRoot}`); + + // Show a notification if the unsafe repository is opened after the initial scan + if (this._state === 'initialized' && !this._unsafeRepositories.has(repositoryRoot)) { + this.showUnsafeRepositoryNotification(); + } + + this._unsafeRepositories.set(repositoryRoot, unsafeRepositoryMatch[2]); + + return; + } + + // Open repository const dotGit = await this.git.getRepositoryDotGit(repositoryRoot); - const repository = new Repository(this.git.open(repositoryRoot, dotGit), this, this, this, this.globalState, this.outputChannelLogger, this.telemetryReporter); + const repository = new Repository(this.git.open(repositoryRoot, dotGit, this.logger), this, this, this, this, this.globalState, this.logger, this.telemetryReporter); this.open(repository); repository.status(); // do not await this, we want SCM to know about the repo asap - } catch (ex) { + } catch (err) { // noop - this.outputChannelLogger.logTrace(`Opening repository for path='${repoPath}' failed; ex=${ex}`); + this.logger.trace(`Opening repository for path='${repoPath}' failed; ex=${err}`); + } + } + + async openParentRepository(repoPath: string): Promise { + // Mark the repository to be opened from the parent folders + this.globalState.update(`parentRepository:${repoPath}`, true); + + await this.openRepository(repoPath); + this.parentRepositories.delete(repoPath); + } + + private async getRepositoryRoot(repoPath: string): Promise<{ repositoryRoot: string; unsafeRepositoryMatch: RegExpMatchArray | null }> { + try { + const rawRoot = await this.git.getRepositoryRoot(repoPath); + + // This can happen whenever `path` has the wrong case sensitivity in case + // insensitive file systems https://github.com/microsoft/vscode/issues/33498 + return { repositoryRoot: Uri.file(rawRoot).fsPath, unsafeRepositoryMatch: null }; + } catch (err) { + // Handle unsafe repository + const unsafeRepositoryMatch = /^fatal: detected dubious ownership in repository at \'([^']+)\'[\s\S]*git config --global --add safe\.directory '?([^'\n]+)'?$/m.exec(err.stderr); + if (unsafeRepositoryMatch && unsafeRepositoryMatch.length === 3) { + return { repositoryRoot: path.normalize(unsafeRepositoryMatch[1]), unsafeRepositoryMatch }; + } + + throw err; } } @@ -407,7 +540,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand } private open(repository: Repository): void { - this.outputChannelLogger.logInfo(`Open repository: ${repository.root}`); + this.logger.info(`Open repository: ${repository.root}`); const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed); const disappearListener = onDidDisappearRepository(() => dispose()); @@ -424,28 +557,62 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand const checkForSubmodules = () => { if (!shouldDetectSubmodules) { + this.logger.trace('Automatic detection of git submodules is not enabled.'); return; } if (repository.submodules.length > submodulesLimit) { - window.showWarningMessage(localize('too many submodules', "The '{0}' repository has {1} submodules which won't be opened automatically. You can still open each one individually by opening a file within.", path.basename(repository.root), repository.submodules.length)); + window.showWarningMessage(l10n.t('The "{0}" repository has {1} submodules which won\'t be opened automatically. You can still open each one individually by opening a file within.', path.basename(repository.root), repository.submodules.length)); statusListener.dispose(); } repository.submodules .slice(0, submodulesLimit) .map(r => path.join(repository.root, r.path)) - .forEach(p => this.eventuallyScanPossibleGitRepository(p)); + .forEach(p => { + this.logger.trace(`Opening submodule: '${p}'`); + this.eventuallyScanPossibleGitRepository(p); + }); }; - const statusListener = repository.onDidRunGitStatus(checkForSubmodules); + const updateMergeChanges = () => { + // set mergeChanges context + const mergeChanges: Uri[] = []; + for (const { repository } of this.openRepositories.values()) { + for (const state of repository.mergeGroup.resourceStates) { + mergeChanges.push(state.resourceUri); + } + } + commands.executeCommand('setContext', 'git.mergeChanges', mergeChanges); + }; + + const statusListener = repository.onDidRunGitStatus(() => { + checkForSubmodules(); + updateMergeChanges(); + }); checkForSubmodules(); + const updateOperationInProgressContext = () => { + let operationInProgress = false; + for (const { repository } of this.openRepositories.values()) { + if (repository.operations.shouldDisableCommands()) { + operationInProgress = true; + } + } + + commands.executeCommand('setContext', 'operationInProgress', operationInProgress); + }; + + const operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event); + const operationListener = operationEvent(() => updateOperationInProgressContext()); + updateOperationInProgressContext(); + const dispose = () => { disappearListener.dispose(); changeListener.dispose(); originalResourceChangeListener.dispose(); statusListener.dispose(); + operationListener.dispose(); repository.dispose(); this.openRepositories = this.openRepositories.filter(e => e !== openRepository); @@ -454,6 +621,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand const openRepository = { repository, dispose }; this.openRepositories.push(openRepository); + updateMergeChanges(); this._onDidOpenRepository.fire(repository); } @@ -464,13 +632,13 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand return; } - this.outputChannelLogger.logInfo(`Close repository: ${repository.root}`); + this.logger.info(`Close repository: ${repository.root}`); openRepository.dispose(); } async pickRepository(): Promise { if (this.openRepositories.length === 0) { - throw new Error(localize('no repositories', "There are no available repositories")); + throw new Error(l10n.t('There are no available repositories')); } const picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index)); @@ -483,7 +651,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand picks.unshift(...picks.splice(index, 1)); } - const placeHolder = localize('pick repo', "Choose a repository"); + const placeHolder = l10n.t('Choose a repository'); const pick = await window.showQuickPick(picks, { placeHolder }); return pick && pick.repository; @@ -498,6 +666,12 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand return liveRepository && liveRepository.repository; } + private getRepositoryExact(repoPath: string): Repository | undefined { + const openRepository = this.openRepositories + .find(r => pathEquals(r.repository.root, repoPath)); + return openRepository?.repository; + } + private getOpenRepository(repository: Repository): OpenRepository | undefined; private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined; private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined; @@ -556,7 +730,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand return liveRepository; } - if (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup) { + if (hint === repository.mergeGroup || hint === repository.indexGroup || hint === repository.workingTreeGroup || hint === repository.untrackedGroup) { return liveRepository; } } @@ -592,6 +766,31 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand return [...this.remoteSourcePublishers.values()]; } + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable { + const providerDisposables: Disposable[] = []; + + this.branchProtectionProviders.set(root.toString(), (this.branchProtectionProviders.get(root.toString()) ?? new Set()).add(provider)); + providerDisposables.push(provider.onDidChangeBranchProtection(uri => this._onDidChangeBranchProtectionProviders.fire(uri))); + + this._onDidChangeBranchProtectionProviders.fire(root); + + return toDisposable(() => { + const providers = this.branchProtectionProviders.get(root.toString()); + + if (providers && providers.has(provider)) { + providers.delete(provider); + this.branchProtectionProviders.set(root.toString(), providers); + this._onDidChangeBranchProtectionProviders.fire(root); + } + + dispose(providerDisposables); + }); + } + + getBranchProtectionProviders(root: Uri): BranchProtectionProvider[] { + return [...(this.branchProtectionProviders.get(root.toString()) ?? new Set()).values()]; + } + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable { this.postCommitCommandsProviders.add(provider); this._onDidChangePostCommitCommandsProviders.fire(); @@ -619,6 +818,89 @@ export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommand return [...this.pushErrorHandlers]; } + private async isRepositoryOutsideWorkspace(repositoryPath: string): Promise { + const workspaceFolders = (workspace.workspaceFolders || []) + .filter(folder => folder.uri.scheme === 'file'); + + if (workspaceFolders.length === 0) { + return true; + } + + const result = await Promise.all(workspaceFolders.map(async folder => { + const workspaceFolderRealPath = await this.getWorkspaceFolderRealPath(folder); + return workspaceFolderRealPath ? pathEquals(workspaceFolderRealPath, repositoryPath) || isDescendant(workspaceFolderRealPath, repositoryPath) : undefined; + })); + + return !result.some(r => r); + } + + private async getWorkspaceFolderRealPath(workspaceFolder: WorkspaceFolder): Promise { + let result = this._workspaceFolders.get(workspaceFolder.uri.fsPath); + + if (!result) { + try { + result = await fs.promises.realpath(workspaceFolder.uri.fsPath, { encoding: 'utf8' }); + this._workspaceFolders.set(workspaceFolder.uri.fsPath, result); + } catch (err) { + // noop - Workspace folder does not exist + this.logger.trace(`Failed to resolve workspace folder: "${workspaceFolder.uri.fsPath}". ${err}`); + } + } + + return result; + } + + private async showParentRepositoryNotification(): Promise { + const message = this.parentRepositories.size === 1 ? + l10n.t('A git repository was found in the parent folders of the workspace or the open file(s). Would you like to open the repository?') : + l10n.t('Git repositories were found in the parent folders of the workspace or the open file(s). Would you like to open the repositories?'); + + const yes = l10n.t('Yes'); + const always = l10n.t('Always'); + const never = l10n.t('Never'); + + const choice = await window.showInformationMessage(message, yes, always, never); + if (choice === yes) { + // Open Parent Repositories + commands.executeCommand('git.openRepositoriesInParentFolders'); + } else if (choice === always || choice === never) { + // Update setting + const config = workspace.getConfiguration('git'); + await config.update('openRepositoryInParentFolders', choice === always ? 'always' : 'never', true); + + if (choice === always) { + for (const parentRepository of [...this.parentRepositories.keys()]) { + await this.openParentRepository(parentRepository); + } + } + } + } + + private async showUnsafeRepositoryNotification(): Promise { + // If no repositories are open, we will use a welcome view to inform the user + // that a potentially unsafe repository was found so we do not have to show + // the notification + if (this.repositories.length === 0) { + return; + } + + const message = this._unsafeRepositories.size === 1 ? + l10n.t('The git repository in the current folder is potentially unsafe as the folder is owned by someone other than the current user.') : + l10n.t('The git repositories in the current folder are potentially unsafe as the folders are owned by someone other than the current user.'); + + const manageUnsafeRepositories = l10n.t('Manage Unsafe Repositories'); + const learnMore = l10n.t('Learn More'); + + const choice = await window.showErrorMessage(message, manageUnsafeRepositories, learnMore); + if (choice === manageUnsafeRepositories) { + // Manage Unsafe Repositories + commands.executeCommand('git.manageUnsafeRepositories'); + } else if (choice === learnMore) { + // Learn More + commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-git-unsafe-repository')); + } + } + dispose(): void { const openRepositories = [...this.openRepositories]; openRepositories.forEach(r => r.dispose()); diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts new file mode 100644 index 0000000000..a96ccd8fd7 --- /dev/null +++ b/extensions/git/src/operation.ts @@ -0,0 +1,273 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LogOutputChannel } from 'vscode'; + +export const enum OperationKind { + Add = 'Add', + AddNoProgress = 'AddNoProgress', + Apply = 'Apply', + Blame = 'Blame', + Branch = 'Branch', + CheckIgnore = 'CheckIgnore', + Checkout = 'Checkout', + CheckoutTracking = 'CheckoutTracking', + CherryPick = 'CherryPick', + Clean = 'Clean', + CleanNoProgress = 'CleanNoProgress', + Commit = 'Commit', + Config = 'Config', + DeleteBranch = 'DeleteBranch', + DeleteRef = 'DeleteRef', + DeleteRemoteTag = 'DeleteRemoteTag', + DeleteTag = 'DeleteTag', + Diff = 'Diff', + Fetch = 'Fetch', + FetchNoProgress = 'FetchNoProgress', + FindTrackingBranches = 'GetTracking', + GetBranch = 'GetBranch', + GetBranches = 'GetBranches', + GetCommitTemplate = 'GetCommitTemplate', + GetObjectDetails = 'GetObjectDetails', + GetRefs = 'GetRefs', + GetRemoteRefs = 'GetRemoteRefs', + HashObject = 'HashObject', + Ignore = 'Ignore', + Log = 'Log', + LogFile = 'LogFile', + Merge = 'Merge', + MergeAbort = 'MergeAbort', + MergeBase = 'MergeBase', + Move = 'Move', + PostCommitCommand = 'PostCommitCommand', + Pull = 'Pull', + Push = 'Push', + Remote = 'Remote', + RenameBranch = 'RenameBranch', + Remove = 'Remove', + Reset = 'Reset', + Rebase = 'Rebase', + RebaseAbort = 'RebaseAbort', + RebaseContinue = 'RebaseContinue', + RevertFiles = 'RevertFiles', + RevertFilesNoProgress = 'RevertFilesNoProgress', + SetBranchUpstream = 'SetBranchUpstream', + Show = 'Show', + Stage = 'Stage', + Status = 'Status', + Stash = 'Stash', + SubmoduleUpdate = 'SubmoduleUpdate', + Sync = 'Sync', + Tag = 'Tag', +} + +export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | + CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | + DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | + GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation | + HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | + MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation | + ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RevertFilesOperation | SetBranchUpstreamOperation | + ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation | SyncOperation | TagOperation; + +type BaseOperation = { kind: OperationKind; blocking: boolean; readOnly: boolean; remote: boolean; retry: boolean; showProgress: boolean }; +export type AddOperation = BaseOperation & { kind: OperationKind.Add }; +export type ApplyOperation = BaseOperation & { kind: OperationKind.Apply }; +export type BlameOperation = BaseOperation & { kind: OperationKind.Blame }; +export type BranchOperation = BaseOperation & { kind: OperationKind.Branch }; +export type CheckIgnoreOperation = BaseOperation & { kind: OperationKind.CheckIgnore }; +export type CherryPickOperation = BaseOperation & { kind: OperationKind.CherryPick }; +export type CheckoutOperation = BaseOperation & { kind: OperationKind.Checkout; refLabel: string }; +export type CheckoutTrackingOperation = BaseOperation & { kind: OperationKind.CheckoutTracking; refLabel: string }; +export type CleanOperation = BaseOperation & { kind: OperationKind.Clean }; +export type CommitOperation = BaseOperation & { kind: OperationKind.Commit }; +export type ConfigOperation = BaseOperation & { kind: OperationKind.Config }; +export type DeleteBranchOperation = BaseOperation & { kind: OperationKind.DeleteBranch }; +export type DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef }; +export type DeleteRemoteTagOperation = BaseOperation & { kind: OperationKind.DeleteRemoteTag }; +export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag }; +export type DiffOperation = BaseOperation & { kind: OperationKind.Diff }; +export type FetchOperation = BaseOperation & { kind: OperationKind.Fetch }; +export type FindTrackingBranchesOperation = BaseOperation & { kind: OperationKind.FindTrackingBranches }; +export type GetBranchOperation = BaseOperation & { kind: OperationKind.GetBranch }; +export type GetBranchesOperation = BaseOperation & { kind: OperationKind.GetBranches }; +export type GetCommitTemplateOperation = BaseOperation & { kind: OperationKind.GetCommitTemplate }; +export type GetObjectDetailsOperation = BaseOperation & { kind: OperationKind.GetObjectDetails }; +export type GetRefsOperation = BaseOperation & { kind: OperationKind.GetRefs }; +export type GetRemoteRefsOperation = BaseOperation & { kind: OperationKind.GetRemoteRefs }; +export type HashObjectOperation = BaseOperation & { kind: OperationKind.HashObject }; +export type IgnoreOperation = BaseOperation & { kind: OperationKind.Ignore }; +export type LogOperation = BaseOperation & { kind: OperationKind.Log }; +export type LogFileOperation = BaseOperation & { kind: OperationKind.LogFile }; +export type MergeOperation = BaseOperation & { kind: OperationKind.Merge }; +export type MergeAbortOperation = BaseOperation & { kind: OperationKind.MergeAbort }; +export type MergeBaseOperation = BaseOperation & { kind: OperationKind.MergeBase }; +export type MoveOperation = BaseOperation & { kind: OperationKind.Move }; +export type PostCommitCommandOperation = BaseOperation & { kind: OperationKind.PostCommitCommand }; +export type PullOperation = BaseOperation & { kind: OperationKind.Pull }; +export type PushOperation = BaseOperation & { kind: OperationKind.Push }; +export type RemoteOperation = BaseOperation & { kind: OperationKind.Remote }; +export type RenameBranchOperation = BaseOperation & { kind: OperationKind.RenameBranch }; +export type RemoveOperation = BaseOperation & { kind: OperationKind.Remove }; +export type ResetOperation = BaseOperation & { kind: OperationKind.Reset }; +export type RebaseOperation = BaseOperation & { kind: OperationKind.Rebase }; +export type RebaseAbortOperation = BaseOperation & { kind: OperationKind.RebaseAbort }; +export type RebaseContinueOperation = BaseOperation & { kind: OperationKind.RebaseContinue }; +export type RevertFilesOperation = BaseOperation & { kind: OperationKind.RevertFiles }; +export type SetBranchUpstreamOperation = BaseOperation & { kind: OperationKind.SetBranchUpstream }; +export type ShowOperation = BaseOperation & { kind: OperationKind.Show }; +export type StageOperation = BaseOperation & { kind: OperationKind.Stage }; +export type StatusOperation = BaseOperation & { kind: OperationKind.Status }; +export type StashOperation = BaseOperation & { kind: OperationKind.Stash }; +export type SubmoduleUpdateOperation = BaseOperation & { kind: OperationKind.SubmoduleUpdate }; +export type SyncOperation = BaseOperation & { kind: OperationKind.Sync }; +export type TagOperation = BaseOperation & { kind: OperationKind.Tag }; + +export const Operation = { + Add: (showProgress: boolean) => ({ kind: OperationKind.Add, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as AddOperation), + Apply: { kind: OperationKind.Apply, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as ApplyOperation, + Blame: { kind: OperationKind.Blame, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as BlameOperation, + Branch: { kind: OperationKind.Branch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as BranchOperation, + CheckIgnore: { kind: OperationKind.CheckIgnore, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as CheckIgnoreOperation, + CherryPick: { kind: OperationKind.CherryPick, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as CherryPickOperation, + Checkout: (refLabel: string) => ({ kind: OperationKind.Checkout, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true, refLabel } as CheckoutOperation), + CheckoutTracking: (refLabel: string) => ({ kind: OperationKind.CheckoutTracking, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true, refLabel } as CheckoutTrackingOperation), + Clean: (showProgress: boolean) => ({ kind: OperationKind.Clean, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as CleanOperation), + Commit: { kind: OperationKind.Commit, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true } as CommitOperation, + Config: { kind: OperationKind.Config, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as ConfigOperation, + DeleteBranch: { kind: OperationKind.DeleteBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, + DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, + DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, + DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, + Diff: { kind: OperationKind.Diff, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as DiffOperation, + Fetch: (showProgress: boolean) => ({ kind: OperationKind.Fetch, blocking: false, readOnly: false, remote: true, retry: true, showProgress } as FetchOperation), + FindTrackingBranches: { kind: OperationKind.FindTrackingBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as FindTrackingBranchesOperation, + GetBranch: { kind: OperationKind.GetBranch, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchOperation, + GetBranches: { kind: OperationKind.GetBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchesOperation, + GetCommitTemplate: { kind: OperationKind.GetCommitTemplate, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetCommitTemplateOperation, + GetObjectDetails: { kind: OperationKind.GetObjectDetails, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectDetailsOperation, + GetRefs: { kind: OperationKind.GetRefs, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetRefsOperation, + GetRemoteRefs: { kind: OperationKind.GetRemoteRefs, blocking: false, readOnly: true, remote: true, retry: false, showProgress: false } as GetRemoteRefsOperation, + HashObject: { kind: OperationKind.HashObject, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as HashObjectOperation, + Ignore: { kind: OperationKind.Ignore, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as IgnoreOperation, + Log: { kind: OperationKind.Log, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as LogOperation, + LogFile: { kind: OperationKind.LogFile, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as LogFileOperation, + Merge: { kind: OperationKind.Merge, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as MergeOperation, + MergeAbort: { kind: OperationKind.MergeAbort, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as MergeAbortOperation, + MergeBase: { kind: OperationKind.MergeBase, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as MergeBaseOperation, + Move: { kind: OperationKind.Move, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as MoveOperation, + PostCommitCommand: { kind: OperationKind.PostCommitCommand, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as PostCommitCommandOperation, + Pull: { kind: OperationKind.Pull, blocking: true, readOnly: false, remote: true, retry: true, showProgress: true } as PullOperation, + Push: { kind: OperationKind.Push, blocking: true, readOnly: false, remote: true, retry: false, showProgress: true } as PushOperation, + Remote: { kind: OperationKind.Remote, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RemoteOperation, + RenameBranch: { kind: OperationKind.RenameBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RenameBranchOperation, + Remove: { kind: OperationKind.Remove, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RemoveOperation, + Reset: { kind: OperationKind.Reset, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as ResetOperation, + Rebase: { kind: OperationKind.Rebase, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseOperation, + RebaseAbort: { kind: OperationKind.RebaseAbort, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseAbortOperation, + RebaseContinue: { kind: OperationKind.RebaseContinue, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as RebaseContinueOperation, + RevertFiles: (showProgress: boolean) => ({ kind: OperationKind.RevertFiles, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as RevertFilesOperation), + SetBranchUpstream: { kind: OperationKind.SetBranchUpstream, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as SetBranchUpstreamOperation, + Show: { kind: OperationKind.Show, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as ShowOperation, + Stage: { kind: OperationKind.Stage, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StageOperation, + Status: { kind: OperationKind.Status, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StatusOperation, + Stash: { kind: OperationKind.Stash, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StashOperation, + SubmoduleUpdate: { kind: OperationKind.SubmoduleUpdate, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as SubmoduleUpdateOperation, + Sync: { kind: OperationKind.Sync, blocking: true, readOnly: false, remote: true, retry: true, showProgress: true } as SyncOperation, + Tag: { kind: OperationKind.Tag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as TagOperation +}; + +export interface OperationResult { + operation: Operation; + error: any; +} + +interface IOperationManager { + getOperations(operationKind: OperationKind): Operation[]; + isIdle(): boolean; + isRunning(operationKind: OperationKind): boolean; + shouldDisableCommands(): boolean; + shouldShowProgress(): boolean; +} + +export class OperationManager implements IOperationManager { + + private operations = new Map>(); + + constructor(private readonly logger: LogOutputChannel) { } + + start(operation: Operation): void { + if (this.operations.has(operation.kind)) { + this.operations.get(operation.kind)!.add(operation); + } else { + this.operations.set(operation.kind, new Set([operation])); + } + + this.logger.trace(`Operation start: ${operation.kind} (blocking: ${operation.blocking}, readOnly: ${operation.readOnly}; retry: ${operation.retry}; showProgress: ${operation.showProgress})`); + } + + end(operation: Operation): void { + const operationSet = this.operations.get(operation.kind); + if (operationSet) { + operationSet.delete(operation); + if (operationSet.size === 0) { + this.operations.delete(operation.kind); + } + } + + this.logger.trace(`Operation end: ${operation.kind} (blocking: ${operation.blocking}, readOnly: ${operation.readOnly}; retry: ${operation.retry}; showProgress: ${operation.showProgress})`); + } + + getOperations(operationKind: OperationKind): Operation[] { + const operationSet = this.operations.get(operationKind); + return operationSet ? Array.from(operationSet) : []; + } + + isIdle(): boolean { + const operationSets = this.operations.values(); + + for (const operationSet of operationSets) { + for (const operation of operationSet) { + if (!operation.readOnly) { + return false; + } + } + } + + return true; + } + + isRunning(operationKind: OperationKind): boolean { + return this.operations.has(operationKind); + } + + shouldDisableCommands(): boolean { + const operationSets = this.operations.values(); + + for (const operationSet of operationSets) { + for (const operation of operationSet) { + if (operation.blocking) { + return true; + } + } + } + + return false; + } + + shouldShowProgress(): boolean { + const operationSets = this.operations.values(); + + for (const operationSet of operationSets) { + for (const operation of operationSet) { + if (operation.showProgress) { + return true; + } + } + } + + return false; + } +} diff --git a/extensions/git/src/postCommitCommands.ts b/extensions/git/src/postCommitCommands.ts index 43028a8533..9ebe11fded 100644 --- a/extensions/git/src/postCommitCommands.ts +++ b/extensions/git/src/postCommitCommands.ts @@ -3,9 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vscode-nls'; -import { Command, Disposable, Event } from 'vscode'; +import { Command, commands, Disposable, Event, EventEmitter, Memento, Uri, workspace, l10n } from 'vscode'; import { PostCommitCommandsProvider } from './api/git'; +import { Repository } from './repository'; +import { ApiRepository } from './api/api1'; +import { dispose } from './util'; +import { OperationKind } from './operation'; export interface IPostCommitCommandsProviderRegistry { readonly onDidChangePostCommitCommandsProviders: Event; @@ -14,19 +17,214 @@ export interface IPostCommitCommandsProviderRegistry { registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; } -const localize = nls.loadMessageBundle(); - export class GitPostCommitCommandsProvider implements PostCommitCommandsProvider { - getCommands(): Command[] { + getCommands(apiRepository: ApiRepository): Command[] { + const config = workspace.getConfiguration('git', Uri.file(apiRepository.repository.root)); + + // Branch protection + const isBranchProtected = apiRepository.repository.isBranchProtected(); + const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!; + const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt'; + const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch'; + + // Icon + const repository = apiRepository.repository; + const isCommitInProgress = repository.operations.isRunning(OperationKind.Commit) || repository.operations.isRunning(OperationKind.PostCommitCommand); + const icon = isCommitInProgress ? '$(sync~spin)' : alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined; + + // Tooltip (default) + let pushCommandTooltip = !alwaysCommitToNewBranch ? + l10n.t('Commit & Push Changes') : + l10n.t('Commit to New Branch & Push Changes'); + + let syncCommandTooltip = !alwaysCommitToNewBranch ? + l10n.t('Commit & Sync Changes') : + l10n.t('Commit to New Branch & Synchronize Changes'); + + // Tooltip (in progress) + if (isCommitInProgress) { + pushCommandTooltip = !alwaysCommitToNewBranch ? + l10n.t('Committing & Pushing Changes...') : + l10n.t('Committing to New Branch & Pushing Changes...'); + + syncCommandTooltip = !alwaysCommitToNewBranch ? + l10n.t('Committing & Synchronizing Changes...') : + l10n.t('Committing to New Branch & Synchronizing Changes...'); + } + return [ { command: 'git.push', - title: localize('scm secondary button commit and push', "Commit & Push") + title: l10n.t('{0} Commit & Push', icon ?? '$(arrow-up)'), + tooltip: pushCommandTooltip }, { command: 'git.sync', - title: localize('scm secondary button commit and sync', "Commit & Sync") + title: l10n.t('{0} Commit & Sync', icon ?? '$(sync)'), + tooltip: syncCommandTooltip }, ]; } } + +export class CommitCommandsCenter { + + private _onDidChange = new EventEmitter(); + get onDidChange(): Event { return this._onDidChange.event; } + + private disposables: Disposable[] = []; + + set postCommitCommand(command: string | null | undefined) { + if (command === undefined) { + // Commit WAS NOT initiated using the action button + // so there is no need to store the post-commit command + return; + } + + this.globalState.update(this.getGlobalStateKey(), command) + .then(() => this._onDidChange.fire()); + } + + constructor( + private readonly globalState: Memento, + private readonly repository: Repository, + private readonly postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry + ) { + const root = Uri.file(repository.root); + + // Migrate post commit command storage + this.migratePostCommitCommandStorage() + .then(() => { + const onRememberPostCommitCommandChange = async () => { + const config = workspace.getConfiguration('git', root); + if (!config.get('rememberPostCommitCommand')) { + await this.globalState.update(this.getGlobalStateKey(), undefined); + } + }; + this.disposables.push(workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('git.rememberPostCommitCommand', root)) { + onRememberPostCommitCommandChange(); + } + })); + onRememberPostCommitCommandChange(); + + this.disposables.push(postCommitCommandsProviderRegistry.onDidChangePostCommitCommandsProviders(() => this._onDidChange.fire())); + }); + } + + getPrimaryCommand(): Command { + const allCommands = this.getSecondaryCommands().map(c => c).flat(); + const commandFromStorage = allCommands.find(c => c.arguments?.length === 2 && c.arguments[1] === this.getPostCommitCommandStringFromStorage()); + const commandFromSetting = allCommands.find(c => c.arguments?.length === 2 && c.arguments[1] === this.getPostCommitCommandStringFromSetting()); + + return commandFromStorage ?? commandFromSetting ?? this.getCommitCommand(); + } + + getSecondaryCommands(): Command[][] { + const commandGroups: Command[][] = []; + + for (const provider of this.postCommitCommandsProviderRegistry.getPostCommitCommandsProviders()) { + const commands = provider.getCommands(new ApiRepository(this.repository)); + commandGroups.push((commands ?? []).map(c => { + return { command: 'git.commit', title: c.title, tooltip: c.tooltip, arguments: [this.repository.sourceControl, c.command] }; + })); + } + + if (commandGroups.length > 0) { + commandGroups[0].splice(0, 0, this.getCommitCommand()); + } + + return commandGroups; + } + + async executePostCommitCommand(command: string | null | undefined): Promise { + try { + if (command === null) { + // No post-commit command + return; + } + + if (command === undefined) { + // Commit WAS NOT initiated using the action button (ex: keybinding, toolbar action, + // command palette) so we have to honour the default post commit command (memento/setting). + const primaryCommand = this.getPrimaryCommand(); + command = primaryCommand.arguments?.length === 2 ? primaryCommand.arguments[1] : null; + } + + if (command !== null) { + await commands.executeCommand(command!.toString(), new ApiRepository(this.repository)); + } + } catch (err) { + throw err; + } + finally { + if (!this.isRememberPostCommitCommandEnabled()) { + await this.globalState.update(this.getGlobalStateKey(), undefined); + this._onDidChange.fire(); + } + } + } + + private getGlobalStateKey(): string { + return `postCommitCommand:${this.repository.root}`; + } + + private getCommitCommand(): Command { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + + // Branch protection + const isBranchProtected = this.repository.isBranchProtected(); + const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!; + const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt'; + const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch'; + + // Icon + const icon = alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined; + + // Tooltip (default) + const branch = this.repository.HEAD?.name; + let tooltip = alwaysCommitToNewBranch ? + l10n.t('Commit Changes to New Branch') : + branch ? + l10n.t('Commit Changes on "{0}"', branch) : + l10n.t('Commit Changes'); + + // Tooltip (in progress) + if (this.repository.operations.isRunning(OperationKind.Commit)) { + tooltip = !alwaysCommitToNewBranch ? + l10n.t('Committing Changes...') : + l10n.t('Committing Changes to New Branch...'); + } + + return { command: 'git.commit', title: l10n.t('{0} Commit', icon ?? '$(check)'), tooltip, arguments: [this.repository.sourceControl, null] }; + } + + private getPostCommitCommandStringFromSetting(): string | undefined { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const postCommitCommandSetting = config.get('postCommitCommand'); + + return postCommitCommandSetting === 'push' || postCommitCommandSetting === 'sync' ? `git.${postCommitCommandSetting}` : undefined; + } + + private getPostCommitCommandStringFromStorage(): string | null | undefined { + return this.globalState.get(this.getGlobalStateKey()); + } + + private async migratePostCommitCommandStorage(): Promise { + const postCommitCommandString = this.globalState.get(this.repository.root); + + if (postCommitCommandString !== undefined) { + await this.globalState.update(this.getGlobalStateKey(), postCommitCommandString); + await this.globalState.update(this.repository.root, undefined); + } + } + + private isRememberPostCommitCommandEnabled(): boolean { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + return config.get('rememberPostCommitCommand') === true; + } + + dispose(): void { + this.disposables = dispose(this.disposables); + } +} diff --git a/extensions/git/src/protocolHandler.ts b/extensions/git/src/protocolHandler.ts index 228375b74e..122f927f4d 100644 --- a/extensions/git/src/protocolHandler.ts +++ b/extensions/git/src/protocolHandler.ts @@ -3,36 +3,45 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { UriHandler, Uri, window, Disposable, commands } from 'vscode'; +import { UriHandler, Uri, window, Disposable, commands, LogOutputChannel, l10n } from 'vscode'; import { dispose } from './util'; import * as querystring from 'querystring'; const schemes = new Set(['file', 'git', 'http', 'https', 'ssh']); +const refRegEx = /^$|[~\^:\\\*\s\[\]]|^-|^\.|\/\.|\.\.|\.lock\/|\.lock$|\/$|\.$/; export class GitProtocolHandler implements UriHandler { private disposables: Disposable[] = []; - constructor() { + constructor(private readonly logger: LogOutputChannel) { this.disposables.push(window.registerUriHandler(this)); } handleUri(uri: Uri): void { + this.logger.info(`GitProtocolHandler.handleUri(${uri.toString()})`); + switch (uri.path) { case '/clone': this.clone(uri); } } - private clone(uri: Uri): void { + private async clone(uri: Uri): Promise { const data = querystring.parse(uri.query); + const ref = data.ref; if (!data.url) { - console.warn('Failed to open URI:', uri); + this.logger.warn('Failed to open URI:' + uri.toString()); return; } if (Array.isArray(data.url) && data.url.length === 0) { - console.warn('Failed to open URI:', uri); + this.logger.warn('Failed to open URI:' + uri.toString()); + return; + } + + if (ref !== undefined && typeof ref !== 'string') { + this.logger.warn('Failed to open URI due to multiple references:' + uri.toString()); return; } @@ -50,13 +59,33 @@ export class GitProtocolHandler implements UriHandler { if (!schemes.has(cloneUri.scheme.toLowerCase())) { throw new Error('Unsupported scheme.'); } + + // Validate the reference + if (typeof ref === 'string' && refRegEx.test(ref)) { + throw new Error('Invalid reference.'); + } } catch (ex) { - console.warn('Invalid URI:', uri); + this.logger.warn('Invalid URI:' + uri.toString()); return; } - commands.executeCommand('git.clone', cloneUri.toString(true)); + if (!(await commands.getCommands(true)).includes('git.clone')) { + this.logger.error('Could not complete git clone operation as git installation was not found.'); + + const errorMessage = l10n.t('Could not clone your repository as Git is not installed.'); + const downloadGit = l10n.t('Download Git'); + + if (await window.showErrorMessage(errorMessage, { modal: true }, downloadGit) === downloadGit) { + commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-download-git')); + } + + return; + } else { + const cloneTarget = cloneUri.toString(true); + this.logger.info(`Executing git.clone for ${cloneTarget}`); + commands.executeCommand('git.clone', cloneTarget, undefined, { ref: ref }); + } } dispose(): void { diff --git a/extensions/git/src/remoteSource.ts b/extensions/git/src/remoteSource.ts index 55ca54f3fc..04be0c2028 100644 --- a/extensions/git/src/remoteSource.ts +++ b/extensions/git/src/remoteSource.ts @@ -11,3 +11,7 @@ export async function pickRemoteSource(options: PickRemoteSourceOptions & { bran export async function pickRemoteSource(options: PickRemoteSourceOptions = {}): Promise { return GitBaseApi.getAPI().pickRemoteSource(options); } + +export async function getRemoteSourceActions(url: string) { + return GitBaseApi.getAPI().getRemoteSourceActions(url); +} diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c03dd255ca..673e322480 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -6,27 +6,26 @@ import * as fs from 'fs'; import * as path from 'path'; import * as picomatch from 'picomatch'; -import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands, Tab, TabInputTextDiff, TabInputNotebookDiff, RelativePattern } from 'vscode'; +import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands, Tab, TabInputTextDiff, TabInputNotebookDiff, RelativePattern, CancellationTokenSource, LogOutputChannel, LogLevel, CancellationError, l10n } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; -import * as nls from 'vscode-nls'; -import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status, CommitOptions, BranchQuery, FetchOptions } from './api/git'; +import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, Remote, Status, CommitOptions, BranchQuery, FetchOptions, RefQuery, RefType } from './api/git'; import { AutoFetcher } from './autofetch'; import { debounce, memoize, throttle } from './decorators'; -import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions } from './git'; +import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions, PullOptions } from './git'; import { StatusBarCommands } from './statusbar'; import { toGitUri } from './uri'; import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; -import { LogLevel, OutputChannelLogger } from './log'; import { IPushErrorHandlerRegistry } from './pushError'; import { ApiRepository } from './api/api1'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { ActionButtonCommand } from './actionButton'; -import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; +import { IPostCommitCommandsProviderRegistry, CommitCommandsCenter } from './postCommitCommands'; +import { Operation, OperationKind, OperationManager, OperationResult } from './operation'; +import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); -const localize = nls.loadMessageBundle(); const iconsRootPath = path.join(path.dirname(__dirname), 'resources', 'icons'); function getIconUri(iconName: string, theme: string): Uri { @@ -49,30 +48,31 @@ export class Resource implements SourceControlResourceState { static getStatusText(type: Status) { switch (type) { - case Status.INDEX_MODIFIED: return localize('index modified', "Index Modified"); - case Status.MODIFIED: return localize('modified', "Modified"); - case Status.INDEX_ADDED: return localize('index added', "Index Added"); - case Status.INDEX_DELETED: return localize('index deleted', "Index Deleted"); - case Status.DELETED: return localize('deleted', "Deleted"); - case Status.INDEX_RENAMED: return localize('index renamed', "Index Renamed"); - case Status.INDEX_COPIED: return localize('index copied', "Index Copied"); - case Status.UNTRACKED: return localize('untracked', "Untracked"); - case Status.IGNORED: return localize('ignored', "Ignored"); - case Status.INTENT_TO_ADD: return localize('intent to add', "Intent to Add"); - case Status.BOTH_DELETED: return localize('both deleted', "Conflict: Both Deleted"); - case Status.ADDED_BY_US: return localize('added by us', "Conflict: Added By Us"); - case Status.DELETED_BY_THEM: return localize('deleted by them', "Conflict: Deleted By Them"); - case Status.ADDED_BY_THEM: return localize('added by them', "Conflict: Added By Them"); - case Status.DELETED_BY_US: return localize('deleted by us', "Conflict: Deleted By Us"); - case Status.BOTH_ADDED: return localize('both added', "Conflict: Both Added"); - case Status.BOTH_MODIFIED: return localize('both modified', "Conflict: Both Modified"); + case Status.INDEX_MODIFIED: return l10n.t('Index Modified'); + case Status.MODIFIED: return l10n.t('Modified'); + case Status.INDEX_ADDED: return l10n.t('Index Added'); + case Status.INDEX_DELETED: return l10n.t('Index Deleted'); + case Status.DELETED: return l10n.t('Deleted'); + case Status.INDEX_RENAMED: return l10n.t('Index Renamed'); + case Status.INDEX_COPIED: return l10n.t('Index Copied'); + case Status.UNTRACKED: return l10n.t('Untracked'); + case Status.IGNORED: return l10n.t('Ignored'); + case Status.INTENT_TO_ADD: return l10n.t('Intent to Add'); + case Status.INTENT_TO_RENAME: return l10n.t('Intent to Rename'); + case Status.BOTH_DELETED: return l10n.t('Conflict: Both Deleted'); + case Status.ADDED_BY_US: return l10n.t('Conflict: Added By Us'); + case Status.DELETED_BY_THEM: return l10n.t('Conflict: Deleted By Them'); + case Status.ADDED_BY_THEM: return l10n.t('Conflict: Added By Them'); + case Status.DELETED_BY_US: return l10n.t('Conflict: Deleted By Us'); + case Status.BOTH_ADDED: return l10n.t('Conflict: Both Added'); + case Status.BOTH_MODIFIED: return l10n.t('Conflict: Both Modified'); default: return ''; } } @memoize get resourceUri(): Uri { - if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED)) { + if (this.renameResourceUri && (this._type === Status.MODIFIED || this._type === Status.DELETED || this._type === Status.INDEX_RENAMED || this._type === Status.INDEX_COPIED || this._type === Status.INTENT_TO_RENAME)) { return this.renameResourceUri; } @@ -87,6 +87,7 @@ export class Resource implements SourceControlResourceState { return this.resources[1]; } + @memoize get command(): Command { return this._commandResolver.resolveDefaultCommand(this); } @@ -136,6 +137,7 @@ export class Resource implements SourceControlResourceState { case Status.UNTRACKED: return Resource.Icons[theme].Untracked; case Status.IGNORED: return Resource.Icons[theme].Ignored; case Status.INTENT_TO_ADD: return Resource.Icons[theme].Added; + case Status.INTENT_TO_RENAME: return Resource.Icons[theme].Renamed; case Status.BOTH_DELETED: return Resource.Icons[theme].Conflict; case Status.ADDED_BY_US: return Resource.Icons[theme].Conflict; case Status.DELETED_BY_THEM: return Resource.Icons[theme].Conflict; @@ -193,6 +195,7 @@ export class Resource implements SourceControlResourceState { case Status.DELETED: return 'D'; case Status.INDEX_RENAMED: + case Status.INTENT_TO_RENAME: return 'R'; case Status.UNTRACKED: return 'U'; @@ -230,6 +233,7 @@ export class Resource implements SourceControlResourceState { return new ThemeColor('gitDecoration.addedResourceForeground'); case Status.INDEX_COPIED: case Status.INDEX_RENAMED: + case Status.INTENT_TO_RENAME: return new ThemeColor('gitDecoration.renamedResourceForeground'); case Status.UNTRACKED: return new ThemeColor('gitDecoration.untrackedResourceForeground'); @@ -299,142 +303,8 @@ export class Resource implements SourceControlResourceState { await commands.executeCommand(command.command, ...(command.arguments || [])); } - clone() { - return new Resource(this._commandResolver, this._resourceGroupType, this._resourceUri, this._type, this._useIcons, this._renameResourceUri); - } -} - -export const enum Operation { - Status = 'Status', - Config = 'Config', - Diff = 'Diff', - MergeBase = 'MergeBase', - Add = 'Add', - Remove = 'Remove', - RevertFiles = 'RevertFiles', - Commit = 'Commit', - Clean = 'Clean', - Branch = 'Branch', - GetBranch = 'GetBranch', - GetBranches = 'GetBranches', - SetBranchUpstream = 'SetBranchUpstream', - HashObject = 'HashObject', - Checkout = 'Checkout', - CheckoutTracking = 'CheckoutTracking', - Reset = 'Reset', - Remote = 'Remote', - Fetch = 'Fetch', - Pull = 'Pull', - Push = 'Push', - CherryPick = 'CherryPick', - Sync = 'Sync', - Show = 'Show', - Stage = 'Stage', - GetCommitTemplate = 'GetCommitTemplate', - DeleteBranch = 'DeleteBranch', - RenameBranch = 'RenameBranch', - DeleteRef = 'DeleteRef', - Merge = 'Merge', - Rebase = 'Rebase', - Ignore = 'Ignore', - Tag = 'Tag', - DeleteTag = 'DeleteTag', - Stash = 'Stash', - CheckIgnore = 'CheckIgnore', - GetObjectDetails = 'GetObjectDetails', - SubmoduleUpdate = 'SubmoduleUpdate', - RebaseAbort = 'RebaseAbort', - RebaseContinue = 'RebaseContinue', - FindTrackingBranches = 'GetTracking', - Apply = 'Apply', - Blame = 'Blame', - Log = 'Log', - LogFile = 'LogFile', - - Move = 'Move' -} - -function isReadOnly(operation: Operation): boolean { - switch (operation) { - case Operation.Blame: - case Operation.CheckIgnore: - case Operation.Diff: - case Operation.FindTrackingBranches: - case Operation.GetBranch: - case Operation.GetCommitTemplate: - case Operation.GetObjectDetails: - case Operation.Log: - case Operation.LogFile: - case Operation.MergeBase: - case Operation.Show: - return true; - default: - return false; - } -} - -function shouldShowProgress(operation: Operation): boolean { - switch (operation) { - case Operation.Fetch: - case Operation.CheckIgnore: - case Operation.GetObjectDetails: - case Operation.Show: - return false; - default: - return true; - } -} - -export interface Operations { - isIdle(): boolean; - shouldShowProgress(): boolean; - isRunning(operation: Operation): boolean; -} - -class OperationsImpl implements Operations { - - private operations = new Map(); - - start(operation: Operation): void { - this.operations.set(operation, (this.operations.get(operation) || 0) + 1); - } - - end(operation: Operation): void { - const count = (this.operations.get(operation) || 0) - 1; - - if (count <= 0) { - this.operations.delete(operation); - } else { - this.operations.set(operation, count); - } - } - - isRunning(operation: Operation): boolean { - return this.operations.has(operation); - } - - isIdle(): boolean { - const operations = this.operations.keys(); - - for (const operation of operations) { - if (!isReadOnly(operation)) { - return false; - } - } - - return true; - } - - shouldShowProgress(): boolean { - const operations = this.operations.keys(); - - for (const operation of operations) { - if (shouldShowProgress(operation)) { - return true; - } - } - - return false; + clone(resourceGroupType?: ResourceGroupType) { + return new Resource(this._commandResolver, resourceGroupType ?? this._resourceGroupType, this._resourceUri, this._type, this._useIcons, this._renameResourceUri); } } @@ -442,9 +312,11 @@ export interface GitResourceGroup extends SourceControlResourceGroup { resourceStates: Resource[]; } -export interface OperationResult { - operation: Operation; - error: any; +interface GitResourceGroups { + indexGroup?: Resource[]; + mergeGroup?: Resource[]; + untrackedGroup?: Resource[]; + workingTreeGroup?: Resource[]; } class ProgressManager { @@ -458,10 +330,8 @@ class ProgressManager { this.updateEnablement(); this.repository.onDidChangeOperations(() => { - const commitInProgress = this.repository.operations.isRunning(Operation.Commit); - - this.repository.sourceControl.inputBox.enabled = !commitInProgress; - commands.executeCommand('setContext', 'commitInProgress', commitInProgress); + // Disable input box when the commit operation is running + this.repository.sourceControl.inputBox.enabled = !this.repository.operations.isRunning(OperationKind.Commit); }); } @@ -517,22 +387,22 @@ class FileEventLogger { constructor( private onWorkspaceWorkingTreeFileChange: Event, private onDotGitFileChange: Event, - private outputChannelLogger: OutputChannelLogger + private logger: LogOutputChannel ) { - this.logLevelDisposable = outputChannelLogger.onDidChangeLogLevel(this.onDidChangeLogLevel, this); - this.onDidChangeLogLevel(outputChannelLogger.currentLogLevel); + this.logLevelDisposable = logger.onDidChangeLogLevel(this.onDidChangeLogLevel, this); + this.onDidChangeLogLevel(logger.logLevel); } - private onDidChangeLogLevel(level: LogLevel): void { + private onDidChangeLogLevel(logLevel: LogLevel): void { this.eventDisposable.dispose(); - if (level > LogLevel.Debug) { + if (logLevel > LogLevel.Debug) { return; } this.eventDisposable = combinedDisposable([ - this.onWorkspaceWorkingTreeFileChange(uri => this.outputChannelLogger.logDebug(`[wt] Change: ${uri.fsPath}`)), - this.onDotGitFileChange(uri => this.outputChannelLogger.logDebug(`[.git] Change: ${uri.fsPath}`)) + this.onWorkspaceWorkingTreeFileChange(uri => this.logger.debug(`[wt] Change: ${uri.fsPath}`)), + this.onDotGitFileChange(uri => this.logger.debug(`[.git] Change: ${uri.fsPath}`)) ]); } @@ -552,7 +422,7 @@ class DotGitWatcher implements IFileWatcher { constructor( private repository: Repository, - private outputChannelLogger: OutputChannelLogger + private logger: LogOutputChannel ) { const rootWatcher = watch(repository.dotGit.path); this.disposables.push(rootWatcher); @@ -583,7 +453,7 @@ class DotGitWatcher implements IFileWatcher { this.transientDisposables.push(upstreamWatcher); upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables); } catch (err) { - this.outputChannelLogger.logWarning(`Failed to watch ref '${upstreamPath}', is most likely packed.`); + this.logger.warn(`Failed to watch ref '${upstreamPath}', is most likely packed.`); } } @@ -607,7 +477,7 @@ class ResourceCommandResolver { resolveFileCommand(resource: Resource): Command { return { command: 'vscode.open', - title: localize('open', "Open"), + title: l10n.t('Open'), arguments: [resource.resourceUri] }; } @@ -619,21 +489,21 @@ class ResourceCommandResolver { const bothModified = resource.type === Status.BOTH_MODIFIED; if (resource.rightUri && workspace.getConfiguration('git').get('mergeEditor', false) && (bothModified || resource.type === Status.BOTH_ADDED)) { return { - command: '_git.openMergeEditor', - title: localize('open.merge', "Open Merge"), + command: 'git.openMergeEditor', + title: l10n.t('Open Merge'), arguments: [resource.rightUri] }; } else { return { command: 'vscode.open', - title: localize('open', "Open"), + title: l10n.t('Open'), arguments: [resource.rightUri, { override: bothModified ? false : undefined }, title] }; } } else { return { command: 'vscode.diff', - title: localize('open', "Open"), + title: l10n.t('Open'), arguments: [resource.leftUri, resource.rightUri, title] }; } @@ -654,6 +524,7 @@ class ResourceCommandResolver { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: case Status.INDEX_ADDED: + case Status.INTENT_TO_RENAME: return toGitUri(resource.original, 'HEAD'); case Status.MODIFIED: @@ -688,7 +559,8 @@ class ResourceCommandResolver { case Status.MODIFIED: case Status.UNTRACKED: case Status.IGNORED: - case Status.INTENT_TO_ADD: { + case Status.INTENT_TO_ADD: + case Status.INTENT_TO_RENAME: { const uriString = resource.resourceUri.toString(); const [indexStatus] = this.repository.indexGroup.resourceStates.filter(r => r.resourceUri.toString() === uriString); @@ -713,25 +585,29 @@ class ResourceCommandResolver { case Status.INDEX_MODIFIED: case Status.INDEX_RENAMED: case Status.INDEX_ADDED: - return localize('git.title.index', '{0} (Index)', basename); + return l10n.t('{0} (Index)', basename); case Status.MODIFIED: case Status.BOTH_ADDED: case Status.BOTH_MODIFIED: - return localize('git.title.workingTree', '{0} (Working Tree)', basename); + return l10n.t('{0} (Working Tree)', basename); case Status.INDEX_DELETED: case Status.DELETED: - return localize('git.title.deleted', '{0} (Deleted)', basename); + return l10n.t('{0} (Deleted)', basename); case Status.DELETED_BY_US: - return localize('git.title.theirs', '{0} (Theirs)', basename); + return l10n.t('{0} (Theirs)', basename); case Status.DELETED_BY_THEM: - return localize('git.title.ours', '{0} (Ours)', basename); + return l10n.t('{0} (Ours)', basename); case Status.UNTRACKED: - return localize('git.title.untracked', '{0} (Untracked)', basename); + return l10n.t('{0} (Untracked)', basename); + + case Status.INTENT_TO_ADD: + case Status.INTENT_TO_RENAME: + return l10n.t('{0} (Intent to add)', basename); default: return ''; @@ -739,6 +615,11 @@ class ResourceCommandResolver { } } +interface BranchProtectionMatcher { + include?: picomatch.Matcher; + exclude?: picomatch.Matcher; +} + export class Repository implements Disposable { private _onDidChangeRepository = new EventEmitter(); @@ -753,12 +634,15 @@ export class Repository implements Disposable { private _onDidChangeOriginalResource = new EventEmitter(); readonly onDidChangeOriginalResource: Event = this._onDidChangeOriginalResource.event; - private _onRunOperation = new EventEmitter(); - readonly onRunOperation: Event = this._onRunOperation.event; + private _onRunOperation = new EventEmitter(); + readonly onRunOperation: Event = this._onRunOperation.event; private _onDidRunOperation = new EventEmitter(); readonly onDidRunOperation: Event = this._onDidRunOperation.event; + private _onDidChangeBranchProtection = new EventEmitter(); + readonly onDidChangeBranchProtection: Event = this._onDidChangeBranchProtection.event; + @memoize get onDidChangeOperations(): Event { return anyEvent(this.onRunOperation as Event, this.onDidRunOperation as Event); @@ -786,11 +670,6 @@ export class Repository implements Disposable { return this._HEAD; } - private _refs: Ref[] = []; - get refs(): Ref[] { - return this._refs; - } - get headShortName(): string | undefined { if (!this.HEAD) { return; @@ -802,13 +681,6 @@ export class Repository implements Disposable { return HEAD.name; } - const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; - const tagName = tag && tag.name; - - if (tagName) { - return tagName; - } - return (HEAD.commit || '').substr(0, 8); } @@ -831,16 +703,35 @@ export class Repository implements Disposable { this.inputBox.value = rebaseCommit.message; } + const shouldUpdateContext = !!this._rebaseCommit !== !!rebaseCommit; this._rebaseCommit = rebaseCommit; - commands.executeCommand('setContext', 'gitRebaseInProgress', !!this._rebaseCommit); + + if (shouldUpdateContext) { + commands.executeCommand('setContext', 'gitRebaseInProgress', !!this._rebaseCommit); + } } get rebaseCommit(): Commit | undefined { return this._rebaseCommit; } - private _operations = new OperationsImpl(); - get operations(): Operations { return this._operations; } + private _mergeInProgress: boolean = false; + + set mergeInProgress(value: boolean) { + if (this._mergeInProgress === value) { + return; + } + + this._mergeInProgress = value; + commands.executeCommand('setContext', 'gitMergeInProgress', value); + } + + get mergeInProgress() { + return this._mergeInProgress; + } + + private _operations = new OperationManager(this.logger); + get operations(): OperationManager { return this._operations; } private _state = RepositoryState.Idle; get state(): RepositoryState { return this._state; } @@ -849,7 +740,6 @@ export class Repository implements Disposable { this._onDidChangeState.fire(state); this._HEAD = undefined; - this._refs = []; this._remotes = []; this.mergeGroup.resourceStates = []; this.indexGroup.resourceStates = []; @@ -869,8 +759,10 @@ export class Repository implements Disposable { private isRepositoryHuge: false | { limit: number } = false; private didWarnAboutLimit = false; - private isBranchProtectedMatcher: picomatch.Matcher | undefined; + private branchProtection = new Map(); + private commitCommandCenter: CommitCommandsCenter; private resourceCommandResolver = new ResourceCommandResolver(this); + private updateModelStateCancellationTokenSource: CancellationTokenSource | undefined; private disposables: Disposable[] = []; constructor( @@ -878,26 +770,27 @@ export class Repository implements Disposable { private pushErrorHandlerRegistry: IPushErrorHandlerRegistry, remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry, postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry, + private readonly branchProtectionProviderRegistry: IBranchProtectionProviderRegistry, globalState: Memento, - outputChannelLogger: OutputChannelLogger, + private readonly logger: LogOutputChannel, private telemetryReporter: TelemetryReporter ) { const repositoryWatcher = workspace.createFileSystemWatcher(new RelativePattern(Uri.file(repository.root), '**')); this.disposables.push(repositoryWatcher); const onRepositoryFileChange = anyEvent(repositoryWatcher.onDidChange, repositoryWatcher.onDidCreate, repositoryWatcher.onDidDelete); - const onRepositoryWorkingTreeFileChange = filterEvent(onRepositoryFileChange, uri => !/\.git($|\/)/.test(relativePath(repository.root, uri.fsPath))); + const onRepositoryWorkingTreeFileChange = filterEvent(onRepositoryFileChange, uri => !/\.git($|\\|\/)/.test(relativePath(repository.root, uri.fsPath))); let onRepositoryDotGitFileChange: Event; try { - const dotGitFileWatcher = new DotGitWatcher(this, outputChannelLogger); + const dotGitFileWatcher = new DotGitWatcher(this, logger); onRepositoryDotGitFileChange = dotGitFileWatcher.event; this.disposables.push(dotGitFileWatcher); } catch (err) { - outputChannelLogger.logError(`Failed to watch path:'${this.dotGit.path}' or commonPath:'${this.dotGit.commonPath}', reverting to legacy API file watched. Some events might be lost.\n${err.stack || err}`); + logger.error(`Failed to watch path:'${this.dotGit.path}' or commonPath:'${this.dotGit.commonPath}', reverting to legacy API file watched. Some events might be lost.\n${err.stack || err}`); - onRepositoryDotGitFileChange = filterEvent(onRepositoryFileChange, uri => /\.git($|\/)/.test(uri.path)); + onRepositoryDotGitFileChange = filterEvent(onRepositoryFileChange, uri => /\.git($|\\|\/)/.test(uri.path)); } // FS changes should trigger `git status`: @@ -909,12 +802,12 @@ export class Repository implements Disposable { // Relevate repository changes should trigger virtual document change events onRepositoryDotGitFileChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables); - this.disposables.push(new FileEventLogger(onRepositoryWorkingTreeFileChange, onRepositoryDotGitFileChange, outputChannelLogger)); + this.disposables.push(new FileEventLogger(onRepositoryWorkingTreeFileChange, onRepositoryDotGitFileChange, logger)); const root = Uri.file(repository.root); this._sourceControl = scm.createSourceControl('git', 'Git', root); - this._sourceControl.acceptInputCommand = { command: 'git.commit', title: localize('commit', "Commit"), arguments: [this._sourceControl] }; + this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] }; this._sourceControl.quickDiffProvider = this; this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); this.disposables.push(this._sourceControl); @@ -922,10 +815,10 @@ export class Repository implements Disposable { this.updateInputBoxPlaceholder(); this.disposables.push(this.onDidRunGitStatus(() => this.updateInputBoxPlaceholder())); - this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes")); - this._indexGroup = this._sourceControl.createResourceGroup('index', localize('staged changes', "Staged Changes")); - this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', localize('changes', "Changes")); - this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', localize('untracked changes', "Untracked Changes")); + this._mergeGroup = this._sourceControl.createResourceGroup('merge', l10n.t('Merge Changes')); + this._indexGroup = this._sourceControl.createResourceGroup('index', l10n.t('Staged Changes')); + this._workingTreeGroup = this._sourceControl.createResourceGroup('workingTree', l10n.t('Changes')); + this._untrackedGroup = this._sourceControl.createResourceGroup('untracked', l10n.t('Untracked Changes')); const updateIndexGroupVisibility = () => { const config = workspace.getConfiguration('git', root); @@ -943,13 +836,13 @@ export class Repository implements Disposable { }, undefined, this.disposables); filterEvent(workspace.onDidChangeConfiguration, e => - e.affectsConfiguration('git.branchProtection', root) - || e.affectsConfiguration('git.branchSortOrder', root) + e.affectsConfiguration('git.branchSortOrder', root) || e.affectsConfiguration('git.untrackedChanges', root) || e.affectsConfiguration('git.ignoreSubmodules', root) || e.affectsConfiguration('git.openDiffOnClick', root) || e.affectsConfiguration('git.showActionButton', root) - )(this.updateModelState, this, this.disposables); + || e.affectsConfiguration('git.similarityThreshold', root) + )(() => this.updateModelState(), this, this.disposables); const updateInputBoxVisibility = () => { const config = workspace.getConfiguration('git', root); @@ -980,25 +873,29 @@ export class Repository implements Disposable { } // https://github.com/microsoft/vscode/issues/39039 - const onSuccessfulPush = filterEvent(this.onDidRunOperation, e => e.operation === Operation.Push && !e.error); + const onSuccessfulPush = filterEvent(this.onDidRunOperation, e => e.operation.kind === OperationKind.Push && !e.error); onSuccessfulPush(() => { const gitConfig = workspace.getConfiguration('git'); if (gitConfig.get('showPushSuccessNotification')) { - window.showInformationMessage(localize('push success', "Successfully pushed.")); + window.showInformationMessage(l10n.t('Successfully pushed.')); } }, null, this.disposables); - const onDidChangeBranchProtection = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchProtection', root)); - onDidChangeBranchProtection(this.updateBranchProtectionMatcher, this, this.disposables); - this.updateBranchProtectionMatcher(); + // Default branch protection provider + const onBranchProtectionProviderChanged = filterEvent(this.branchProtectionProviderRegistry.onDidChangeBranchProtectionProviders, e => pathEquals(e.fsPath, root.fsPath)); + this.disposables.push(onBranchProtectionProviderChanged(root => this.updateBranchProtectionMatchers(root))); + this.disposables.push(this.branchProtectionProviderRegistry.registerBranchProtectionProvider(root, new GitBranchProtectionProvider(root))); const statusBar = new StatusBarCommands(this, remoteSourcePublisherRegistry); this.disposables.push(statusBar); statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables); this._sourceControl.statusBarCommands = statusBar.commands; - const actionButton = new ActionButtonCommand(this, postCommitCommandsProviderRegistry); + this.commitCommandCenter = new CommitCommandsCenter(globalState, this, postCommitCommandsProviderRegistry); + this.disposables.push(this.commitCommandCenter); + + const actionButton = new ActionButtonCommand(this, this.commitCommandCenter); this.disposables.push(actionButton); actionButton.onDidChange(() => this._sourceControl.actionButton = actionButton.button); this._sourceControl.actionButton = actionButton.button; @@ -1015,7 +912,7 @@ export class Repository implements Disposable { let tooManyChangesWarning: SourceControlInputBoxValidation | undefined; if (this.isRepositoryHuge) { tooManyChangesWarning = { - message: localize('tooManyChangesWarning', "Too many changes were detected. Only the first {0} changes will be shown below.", this.isRepositoryHuge.limit), + message: l10n.t('Too many changes were detected. Only the first {0} changes will be shown below.', this.isRepositoryHuge.limit), type: SourceControlInputBoxValidationType.Warning }; } @@ -1023,7 +920,7 @@ export class Repository implements Disposable { if (this.rebaseCommit) { if (this.rebaseCommit.message !== text) { return { - message: localize('commit in rebase', "It's not possible to change the commit message in the middle of a rebase. Please complete the rebase operation and use interactive rebase instead."), + message: l10n.t('It\'s not possible to change the commit message in the middle of a rebase. Please complete the rebase operation and use interactive rebase instead.'), type: SourceControlInputBoxValidationType.Warning }; } @@ -1038,7 +935,7 @@ export class Repository implements Disposable { if (/^\s+$/.test(text)) { return { - message: localize('commitMessageWhitespacesOnlyWarning', "Current commit message only contains whitespace characters"), + message: l10n.t('Current commit message only contains whitespace characters'), type: SourceControlInputBoxValidationType.Warning }; } @@ -1073,25 +970,36 @@ export class Repository implements Disposable { } return { - message: localize('commitMessageCountdown', "{0} characters left in current line", threshold - line.length), + message: l10n.t('{0} characters left in current line', threshold - line.length), type: SourceControlInputBoxValidationType.Information }; } else { return { - message: localize('commitMessageWarning', "{0} characters over {1} in current line", line.length - threshold, threshold), + message: l10n.t('{0} characters over {1} in current line', line.length - threshold, threshold), type: SourceControlInputBoxValidationType.Warning }; } } + /** + * Quick diff label + */ + get label(): string { + return l10n.t('Git local working changes'); + } + provideOriginalResource(uri: Uri): Uri | undefined { if (uri.scheme !== 'file') { return; } - const path = uri.path; + // Ignore path that is inside a merge group + if (this.mergeGroup.resourceStates.some(r => r.resourceUri.path === uri.path)) { + return undefined; + } - if (this.mergeGroup.resourceStates.some(r => r.resourceUri.path === path)) { + // Ignore path that is inside a submodule + if (this.submodules.some(s => isDescendant(path.join(this.repository.root, s.path), uri.path))) { return undefined; } @@ -1190,10 +1098,41 @@ export class Repository implements Disposable { } async add(resources: Uri[], opts?: { update?: boolean }): Promise { - await this.run(Operation.Add, async () => { - await this.repository.add(resources.map(r => r.fsPath), opts); - this.closeDiffEditors([], [...resources.map(r => r.fsPath)]); - }); + await this.run( + Operation.Add(!this.optimisticUpdateEnabled()), + async () => { + await this.repository.add(resources.map(r => r.fsPath), opts); + this.closeDiffEditors([], [...resources.map(r => r.fsPath)]); + }, + () => { + const resourcePaths = resources.map(r => r.fsPath); + const indexGroupResourcePaths = this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath); + + // Collect added resources + const addedResourceStates: Resource[] = []; + for (const resource of [...this.mergeGroup.resourceStates, ...this.untrackedGroup.resourceStates, ...this.workingTreeGroup.resourceStates]) { + if (resourcePaths.includes(resource.resourceUri.fsPath) && !indexGroupResourcePaths.includes(resource.resourceUri.fsPath)) { + addedResourceStates.push(resource.clone(ResourceGroupType.Index)); + } + } + + // Add new resource(s) to index group + const indexGroup = [...this.indexGroup.resourceStates, ...addedResourceStates]; + + // Remove resource(s) from merge group + const mergeGroup = this.mergeGroup.resourceStates + .filter(r => !resourcePaths.includes(r.resourceUri.fsPath)); + + // Remove resource(s) from working group + const workingTreeGroup = this.workingTreeGroup.resourceStates + .filter(r => !resourcePaths.includes(r.resourceUri.fsPath)); + + // Remove resource(s) from untracked group + const untrackedGroup = this.untrackedGroup.resourceStates + .filter(r => !resourcePaths.includes(r.resourceUri.fsPath)); + + return { indexGroup, mergeGroup, workingTreeGroup, untrackedGroup }; + }); } async rm(resources: Uri[]): Promise { @@ -1210,94 +1149,189 @@ export class Repository implements Disposable { } async revert(resources: Uri[]): Promise { - await this.run(Operation.RevertFiles, async () => { - await this.repository.revert('HEAD', resources.map(r => r.fsPath)); - this.closeDiffEditors([...resources.length !== 0 ? - resources.map(r => r.fsPath) : - this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)], []); - }); + await this.run( + Operation.RevertFiles(!this.optimisticUpdateEnabled()), + async () => { + await this.repository.revert('HEAD', resources.map(r => r.fsPath)); + this.closeDiffEditors([...resources.length !== 0 ? + resources.map(r => r.fsPath) : + this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)], []); + }, + () => { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const untrackedChanges = config.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); + const untrackedChangesResourceGroupType = untrackedChanges === 'mixed' ? ResourceGroupType.WorkingTree : ResourceGroupType.Untracked; + + const resourcePaths = resources.length === 0 ? + this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath) : resources.map(r => r.fsPath); + + // Collect removed resources + const trackedResources: Resource[] = []; + const untrackedResources: Resource[] = []; + for (const resource of this.indexGroup.resourceStates) { + if (resourcePaths.includes(resource.resourceUri.fsPath)) { + if (resource.type === Status.INDEX_ADDED) { + untrackedResources.push(resource.clone(untrackedChangesResourceGroupType)); + } else { + trackedResources.push(resource.clone(ResourceGroupType.WorkingTree)); + } + } + } + + // Remove resource(s) from index group + const indexGroup = this.indexGroup.resourceStates + .filter(r => !resourcePaths.includes(r.resourceUri.fsPath)); + + // Add resource(s) to working group + const workingTreeGroup = untrackedChanges === 'mixed' ? + [...this.workingTreeGroup.resourceStates, ...trackedResources, ...untrackedResources] : + [...this.workingTreeGroup.resourceStates, ...trackedResources]; + + // Add resource(s) to untracked group + const untrackedGroup = untrackedChanges === 'separate' ? + [...this.untrackedGroup.resourceStates, ...untrackedResources] : undefined; + + return { indexGroup, workingTreeGroup, untrackedGroup }; + }); } async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise { - const indexResources = [...this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)]; - const workingGroupResources = opts.all && opts.all !== 'tracked' ? - [...this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath)] : []; - if (this.rebaseCommit) { - await this.run(Operation.RebaseContinue, async () => { - if (opts.all) { - const addOpts = opts.all === 'tracked' ? { update: true } : {}; - await this.repository.add([], addOpts); - } + await this.run( + Operation.RebaseContinue, + async () => { + if (opts.all) { + const addOpts = opts.all === 'tracked' ? { update: true } : {}; + await this.repository.add([], addOpts); + } - await this.repository.rebaseContinue(); - this.closeDiffEditors(indexResources, workingGroupResources); - }); + await this.repository.rebaseContinue(); + await this.commitOperationCleanup(message, opts); + }, + () => this.commitOperationGetOptimisticResourceGroups(opts)); } else { - await this.run(Operation.Commit, async () => { - if (opts.all) { - const addOpts = opts.all === 'tracked' ? { update: true } : {}; - await this.repository.add([], addOpts); - } + // Set post-commit command to render the correct action button + this.commitCommandCenter.postCommitCommand = opts.postCommitCommand; - delete opts.all; + await this.run( + Operation.Commit, + async () => { + if (opts.all) { + const addOpts = opts.all === 'tracked' ? { update: true } : {}; + await this.repository.add([], addOpts); + } - if (opts.requireUserConfig === undefined || opts.requireUserConfig === null) { - const config = workspace.getConfiguration('git', Uri.file(this.root)); - opts.requireUserConfig = config.get('requireGitUserConfig'); - } + delete opts.all; - await this.repository.commit(message, opts); - this.closeDiffEditors(indexResources, workingGroupResources); + if (opts.requireUserConfig === undefined || opts.requireUserConfig === null) { + const config = workspace.getConfiguration('git', Uri.file(this.root)); + opts.requireUserConfig = config.get('requireGitUserConfig'); + } + + await this.repository.commit(message, opts); + await this.commitOperationCleanup(message, opts); + }, + () => this.commitOperationGetOptimisticResourceGroups(opts)); + + // Execute post-commit command + await this.run(Operation.PostCommitCommand, async () => { + await this.commitCommandCenter.executePostCommitCommand(opts.postCommitCommand); }); } } - async clean(resources: Uri[]): Promise { - await this.run(Operation.Clean, async () => { - const toClean: string[] = []; - const toCheckout: string[] = []; - const submodulesToUpdate: string[] = []; - const resourceStates = [...this.workingTreeGroup.resourceStates, ...this.untrackedGroup.resourceStates]; + private async commitOperationCleanup(message: string | undefined, opts: CommitOptions) { + if (message) { + this.inputBox.value = await this.getInputTemplate(); + } - resources.forEach(r => { - const fsPath = r.fsPath; + const indexResources = [...this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)]; + const workingGroupResources = opts.all && opts.all !== 'tracked' ? + [...this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath)] : []; - for (const submodule of this.submodules) { - if (path.join(this.root, submodule.path) === fsPath) { - submodulesToUpdate.push(fsPath); - return; - } - } - - const raw = r.toString(); - const scmResource = find(resourceStates, sr => sr.resourceUri.toString() === raw); - - if (!scmResource) { - return; - } - - switch (scmResource.type) { - case Status.UNTRACKED: - case Status.IGNORED: - toClean.push(fsPath); - break; - - default: - toCheckout.push(fsPath); - break; - } - }); - - await this.repository.clean(toClean); - await this.repository.checkout('', toCheckout); - await this.repository.updateSubmodules(submodulesToUpdate); - - this.closeDiffEditors([], [...toClean, ...toCheckout]); - }); + this.closeDiffEditors(indexResources, workingGroupResources); } - closeDiffEditors(indexResources: string[] | undefined, workingTreeResources: string[] | undefined, ignoreSetting: boolean = false): void { + private commitOperationGetOptimisticResourceGroups(opts: CommitOptions): GitResourceGroups { + let untrackedGroup: Resource[] | undefined = undefined, + workingTreeGroup: Resource[] | undefined = undefined; + + if (opts.all === 'tracked') { + workingTreeGroup = this.workingTreeGroup.resourceStates + .filter(r => r.type === Status.UNTRACKED); + } else if (opts.all) { + untrackedGroup = workingTreeGroup = []; + } + + return { indexGroup: [], mergeGroup: [], untrackedGroup, workingTreeGroup }; + } + + async clean(resources: Uri[]): Promise { + await this.run( + Operation.Clean(!this.optimisticUpdateEnabled()), + async () => { + const toClean: string[] = []; + const toCheckout: string[] = []; + const submodulesToUpdate: string[] = []; + const resourceStates = [...this.workingTreeGroup.resourceStates, ...this.untrackedGroup.resourceStates]; + + resources.forEach(r => { + const fsPath = r.fsPath; + + for (const submodule of this.submodules) { + if (path.join(this.root, submodule.path) === fsPath) { + submodulesToUpdate.push(fsPath); + return; + } + } + + const raw = r.toString(); + const scmResource = find(resourceStates, sr => sr.resourceUri.toString() === raw); + + if (!scmResource) { + return; + } + + switch (scmResource.type) { + case Status.UNTRACKED: + case Status.IGNORED: + toClean.push(fsPath); + break; + + default: + toCheckout.push(fsPath); + break; + } + }); + + await this.repository.clean(toClean); + try { + await this.repository.checkout('', toCheckout); + } catch (err) { + if (err.gitErrorCode !== GitErrorCodes.BranchNotYetBorn) { + throw err; + } + } + await this.repository.updateSubmodules(submodulesToUpdate); + + this.closeDiffEditors([], [...toClean, ...toCheckout]); + }, + () => { + const resourcePaths = resources.map(r => r.fsPath); + + // Remove resource(s) from working group + const workingTreeGroup = this.workingTreeGroup.resourceStates + .filter(r => !resourcePaths.includes(r.resourceUri.fsPath)); + + // Remove resource(s) from untracked group + const untrackedGroup = this.untrackedGroup.resourceStates + .filter(r => !resourcePaths.includes(r.resourceUri.fsPath)); + + return { workingTreeGroup, untrackedGroup }; + }); + } + + closeDiffEditors(indexResources: string[] | undefined, workingTreeResources: string[] | undefined, ignoreSetting = false): void { const config = workspace.getConfiguration('git', Uri.file(this.root)); if (!config.get('closeDiffOnOperation', false) && !ignoreSetting) { return; } @@ -1333,6 +1367,27 @@ export class Repository implements Disposable { await this.run(Operation.RenameBranch, () => this.repository.renameBranch(name)); } + @throttle + async fastForwardBranch(name: string): Promise { + // Get branch details + const branch = await this.getBranch(name); + if (!branch.upstream?.remote || !branch.upstream?.name || !branch.name) { + return; + } + + try { + // Fast-forward the branch if possible + const options = { remote: branch.upstream.remote, ref: `${branch.upstream.name}:${branch.name}` }; + await this.run(Operation.Fetch(true), async () => this.repository.fetch(options)); + } catch (err) { + if (err.gitErrorCode === GitErrorCodes.BranchFastForwardRejected) { + return; + } + + throw err; + } + } + async cherryPick(commitHash: string): Promise { await this.run(Operation.CherryPick, () => this.repository.cherryPick(commitHash)); } @@ -1345,8 +1400,26 @@ export class Repository implements Disposable { return await this.run(Operation.GetBranch, () => this.repository.getBranch(name)); } - async getBranches(query: BranchQuery): Promise { - return await this.run(Operation.GetBranches, () => this.repository.getBranches(query)); + async getBranches(query: BranchQuery = {}, cancellationToken?: CancellationToken): Promise { + return await this.run(Operation.GetBranches, async () => { + const refs = await this.getRefs(query, cancellationToken); + return refs.filter(value => (value.type === RefType.Head || value.type === RefType.RemoteHead) && (query.remote || !value.remote)); + }); + } + + async getRefs(query: RefQuery = {}, cancellationToken?: CancellationToken): Promise { + const config = workspace.getConfiguration('git'); + let defaultSort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder'); + if (defaultSort !== 'alphabetically' && defaultSort !== 'committerdate') { + defaultSort = 'alphabetically'; + } + + query = { ...query, sort: query?.sort ?? defaultSort }; + return await this.run(Operation.GetRefs, () => this.repository.getRefs(query, cancellationToken)); + } + + async getRemoteRefs(remote: string, opts?: { heads?: boolean; tags?: boolean }): Promise { + return await this.run(Operation.GetRemoteRefs, () => this.repository.getRemoteRefs(remote, opts)); } async setBranchUpstream(name: string, upstream: string): Promise { @@ -1357,6 +1430,10 @@ export class Repository implements Disposable { await this.run(Operation.Merge, () => this.repository.merge(ref)); } + async mergeAbort(): Promise { + await this.run(Operation.MergeAbort, async () => await this.repository.mergeAbort()); + } + async rebase(branch: string): Promise { await this.run(Operation.Rebase, () => this.repository.rebase(branch)); } @@ -1369,12 +1446,31 @@ export class Repository implements Disposable { await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name)); } - async checkout(treeish: string, opts?: { detached?: boolean }): Promise { - await this.run(Operation.Checkout, () => this.repository.checkout(treeish, [], opts)); + async deleteRemoteTag(remoteName: string, tagName: string): Promise { + await this.run(Operation.DeleteRemoteTag, () => this.repository.deleteRemoteTag(remoteName, tagName)); + } + + async checkout(treeish: string, opts?: { detached?: boolean; pullBeforeCheckout?: boolean }): Promise { + const refLabel = opts?.detached ? treeish.substring(0, 8) : treeish; + + await this.run(Operation.Checkout(refLabel), + async () => { + if (opts?.pullBeforeCheckout && !opts?.detached) { + try { + await this.fastForwardBranch(treeish); + } + catch (err) { + // noop + } + } + + await this.repository.checkout(treeish, [], opts); + }); } async checkoutTracking(treeish: string, opts: { detached?: boolean } = {}): Promise { - await this.run(Operation.CheckoutTracking, () => this.repository.checkout(treeish, [], { ...opts, track: true })); + const refLabel = opts.detached ? treeish.substring(0, 8) : treeish; + await this.run(Operation.CheckoutTracking(refLabel), () => this.repository.checkout(treeish, [], { ...opts, track: true })); } async findTrackingBranches(upstreamRef: string): Promise { @@ -1416,8 +1512,8 @@ export class Repository implements Disposable { } @throttle - async fetchAll(cancellationToken?: CancellationToken): Promise { - await this._fetch({ all: true, cancellationToken }); + async fetchAll(options: { silent?: boolean } = {}, cancellationToken?: CancellationToken): Promise { + await this._fetch({ all: true, silent: options.silent, cancellationToken }); } async fetch(options: FetchOptions): Promise { @@ -1431,7 +1527,7 @@ export class Repository implements Disposable { options.prune = prune; } - await this.run(Operation.Fetch, async () => this.repository.fetch(options)); + await this.run(Operation.Fetch(options.silent !== true), async () => this.repository.fetch(options)); } @throttle @@ -1473,12 +1569,28 @@ export class Repository implements Disposable { } if (await this.checkIfMaybeRebased(this.HEAD?.name)) { - await this.repository.pull(rebase, remote, branch, { unshallow, tags }); + await this._pullAndHandleTagConflict(rebase, remote, branch, { unshallow, tags }); } }); }); } + private async _pullAndHandleTagConflict(rebase?: boolean, remote?: string, branch?: string, options: PullOptions = {}): Promise { + try { + await this.repository.pull(rebase, remote, branch, options); + } + catch (err) { + if (err.gitErrorCode !== GitErrorCodes.TagConflict) { + throw err; + } + + // Handle tag(s) conflict + if (await this.handleTagConflict(remote, err.stderr)) { + await this.repository.pull(rebase, remote, branch, options); + } + } + } + @throttle async push(head: Branch, forcePushMode?: ForcePushMode): Promise { let remote: string | undefined; @@ -1492,7 +1604,7 @@ export class Repository implements Disposable { await this.run(Operation.Push, () => this._push(remote, branch, undefined, undefined, forcePushMode)); } - async pushTo(remote?: string, name?: string, setUpstream: boolean = false, forcePushMode?: ForcePushMode): Promise { + async pushTo(remote?: string, name?: string, setUpstream = false, forcePushMode?: ForcePushMode): Promise { await this.run(Operation.Push, () => this._push(remote, name, setUpstream, undefined, forcePushMode)); } @@ -1535,18 +1647,18 @@ export class Repository implements Disposable { const fn = async (cancellationToken?: CancellationToken) => { // When fetchOnPull is enabled, fetch all branches when pulling if (fetchOnPull) { - await this.fetchAll(cancellationToken); + await this.fetchAll({}, cancellationToken); } if (await this.checkIfMaybeRebased(this.HEAD?.name)) { - await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken }); + await this._pullAndHandleTagConflict(rebase, remoteName, pullBranch, { tags, cancellationToken }); } }; if (supportCancellation) { const opts: ProgressOptions = { location: ProgressLocation.Notification, - title: localize('sync is unpredictable', "Syncing. Cancelling may cause serious damages to the repository"), + title: l10n.t('Syncing. Cancelling may cause serious damages to the repository'), cancellable: true }; @@ -1595,13 +1707,13 @@ export class Repository implements Disposable { return true; } - const always = { title: localize('always pull', "Always Pull") }; - const pull = { title: localize('pull', "Pull") }; - const cancel = { title: localize('dont pull', "Don't Pull") }; + const always = { title: l10n.t('Always Pull') }; + const pull = { title: l10n.t('Pull') }; + const cancel = { title: l10n.t('Don\'t Pull') }; const result = await window.showWarningMessage( currentBranch - ? localize('pull branch maybe rebased', "It looks like the current branch \'{0}\' might have been rebased. Are you sure you still want to pull into it?", currentBranch) - : localize('pull maybe rebased', "It looks like the current branch might have been rebased. Are you sure you still want to pull into it?"), + ? l10n.t('It looks like the current branch "{0}" might have been rebased. Are you sure you still want to pull into it?', currentBranch) + : l10n.t('It looks like the current branch might have been rebased. Are you sure you still want to pull into it?'), always, pull, cancel ); @@ -1661,14 +1773,14 @@ export class Repository implements Disposable { return await this.repository.getStashes(); } - async createStash(message?: string, includeUntracked?: boolean): Promise { + async createStash(message?: string, includeUntracked?: boolean, staged?: boolean): Promise { const indexResources = [...this.indexGroup.resourceStates.map(r => r.resourceUri.fsPath)]; const workingGroupResources = [ - ...this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath), + ...!staged ? this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath) : [], ...includeUntracked ? this.untrackedGroup.resourceStates.map(r => r.resourceUri.fsPath) : []]; return await this.run(Operation.Stash, async () => { - this.repository.createStash(message, includeUntracked); + await this.repository.createStash(message, includeUntracked, staged); this.closeDiffEditors(indexResources, workingGroupResources); }); } @@ -1782,7 +1894,7 @@ export class Repository implements Disposable { return ignored; } - private async _push(remote?: string, refspec?: string, setUpstream: boolean = false, followTags = false, forcePushMode?: ForcePushMode, tags = false): Promise { + private async _push(remote?: string, refspec?: string, setUpstream = false, followTags = false, forcePushMode?: ForcePushMode, tags = false): Promise { try { await this.repository.push(remote, refspec, setUpstream, followTags, forcePushMode, tags); } catch (err) { @@ -1807,7 +1919,11 @@ export class Repository implements Disposable { } } - private async run(operation: Operation, runOperation: () => Promise = () => Promise.resolve(null)): Promise { + private async run( + operation: Operation, + runOperation: () => Promise = () => Promise.resolve(null), + getOptimisticResourceGroups: () => GitResourceGroups | undefined = () => undefined): Promise { + if (this.state !== RepositoryState.Idle) { throw new Error('Repository not initialized'); } @@ -1815,13 +1931,13 @@ export class Repository implements Disposable { let error: any = null; this._operations.start(operation); - this._onRunOperation.fire(operation); + this._onRunOperation.fire(operation.kind); try { const result = await this.retryRun(operation, runOperation); - if (!isReadOnly(operation)) { - await this.updateModelState(); + if (!operation.readOnly) { + await this.updateModelState(this.optimisticUpdateEnabled() ? getOptimisticResourceGroups() : undefined); } return result; @@ -1832,10 +1948,14 @@ export class Repository implements Disposable { this.state = RepositoryState.Disposed; } + if (!operation.readOnly) { + await this.updateModelState(); + } + throw err; } finally { this._operations.end(operation); - this._onDidRunOperation.fire({ operation, error }); + this._onDidRunOperation.fire({ operation: operation, error }); } } @@ -1849,7 +1969,7 @@ export class Repository implements Disposable { } catch (err) { const shouldRetry = attempt <= 10 && ( (err.gitErrorCode === GitErrorCodes.RepositoryIsLocked) - || ((operation === Operation.Pull || operation === Operation.Sync || operation === Operation.Fetch) && (err.gitErrorCode === GitErrorCodes.CantLockRef || err.gitErrorCode === GitErrorCodes.CantRebaseMultipleBranches)) + || (operation.retry && (err.gitErrorCode === GitErrorCodes.CantLockRef || err.gitErrorCode === GitErrorCodes.CantRebaseMultipleBranches)) ); if (shouldRetry) { @@ -1880,15 +2000,80 @@ export class Repository implements Disposable { return folderPaths.filter(p => !ignored.has(p)); } - @throttle - private async updateModelState(): Promise { + private async updateModelState(optimisticResourcesGroups?: GitResourceGroups) { + this.updateModelStateCancellationTokenSource?.cancel(); + + this.updateModelStateCancellationTokenSource = new CancellationTokenSource(); + await this._updateModelState(optimisticResourcesGroups, this.updateModelStateCancellationTokenSource.token); + } + + private async _updateModelState(optimisticResourcesGroups?: GitResourceGroups, cancellationToken?: CancellationToken): Promise { + try { + // Optimistically update resource groups + if (optimisticResourcesGroups) { + this._updateResourceGroupsState(optimisticResourcesGroups); + } + + const [HEAD, remotes, submodules, rebaseCommit, mergeInProgress, commitTemplate] = + await Promise.all([ + this.repository.getHEADRef(), + this.repository.getRemotes(), + this.repository.getSubmodules(), + this.getRebaseCommit(), + this.isMergeInProgress(), + this.getInputTemplate()]); + + this._HEAD = HEAD; + this._remotes = remotes!; + this._submodules = submodules!; + this.rebaseCommit = rebaseCommit; + this.mergeInProgress = mergeInProgress; + + this._sourceControl.commitTemplate = commitTemplate; + + // Execute cancellable long-running operation + const resourceGroups = await this.getStatus(cancellationToken); + this._updateResourceGroupsState(resourceGroups); + + this._onDidChangeStatus.fire(); + } + catch (err) { + if (err instanceof CancellationError) { + return; + } + + throw err; + } + } + + private _updateResourceGroupsState(resourcesGroups: GitResourceGroups): void { + // set resource groups + if (resourcesGroups.indexGroup) { this.indexGroup.resourceStates = resourcesGroups.indexGroup; } + if (resourcesGroups.mergeGroup) { this.mergeGroup.resourceStates = resourcesGroups.mergeGroup; } + if (resourcesGroups.untrackedGroup) { this.untrackedGroup.resourceStates = resourcesGroups.untrackedGroup; } + if (resourcesGroups.workingTreeGroup) { this.workingTreeGroup.resourceStates = resourcesGroups.workingTreeGroup; } + + // set count badge + this.setCountBadge(); + } + + private async getStatus(cancellationToken?: CancellationToken): Promise { + if (cancellationToken && cancellationToken.isCancellationRequested) { + throw new CancellationError(); + } + const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root)); const untrackedChanges = scopedConfig.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges'); const ignoreSubmodules = scopedConfig.get('ignoreSubmodules'); const limit = scopedConfig.get('statusLimit', 10000); + const similarityThreshold = scopedConfig.get('similarityThreshold', 50); - const { status, statusLength, didHitLimit } = await this.repository.getStatus({ limit, ignoreSubmodules, untrackedChanges }); + const start = new Date().getTime(); + const { status, statusLength, didHitLimit } = await this.repository.getStatus({ limit, ignoreSubmodules, similarityThreshold, untrackedChanges, cancellationToken }); + const totalTime = new Date().getTime() - start; + + this.isRepositoryHuge = didHitLimit ? { limit } : false; if (didHitLimit) { /* __GDPR__ @@ -1896,31 +2081,48 @@ export class Repository implements Disposable { "owner": "lszomoru", "ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Setting indicating whether submodules are ignored" }, "limit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Setting indicating the limit of status entries" }, - "statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of status entries" } + "statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of status entries" }, + "totalTime": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of ms the operation took" } } */ - this.telemetryReporter.sendTelemetryEvent('statusLimit', { ignoreSubmodules: String(ignoreSubmodules) }, { limit, statusLength }); + this.telemetryReporter.sendTelemetryEvent('statusLimit', { ignoreSubmodules: String(ignoreSubmodules) }, { limit, statusLength, totalTime }); } + if (totalTime > 5000) { + /* __GDPR__ + "statusSlow" : { + "owner": "digitarald", + "comment": "Reports when git status is slower than 5s", + "expiration": "1.73", + "ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Setting indicating whether submodules are ignored" }, + "didHitLimit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Total number of status entries" }, + "didWarnAboutLimit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "True when the user was warned about slow git status" }, + "statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of status entries" }, + "totalTime": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of ms the operation took" } + } + */ + this.telemetryReporter.sendTelemetryEvent('statusSlow', { ignoreSubmodules: String(ignoreSubmodules), didHitLimit: String(didHitLimit), didWarnAboutLimit: String(this.didWarnAboutLimit) }, { statusLength, totalTime }); + } + + // Triggers or clears any validation warning + this._sourceControl.inputBox.validateInput = this._sourceControl.inputBox.validateInput; + const config = workspace.getConfiguration('git'); const shouldIgnore = config.get('ignoreLimitWarning') === true; const useIcons = !config.get('decorations.enabled', true); - this.isRepositoryHuge = didHitLimit ? { limit } : false; - // Triggers or clears any validation warning - this._sourceControl.inputBox.validateInput = this._sourceControl.inputBox.validateInput; if (didHitLimit && !shouldIgnore && !this.didWarnAboutLimit) { const knownHugeFolderPaths = await this.findKnownHugeFolderPathsToIgnore(); - const gitWarn = localize('huge', "The git repository at '{0}' has too many active changes, only a subset of Git features will be enabled.", this.repository.root); - const neverAgain = { title: localize('neveragain', "Don't Show Again") }; + const gitWarn = l10n.t('The git repository at "{0}" has too many active changes, only a subset of Git features will be enabled.', this.repository.root); + const neverAgain = { title: l10n.t('Don\'t Show Again') }; if (knownHugeFolderPaths.length > 0) { const folderPath = knownHugeFolderPaths[0]; const folderName = path.basename(folderPath); - const addKnown = localize('add known', "Would you like to add '{0}' to .gitignore?", folderName); - const yes = { title: localize('yes', "Yes") }; - const no = { title: localize('no', "No") }; + const addKnown = l10n.t('Would you like to add "{0}" to .gitignore?', folderName); + const yes = { title: l10n.t('Yes') }; + const no = { title: l10n.t('No') }; const result = await window.showWarningMessage(`${gitWarn} ${addKnown}`, yes, no, neverAgain); if (result === yes) { @@ -1933,7 +2135,7 @@ export class Repository implements Disposable { this.didWarnAboutLimit = true; } } else { - const ok = { title: localize('ok', "OK") }; + const ok = { title: l10n.t('OK') }; const result = await window.showWarningMessage(gitWarn, ok, neverAgain); if (result === neverAgain) { config.update('ignoreLimitWarning', true, false); @@ -1943,38 +2145,10 @@ export class Repository implements Disposable { } } - let HEAD: Branch | undefined; - - try { - HEAD = await this.repository.getHEAD(); - - if (HEAD.name) { - try { - HEAD = await this.repository.getBranch(HEAD.name); - } catch (err) { - // noop - } - } - } catch (err) { - // noop - } - - let sort = config.get<'alphabetically' | 'committerdate'>('branchSortOrder') || 'alphabetically'; - if (sort !== 'alphabetically' && sort !== 'committerdate') { - sort = 'alphabetically'; - } - const [refs, remotes, submodules, rebaseCommit] = await Promise.all([this.repository.getRefs({ sort }), this.repository.getRemotes(), this.repository.getSubmodules(), this.getRebaseCommit()]); - - this._HEAD = HEAD; - this._refs = refs!; - this._remotes = remotes!; - this._submodules = submodules!; - this.rebaseCommit = rebaseCommit; - - const index: Resource[] = []; - const workingTree: Resource[] = []; - const merge: Resource[] = []; - const untracked: Resource[] = []; + const indexGroup: Resource[] = [], + mergeGroup: Resource[] = [], + untrackedGroup: Resource[] = [], + workingTreeGroup: Resource[] = []; status.forEach(raw => { const uri = Uri.file(path.join(this.repository.root, raw.path)); @@ -1984,56 +2158,43 @@ export class Repository implements Disposable { switch (raw.x + raw.y) { case '??': switch (untrackedChanges) { - 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)); + case 'mixed': return workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); + case 'separate': return untrackedGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons)); default: return undefined; } case '!!': switch (untrackedChanges) { - 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)); + case 'mixed': return workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); + case 'separate': return untrackedGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons)); default: return undefined; } - 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)); + case 'DD': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_DELETED, useIcons)); + case 'AU': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_US, useIcons)); + case 'UD': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_THEM, useIcons)); + case 'UA': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.ADDED_BY_THEM, useIcons)); + case 'DU': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.DELETED_BY_US, useIcons)); + case 'AA': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_ADDED, useIcons)); + case 'UU': return mergeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Merge, uri, Status.BOTH_MODIFIED, useIcons)); } switch (raw.x) { - 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; + case 'M': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break; + case 'A': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break; + case 'D': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break; + case 'R': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break; + case 'C': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break; } switch (raw.y) { - 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; + case 'M': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break; + case 'D': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; + case 'A': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; + case 'R': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_RENAME, useIcons, renameUri)); break; } return undefined; }); - // set resource groups - this.mergeGroup.resourceStates = merge; - this.indexGroup.resourceStates = index; - this.workingTreeGroup.resourceStates = workingTree; - this.untrackedGroup.resourceStates = untracked; - - // set count badge - this.setCountBadge(); - - // set mergeChanges context - commands.executeCommand('setContext', 'git.mergeChanges', merge.map(item => item.resourceUri.toString())); - - this._onDidChangeStatus.fire(); - - this._sourceControl.commitTemplate = await this.getInputTemplate(); + return { indexGroup, mergeGroup, untrackedGroup, workingTreeGroup }; } private setCountBadge(): void { @@ -2083,6 +2244,11 @@ export class Repository implements Disposable { } } + private isMergeInProgress(): Promise { + const mergeHeadPath = path.join(this.repository.root, '.git', 'MERGE_HEAD'); + return new Promise(resolve => fs.exists(mergeHeadPath, resolve)); + } + private async maybeAutoStash(runOperation: () => Promise): Promise { const config = workspace.getConfiguration('git', Uri.file(this.root)); const shouldAutoStash = config.get('autoStash') @@ -2154,9 +2320,7 @@ export class Repository implements Disposable { return ''; } - const tag = this.refs.filter(iref => iref.type === RefType.Tag && iref.commit === HEAD.commit)[0]; - const tagName = tag && tag.name; - const head = HEAD.name || tagName || (HEAD.commit || '').substr(0, 8); + const head = HEAD.name || (HEAD.commit || '').substr(0, 8); return head + (this.workingTreeGroup.resourceStates.length + this.untrackedGroup.resourceStates.length > 0 ? '*' : '') @@ -2191,18 +2355,18 @@ export class Repository implements Disposable { || !this.HEAD.upstream || !(this.HEAD.ahead || this.HEAD.behind) ) { - return localize('sync changes', "Synchronize Changes"); + return l10n.t('Synchronize Changes'); } const remoteName = this.HEAD && this.HEAD.remote || this.HEAD.upstream.remote; const remote = this.remotes.find(r => r.name === remoteName); if ((remote && remote.isReadOnly) || !this.HEAD.ahead) { - return localize('pull n', "Pull {0} commits from {1}/{2}", this.HEAD.behind, this.HEAD.upstream.remote, this.HEAD.upstream.name); + return l10n.t('Pull {0} commits from {1}/{2}', this.HEAD.behind!, this.HEAD.upstream.remote, this.HEAD.upstream.name); } else if (!this.HEAD.behind) { - return localize('push n', "Push {0} commits to {1}/{2}", this.HEAD.ahead, this.HEAD.upstream.remote, this.HEAD.upstream.name); + return l10n.t('Push {0} commits to {1}/{2}', this.HEAD.ahead, this.HEAD.upstream.remote, this.HEAD.upstream.name); } else { - return localize('pull push n', "Pull {0} and push {1} commits between {2}/{3}", this.HEAD.behind, this.HEAD.ahead, this.HEAD.upstream.remote, this.HEAD.upstream.name); + return l10n.t('Pull {0} and push {1} commits between {2}/{3}', this.HEAD.behind, this.HEAD.ahead, this.HEAD.upstream.remote, this.HEAD.upstream.name); } } @@ -2211,25 +2375,99 @@ export class Repository implements Disposable { if (branchName) { // '{0}' will be replaced by the corresponding key-command later in the process, which is why it needs to stay. - this._sourceControl.inputBox.placeholder = localize('commitMessageWithHeadLabel', "Message ({0} to commit on '{1}')", '{0}', branchName); + this._sourceControl.inputBox.placeholder = l10n.t('Message ({0} to commit on "{1}")', '{0}', branchName); } else { - this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message ({0} to commit)"); + this._sourceControl.inputBox.placeholder = l10n.t('Message ({0} to commit)'); } } - private updateBranchProtectionMatcher(): void { - const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root)); - const branchProtectionGlobs = scopedConfig.get('branchProtection')!.map(bp => bp.trim()).filter(bp => bp !== ''); + private updateBranchProtectionMatchers(root: Uri): void { + this.branchProtection.clear(); - if (branchProtectionGlobs.length === 0) { - this.isBranchProtectedMatcher = undefined; - } else { - this.isBranchProtectedMatcher = picomatch(branchProtectionGlobs); + for (const provider of this.branchProtectionProviderRegistry.getBranchProtectionProviders(root)) { + for (const { remote, rules } of provider.provideBranchProtection()) { + const matchers: BranchProtectionMatcher[] = []; + + for (const rule of rules) { + const include = rule.include && rule.include.length !== 0 ? picomatch(rule.include) : undefined; + const exclude = rule.exclude && rule.exclude.length !== 0 ? picomatch(rule.exclude) : undefined; + + if (include || exclude) { + matchers.push({ include, exclude }); + } + } + + if (matchers.length !== 0) { + this.branchProtection.set(remote, matchers); + } + } } + + this._onDidChangeBranchProtection.fire(); } - public isBranchProtected(name: string = this.HEAD?.name ?? ''): boolean { - return this.isBranchProtectedMatcher ? this.isBranchProtectedMatcher(name) : false; + private optimisticUpdateEnabled(): boolean { + const config = workspace.getConfiguration('git', Uri.file(this.root)); + return config.get('optimisticUpdate') === true; + } + + private async handleTagConflict(remote: string | undefined, raw: string): Promise { + // Ensure there is a remote + remote = remote ?? this.HEAD?.upstream?.remote; + if (!remote) { + throw new Error('Unable to resolve tag conflict due to missing remote.'); + } + + // Extract tag names from message + const tags: string[] = []; + for (const match of raw.matchAll(/^ ! \[rejected\]\s+([^\s]+)\s+->\s+([^\s]+)\s+\(would clobber existing tag\)$/gm)) { + if (match.length === 3) { + tags.push(match[1]); + } + } + if (tags.length === 0) { + throw new Error(`Unable to extract tag names from error message: ${raw}`); + } + + // Notification + const replaceLocalTags = l10n.t('Replace Local Tag(s)'); + const message = l10n.t('Unable to pull from remote repository due to conflicting tag(s): {0}. Would you like to resolve the conflict by replacing the local tag(s)?', tags.join(', ')); + const choice = await window.showErrorMessage(message, { modal: true }, replaceLocalTags); + + if (choice !== replaceLocalTags) { + return false; + } + + // Force fetch tags + await this.repository.fetchTags({ remote, tags, force: true }); + return true; + } + + public isBranchProtected(branch = this.HEAD): boolean { + if (branch?.name) { + // Default branch protection (settings) + const defaultBranchProtectionMatcher = this.branchProtection.get(''); + if (defaultBranchProtectionMatcher?.length === 1 && + defaultBranchProtectionMatcher[0].include && + defaultBranchProtectionMatcher[0].include(branch.name)) { + return true; + } + + if (branch.upstream?.remote) { + // Branch protection (contributed) + const remoteBranchProtectionMatcher = this.branchProtection.get(branch.upstream.remote); + if (remoteBranchProtectionMatcher && remoteBranchProtectionMatcher?.length !== 0) { + return remoteBranchProtectionMatcher.some(matcher => { + const include = matcher.include ? matcher.include(branch.name!) : true; + const exclude = matcher.exclude ? matcher.exclude(branch.name!) : false; + + return include && !exclude; + }); + } + } + } + + return false; } dispose(): void { diff --git a/extensions/git/src/ssh-askpass-empty.sh b/extensions/git/src/ssh-askpass-empty.sh new file mode 100755 index 0000000000..8fb014e5cc --- /dev/null +++ b/extensions/git/src/ssh-askpass-empty.sh @@ -0,0 +1,2 @@ +#!/bin/sh +echo '' \ No newline at end of file diff --git a/extensions/git/src/ssh-askpass.sh b/extensions/git/src/ssh-askpass.sh new file mode 100755 index 0000000000..dca45bc840 --- /dev/null +++ b/extensions/git/src/ssh-askpass.sh @@ -0,0 +1,5 @@ +#!/bin/sh +VSCODE_GIT_ASKPASS_PIPE=`mktemp` +ELECTRON_RUN_AS_NODE="1" VSCODE_GIT_ASKPASS_PIPE="$VSCODE_GIT_ASKPASS_PIPE" VSCODE_GIT_ASKPASS_TYPE="ssh" "$VSCODE_GIT_ASKPASS_NODE" "$VSCODE_GIT_ASKPASS_MAIN" $VSCODE_GIT_ASKPASS_EXTRA_ARGS $* +cat $VSCODE_GIT_ASKPASS_PIPE +rm $VSCODE_GIT_ASKPASS_PIPE diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index a178f4c2c1..26e72e9b74 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -3,14 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, Command, EventEmitter, Event, workspace, Uri } from 'vscode'; -import { Repository, Operation } from './repository'; +import { Disposable, Command, EventEmitter, Event, workspace, Uri, l10n } from 'vscode'; +import { Repository } from './repository'; import { anyEvent, dispose, filterEvent } from './util'; -import * as nls from 'vscode-nls'; -import { Branch, RemoteSourcePublisher } from './api/git'; +import { Branch, RefType, RemoteSourcePublisher } from './api/git'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; +import { CheckoutOperation, CheckoutTrackingOperation, OperationKind } from './operation'; -const localize = nls.loadMessageBundle(); +interface CheckoutStatusBarState { + readonly isCheckoutRunning: boolean; + readonly isCommitRunning: boolean; + readonly isSyncRunning: boolean; +} class CheckoutStatusBar { @@ -18,23 +22,95 @@ class CheckoutStatusBar { get onDidChange(): Event { return this._onDidChange.event; } private disposables: Disposable[] = []; + private _state: CheckoutStatusBarState; + private get state() { return this._state; } + private set state(state: CheckoutStatusBarState) { + this._state = state; + this._onDidChange.fire(); + } + constructor(private repository: Repository) { + this._state = { + isCheckoutRunning: false, + isCommitRunning: false, + isSyncRunning: false + }; + + repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables); repository.onDidRunGitStatus(this._onDidChange.fire, this._onDidChange, this.disposables); + repository.onDidChangeBranchProtection(this._onDidChange.fire, this._onDidChange, this.disposables); } get command(): Command | undefined { + const operationData = [ + ...this.repository.operations.getOperations(OperationKind.Checkout) as CheckoutOperation[], + ...this.repository.operations.getOperations(OperationKind.CheckoutTracking) as CheckoutTrackingOperation[] + ]; + const rebasing = !!this.repository.rebaseCommit; - const isBranchProtected = this.repository.isBranchProtected(); - const title = `${isBranchProtected ? '$(lock)' : '$(git-branch)'} ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`; + const label = operationData[0]?.refLabel ?? `${this.repository.headLabel}${rebasing ? ` (${l10n.t('Rebasing')})` : ''}`; + const command = (this.state.isCheckoutRunning || this.state.isCommitRunning || this.state.isSyncRunning) ? '' : 'git.checkout'; return { - command: 'git.checkout', - tooltip: localize('checkout', "Checkout branch/tag..."), - title, + command, + tooltip: `${label}, ${this.getTooltip()}`, + title: `${this.getIcon()} ${label}`, arguments: [this.repository.sourceControl] }; } + private getIcon(): string { + if (!this.repository.HEAD) { + return ''; + } + + // Checkout + if (this.state.isCheckoutRunning) { + return '$(loading~spin)'; + } + + // Branch + if (this.repository.HEAD.type === RefType.Head && this.repository.HEAD.name) { + return this.repository.isBranchProtected() ? '$(lock)' : '$(git-branch)'; + } + + // Tag + if (this.repository.HEAD.type === RefType.Tag) { + return '$(tag)'; + } + + // Commit + return '$(git-commit)'; + } + + private getTooltip(): string { + if (this.state.isCheckoutRunning) { + return l10n.t('Checking Out Branch/Tag...'); + } + + if (this.state.isCommitRunning) { + return l10n.t('Committing Changes...'); + + } + + if (this.state.isSyncRunning) { + return l10n.t('Synchronizing Changes...'); + } + + return l10n.t('Checkout Branch/Tag...'); + } + + private onDidChangeOperations(): void { + const isCommitRunning = this.repository.operations.isRunning(OperationKind.Commit); + const isCheckoutRunning = this.repository.operations.isRunning(OperationKind.Checkout) || + this.repository.operations.isRunning(OperationKind.CheckoutTracking); + const isSyncRunning = this.repository.operations.isRunning(OperationKind.Sync) || + this.repository.operations.isRunning(OperationKind.Push) || + this.repository.operations.isRunning(OperationKind.Pull); + + this.state = { ...this.state, isCheckoutRunning, isCommitRunning, isSyncRunning }; + } + dispose(): void { this.disposables.forEach(d => d.dispose()); } @@ -42,6 +118,8 @@ class CheckoutStatusBar { interface SyncStatusBarState { readonly enabled: boolean; + readonly isCheckoutRunning: boolean; + readonly isCommitRunning: boolean; readonly isSyncRunning: boolean; readonly hasRemotes: boolean; readonly HEAD: Branch | undefined; @@ -64,6 +142,8 @@ class SyncStatusBar { constructor(private repository: Repository, private remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry) { this._state = { enabled: true, + isCheckoutRunning: false, + isCommitRunning: false, isSyncRunning: false, hasRemotes: false, HEAD: undefined, @@ -89,11 +169,14 @@ class SyncStatusBar { } private onDidChangeOperations(): void { - const isSyncRunning = this.repository.operations.isRunning(Operation.Sync) || - this.repository.operations.isRunning(Operation.Push) || - this.repository.operations.isRunning(Operation.Pull); + const isCommitRunning = this.repository.operations.isRunning(OperationKind.Commit); + const isCheckoutRunning = this.repository.operations.isRunning(OperationKind.Checkout) || + this.repository.operations.isRunning(OperationKind.CheckoutTracking); + const isSyncRunning = this.repository.operations.isRunning(OperationKind.Sync) || + this.repository.operations.isRunning(OperationKind.Push) || + this.repository.operations.isRunning(OperationKind.Pull); - this.state = { ...this.state, isSyncRunning }; + this.state = { ...this.state, isCheckoutRunning, isCommitRunning, isSyncRunning }; } private onDidRunGitStatus(): void { @@ -121,12 +204,16 @@ class SyncStatusBar { return; } - const tooltip = this.state.remoteSourcePublishers.length === 1 - ? localize('publish to', "Publish to {0}", this.state.remoteSourcePublishers[0].name) - : localize('publish to...', "Publish to..."); + const command = (this.state.isCheckoutRunning || this.state.isCommitRunning) ? '' : 'git.publish'; + const tooltip = + this.state.isCheckoutRunning ? l10n.t('Checking Out Changes...') : + this.state.isCommitRunning ? l10n.t('Committing Changes...') : + this.state.remoteSourcePublishers.length === 1 + ? l10n.t('Publish to {0}', this.state.remoteSourcePublishers[0].name) + : l10n.t('Publish to...'); return { - command: 'git.publish', + command, title: `$(cloud-upload)`, tooltip, arguments: [this.repository.sourceControl] @@ -150,17 +237,27 @@ class SyncStatusBar { } else { icon = '$(cloud-upload)'; command = 'git.publish'; - tooltip = localize('publish branch', "Publish Branch"); + tooltip = l10n.t('Publish Branch'); } } else { command = ''; tooltip = ''; } + if (this.state.isCheckoutRunning) { + command = ''; + tooltip = l10n.t('Checking Out Changes...'); + } + + if (this.state.isCommitRunning) { + command = ''; + tooltip = l10n.t('Committing Changes...'); + } + if (this.state.isSyncRunning) { icon = '$(sync~spin)'; command = ''; - tooltip = localize('syncing changes', "Synchronizing Changes..."); + tooltip = l10n.t('Synchronizing Changes...'); } return { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index 5d394abe54..3fa6583200 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'mocha'; -import { GitStatusParser, parseGitCommits, parseGitmodules, parseLsTree, parseLsFiles } from '../git'; +import { GitStatusParser, parseGitCommits, parseGitmodules, parseLsTree, parseLsFiles, parseGitRemotes } from '../git'; import * as assert from 'assert'; import { splitInChunks } from '../util'; @@ -197,6 +197,77 @@ suite('git', () => { }); }); + suite('parseGitRemotes', () => { + test('empty', () => { + assert.deepStrictEqual(parseGitRemotes(''), []); + }); + + test('single remote', () => { + const sample = `[remote "origin"] + url = https://github.com/microsoft/vscode.git + fetch = +refs/heads/*:refs/remotes/origin/* +`; + + assert.deepStrictEqual(parseGitRemotes(sample), [ + { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'https://github.com/microsoft/vscode.git', isReadOnly: false } + ]); + }); + + test('single remote (multiple urls)', () => { + const sample = `[remote "origin"] + url = https://github.com/microsoft/vscode.git + url = https://github.com/microsoft/vscode2.git + fetch = +refs/heads/*:refs/remotes/origin/* +`; + + assert.deepStrictEqual(parseGitRemotes(sample), [ + { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'https://github.com/microsoft/vscode.git', isReadOnly: false } + ]); + }); + + test('multiple remotes', () => { + const sample = `[remote "origin"] + url = https://github.com/microsoft/vscode.git + pushurl = https://github.com/microsoft/vscode1.git + fetch = +refs/heads/*:refs/remotes/origin/* +[remote "remote2"] + url = https://github.com/microsoft/vscode2.git + fetch = +refs/heads/*:refs/remotes/origin/* +`; + + assert.deepStrictEqual(parseGitRemotes(sample), [ + { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'https://github.com/microsoft/vscode1.git', isReadOnly: false }, + { name: 'remote2', fetchUrl: 'https://github.com/microsoft/vscode2.git', pushUrl: 'https://github.com/microsoft/vscode2.git', isReadOnly: false } + ]); + }); + + test('remotes (white space)', () => { + const sample = ` [remote "origin"] + url = https://github.com/microsoft/vscode.git + pushurl=https://github.com/microsoft/vscode1.git + fetch = +refs/heads/*:refs/remotes/origin/* +[ remote"remote2"] + url = https://github.com/microsoft/vscode2.git + fetch = +refs/heads/*:refs/remotes/origin/* +`; + + assert.deepStrictEqual(parseGitRemotes(sample), [ + { name: 'origin', fetchUrl: 'https://github.com/microsoft/vscode.git', pushUrl: 'https://github.com/microsoft/vscode1.git', isReadOnly: false }, + { name: 'remote2', fetchUrl: 'https://github.com/microsoft/vscode2.git', pushUrl: 'https://github.com/microsoft/vscode2.git', isReadOnly: false } + ]); + }); + + test('remotes (invalid section)', () => { + const sample = `[remote "origin" + url = https://github.com/microsoft/vscode.git + pushurl = https://github.com/microsoft/vscode1.git + fetch = +refs/heads/*:refs/remotes/origin/* +`; + + assert.deepStrictEqual(parseGitRemotes(sample), []); + }); + }); + suite('parseGitCommit', () => { test('single parent commit', function () { const GIT_OUTPUT_SINGLE_PARENT = `52c293a05038d865604c2284aa8698bd087915a1 diff --git a/extensions/git/src/test/index.ts b/extensions/git/src/test/index.ts index 52697fe861..a1f4da5d28 100644 --- a/extensions/git/src/test/index.ts +++ b/extensions/git/src/test/index.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const path = require('path'); -const testRunner = require('../../../../test/integration/electron/testrunner'); +import * as path from 'path'; +import * as testRunner from '../../../../test/integration/electron/testrunner'; -const options: any = { +const options: import('mocha').MochaOptions = { ui: 'tdd', color: true, timeout: 60000 diff --git a/extensions/git/src/test/smoke.test.ts b/extensions/git/src/test/smoke.test.ts index 57340a28e9..fea9d5bd02 100644 --- a/extensions/git/src/test/smoke.test.ts +++ b/extensions/git/src/test/smoke.test.ts @@ -44,7 +44,7 @@ suite('git smoke test', function () { fs.writeFileSync(file('index.pug'), 'hello', 'utf8'); cp.execSync('git init -b main', { cwd }); cp.execSync('git config user.name testuser', { cwd }); - cp.execSync('git config user.email monacotools@microsoft.com', { cwd }); + cp.execSync('git config user.email monacotools@example.com', { cwd }); cp.execSync('git config commit.gpgsign false', { cwd }); cp.execSync('git add .', { cwd }); cp.execSync('git commit -m "initial commit"', { cwd }); diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 66f774b0c3..886e12a739 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -3,15 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vscode-nls'; -import { CancellationToken, ConfigurationChangeEvent, Disposable, env, Event, EventEmitter, MarkdownString, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace } from 'vscode'; +import { CancellationToken, ConfigurationChangeEvent, Disposable, env, Event, EventEmitter, MarkdownString, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n } from 'vscode'; import { Model } from './model'; import { Repository, Resource } from './repository'; import { debounce } from './decorators'; import { emojify, ensureEmojis } from './emoji'; import { CommandCenter } from './commands'; - -const localize = nls.loadMessageBundle(); +import { OperationKind, OperationResult } from './operation'; export class GitTimelineItem extends TimelineItem { static is(item: TimelineItem): item is GitTimelineItem { @@ -54,7 +52,7 @@ export class GitTimelineItem extends TimelineItem { this.tooltip = new MarkdownString('', true); if (email) { - const emailTitle = localize('git.timeline.email', "Email"); + const emailTitle = l10n.t('Email'); this.tooltip.appendMarkdown(`$(account) [**${author}**](mailto:${email} "${emailTitle} ${author}")\n\n`); } else { this.tooltip.appendMarkdown(`$(account) **${author}**\n\n`); @@ -79,14 +77,14 @@ export class GitTimelineProvider implements TimelineProvider { } readonly id = 'git-history'; - readonly label = localize('git.timeline.source', 'Git History'); + readonly label = l10n.t('Git History'); private readonly disposable: Disposable; private providerDisposable: Disposable | undefined; private repo: Repository | undefined; private repoDisposable: Disposable | undefined; - private repoStatusDate: Date | undefined; + private repoOperationDate: Date | undefined; constructor(private readonly model: Model, private commands: CommandCenter) { this.disposable = Disposable.from( @@ -105,12 +103,12 @@ export class GitTimelineProvider implements TimelineProvider { } async provideTimeline(uri: Uri, options: TimelineOptions, _token: CancellationToken): Promise { - // console.log(`GitTimelineProvider.provideTimeline: uri=${uri} state=${this._model.state}`); + // console.log(`GitTimelineProvider.provideTimeline: uri=${uri}`); const repo = this.model.getRepository(uri); if (!repo) { this.repoDisposable?.dispose(); - this.repoStatusDate = undefined; + this.repoOperationDate = undefined; this.repo = undefined; return { items: [] }; @@ -120,10 +118,11 @@ export class GitTimelineProvider implements TimelineProvider { this.repoDisposable?.dispose(); this.repo = repo; - this.repoStatusDate = new Date(); + this.repoOperationDate = new Date(); this.repoDisposable = Disposable.from( repo.onDidChangeRepository(uri => this.onRepositoryChanged(repo, uri)), - repo.onDidRunGitStatus(() => this.onRepositoryStatusChanged(repo)) + repo.onDidRunGitStatus(() => this.onRepositoryStatusChanged(repo)), + repo.onDidRunOperation(result => this.onRepositoryOperationRun(repo, result)) ); } @@ -171,7 +170,7 @@ export class GitTimelineProvider implements TimelineProvider { const showAuthor = config.get('showAuthor'); const showUncommitted = config.get('showUncommitted'); - const openComparison = localize('git.timeline.openComparison', "Open Comparison"); + const openComparison = l10n.t('Open Comparison'); const items = commits.map((c, i) => { const date = dateType === 'authored' ? c.authorDate : c.commitDate; @@ -199,13 +198,13 @@ export class GitTimelineProvider implements TimelineProvider { }); if (options.cursor === undefined) { - const you = localize('git.timeline.you', 'You'); + const you = l10n.t('You'); const index = repo.indexGroup.resourceStates.find(r => r.resourceUri.fsPath === uri.fsPath); if (index) { - const date = this.repoStatusDate ?? new Date(); + const date = this.repoOperationDate ?? new Date(); - const item = new GitTimelineItem('~', 'HEAD', localize('git.timeline.stagedChanges', 'Staged Changes'), date.getTime(), 'index', 'git:file:index'); + const item = new GitTimelineItem('~', 'HEAD', l10n.t('Staged Changes'), date.getTime(), 'index', 'git:file:index'); // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? item.iconPath = new ThemeIcon('git-commit'); item.description = ''; @@ -228,7 +227,7 @@ export class GitTimelineProvider implements TimelineProvider { if (working) { const date = new Date(); - const item = new GitTimelineItem('', index ? '~' : 'HEAD', localize('git.timeline.uncommitedChanges', 'Uncommitted Changes'), date.getTime(), 'working', 'git:file:working'); + const item = new GitTimelineItem('', index ? '~' : 'HEAD', l10n.t('Uncommitted Changes'), date.getTime(), 'working', 'git:file:working'); item.iconPath = new ThemeIcon('circle-outline'); item.description = ''; item.setItemDetails(you, undefined, dateFormatter.format(date), Resource.getStatusText(working.type)); @@ -255,7 +254,7 @@ export class GitTimelineProvider implements TimelineProvider { private ensureProviderRegistration() { if (this.providerDisposable === undefined) { - this.providerDisposable = workspace.registerTimelineProvider(['file', 'git', 'vscode-remote', 'gitlens-git', 'vscode-local-history'], this); + this.providerDisposable = workspace.registerTimelineProvider(['file', 'git', 'vscode-remote', 'vscode-local-history'], this); } } @@ -283,10 +282,25 @@ export class GitTimelineProvider implements TimelineProvider { private onRepositoryStatusChanged(_repo: Repository) { // console.log(`GitTimelineProvider.onRepositoryStatusChanged`); - // This is less than ideal, but for now just save the last time a status was run and use that as the timestamp for staged items - this.repoStatusDate = new Date(); + const config = workspace.getConfiguration('git.timeline'); + const showUncommitted = config.get('showUncommitted') === true; - this.fireChanged(); + if (showUncommitted) { + this.fireChanged(); + } + } + + private onRepositoryOperationRun(_repo: Repository, _result: OperationResult) { + // console.log(`GitTimelineProvider.onRepositoryOperationRun`); + + // Successful operations that are not read-only and not status operations + if (!_result.error && !_result.operation.readOnly && _result.operation.kind !== OperationKind.Status) { + // This is less than ideal, but for now just save the last time an + // operation was run and use that as the timestamp for staged items + this.repoOperationDate = new Date(); + + this.fireChanged(); + } } @debounce(500) diff --git a/extensions/git/src/typings/vscode.proposed.canonicalUriProvider.d.ts b/extensions/git/src/typings/vscode.proposed.canonicalUriProvider.d.ts new file mode 100644 index 0000000000..d52dfa0ee8 --- /dev/null +++ b/extensions/git/src/typings/vscode.proposed.canonicalUriProvider.d.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/180582 + + export namespace workspace { + /** + * + * @param scheme The URI scheme that this provider can provide canonical URIs for. + * A canonical URI represents the conversion of a resource's alias into a source of truth URI. + * Multiple aliases may convert to the same source of truth URI. + * @param provider A provider which can convert URIs of scheme @param scheme to + * a canonical URI which is stable across machines. + */ + export function registerCanonicalUriProvider(scheme: string, provider: CanonicalUriProvider): Disposable; + + /** + * + * @param uri The URI to provide a canonical URI for. + * @param token A cancellation token for the request. + */ + export function getCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, token: CancellationToken): ProviderResult; + } + + export interface CanonicalUriProvider { + /** + * + * @param uri The URI to provide a canonical URI for. + * @param options Options that the provider should honor in the URI it returns. + * @param token A cancellation token for the request. + * @returns The canonical URI for the requested URI or undefined if no canonical URI can be provided. + */ + provideCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, token: CancellationToken): ProviderResult; + } + + export interface CanonicalUriRequestOptions { + /** + * + * The desired scheme of the canonical URI. + */ + targetScheme: string; + } +} diff --git a/extensions/git/src/typings/vscode.proposed.editSessionIdentityProvider.d.ts b/extensions/git/src/typings/vscode.proposed.editSessionIdentityProvider.d.ts new file mode 100644 index 0000000000..e7fa91f867 --- /dev/null +++ b/extensions/git/src/typings/vscode.proposed.editSessionIdentityProvider.d.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/157734 + + export namespace workspace { + /** + * An event that is emitted when an edit session identity is about to be requested. + */ + export const onWillCreateEditSessionIdentity: Event; + + /** + * + * @param scheme The URI scheme that this provider can provide edit session identities for. + * @param provider A provider which can convert URIs for workspace folders of scheme @param scheme to + * an edit session identifier which is stable across machines. This enables edit sessions to be resolved. + */ + export function registerEditSessionIdentityProvider(scheme: string, provider: EditSessionIdentityProvider): Disposable; + } + + export interface EditSessionIdentityProvider { + /** + * + * @param workspaceFolder The workspace folder to provide an edit session identity for. + * @param token A cancellation token for the request. + * @returns A string representing the edit session identity for the requested workspace folder. + */ + provideEditSessionIdentity(workspaceFolder: WorkspaceFolder, token: CancellationToken): ProviderResult; + + /** + * + * @param identity1 An edit session identity. + * @param identity2 A second edit session identity to compare to @param identity1. + * @param token A cancellation token for the request. + * @returns An {@link EditSessionIdentityMatch} representing the edit session identity match confidence for the provided identities. + */ + provideEditSessionIdentityMatch(identity1: string, identity2: string, token: CancellationToken): ProviderResult; + } + + export enum EditSessionIdentityMatch { + Complete = 100, + Partial = 50, + None = 0 + } + + export interface EditSessionIdentityWillCreateEvent { + + /** + * A cancellation token. + */ + readonly token: CancellationToken; + + /** + * The workspace folder to create an edit session identity for. + */ + readonly workspaceFolder: WorkspaceFolder; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } +} diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index c7082cd41a..c405ad61cc 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -314,6 +314,13 @@ export function pathEquals(a: string, b: string): boolean { * casing. */ export function relativePath(from: string, to: string): string { + // On Windows, there are cases in which `from` is a path that contains a trailing `\` character + // (ex: C:\, \\server\folder\) due to the implementation of `path.normalize()`. This behavior is + // by design as documented in https://github.com/nodejs/node/issues/1765. + if (isWindows) { + from = from.replace(/\\$/, ''); + } + if (isDescendant(from, to) && from.length < to.length) { return to.substring(from.length + 1); } diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index f58d3d4b07..0faf600c24 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -2,52 +2,178 @@ # yarn lockfile v1 -"@joaomoreno/unique-names-generator@5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@joaomoreno/unique-names-generator/-/unique-names-generator-5.0.0.tgz#a67fe66e3d825c929fc97abfdf17fd80a72beab0" - integrity sha512-3kP6z7aoGEoM3tvhTBZioYa1QFkovOU8uxAlVclnZlXivwF/WTE5EcOzvoDdM+jtjJyWvCMDR1Q4RBjDqupD3A== - -"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" - integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + tslib "^2.2.0" -"@microsoft/1ds-post-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" - integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== +"@azure/core-auth@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e" + integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ== dependencies: - "@microsoft/1ds-core-js" "3.2.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== +"@azure/core-rest-pipeline@^1.10.0": + version "1.10.3" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.3.tgz#7603afd71ff3c290351dbeeab2c814832e47b8ef" + integrity sha512-AMQb0ttiGJ0MIV/r+4TVra6U4+90mPeOveehFnrqKlo7dknPJYdJ61wOzYJXJjDxF8LcCtSogfRelkq+fCGFTw== dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.3.0" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== +"@azure/core-tracing@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" + integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw== + dependencies: + tslib "^2.2.0" -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== +"@azure/core-util@^1.3.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.3.2.tgz#3f8cfda1e87fac0ce84f8c1a42fcd6d2a986632d" + integrity sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.4.tgz#28bc6d0e5b3c38ef29296b32d35da4e483593fa1" + integrity sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg== + dependencies: + tslib "^2.2.0" + +"@joaomoreno/unique-names-generator@^5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@joaomoreno/unique-names-generator/-/unique-names-generator-5.1.0.tgz#d577d425aed794c44c0e8863cddd5dea349f74f3" + integrity sha512-KEVThTpUIKPb7dBKJ9mJ3WYnD1mJZZsEinCSp9CVEPlWbDagurFv1RKRjvvujrLfJzsGc0HkBHS9W8Bughao4A== + +"@microsoft/1ds-core-js@3.2.11", "@microsoft/1ds-core-js@^3.2.8": + version "3.2.11" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.11.tgz#0a3857562a9bde7c15a2fa141c647f40aabd52e2" + integrity sha512-UuqZ1iWjaEFsBsnB+J0Q4IbgJdvJAq3LcNArAdMQQq9+wBWpjyGG4yu9gL6fS5AKfpF6yy73mtgWD7WzvphMLQ== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.13" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/1ds-post-js@^3.2.8": + version "3.2.11" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.11.tgz#bbfc935bafb43982ccaf94ebd12f1fabf5ef8ebe" + integrity sha512-86prrN8cKjfpqfeyC4k0rFqg7fJDkTwPeKLHnkUgfq9mKVzu+BdtlEzaJtc56MroKWNYPkitZ/PWSwwpTPIgMA== + dependencies: + "@microsoft/1ds-core-js" "3.2.11" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-channel-js@2.8.13": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.13.tgz#bd117cc7b00ec929e74a6555cb7462ab4fccdf62" + integrity sha512-zc2BSsHk4HAqK5STdNzKGV817jKNbiTZPYpNt3zuE+jO5druJgloqrvclUfLnCoa7zwrQ2UxoAXlpJGmroGZPA== + dependencies: + "@microsoft/applicationinsights-common" "2.8.13" + "@microsoft/applicationinsights-core-js" "2.8.13" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.9" + +"@microsoft/applicationinsights-common@2.8.13": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.13.tgz#e3457bdc54a61cbfa481494da25e55bb9156096e" + integrity sha512-UYLLGVtuzrWUEmGYRroMzLyTi2fHqL6SwJUlmVWPJrmdK43PGpviRix/sBW0Qs+6qjiI1Z6CiG4Xah6w/HylhA== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.13" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.9" + +"@microsoft/applicationinsights-core-js@2.8.13": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.13.tgz#45c2b8fff35e5aa519355dd3a69e3758293b08f4" + integrity sha512-PP7Xjplvy0d5G2Tk7DcSDYRmgDYRv+7n3wEiqgm63DrSoa8rEuoODavjWunhX058zPNIeKbus59NE+DusLLyZg== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.9" + +"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085" + integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg== + +"@microsoft/applicationinsights-web-basic@^2.8.9": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.13.tgz#a3934fa7b7f221d09fbf403fcfeb06a8c18ca246" + integrity sha512-DgPx1ryZucLWc285qkAEBG6LCcTSG6Gdb4u4yAlmAW0G+Qau49GoJnfJGR+cDXvyXSwHcH0dsainqzeYYY1K7A== + dependencies: + "@microsoft/applicationinsights-channel-js" "2.8.13" + "@microsoft/applicationinsights-common" "2.8.13" + "@microsoft/applicationinsights-core-js" "2.8.13" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.9" + +"@microsoft/applicationinsights-web-snippet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz#6bb788b2902e48bf5d460c38c6bb7fedd686ddd7" + integrity sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ== + +"@microsoft/dynamicproto-js@^1.1.7", "@microsoft/dynamicproto-js@^1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz#7437db7aa061162ee94e4131b69a62b8dad5dea6" + integrity sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ== + +"@opentelemetry/api@^1.0.4": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f" + integrity sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA== + +"@opentelemetry/core@1.13.0", "@opentelemetry/core@^1.0.1": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.13.0.tgz#7cdcb4176d260d279b0aa31456c4ce2ba7f410aa" + integrity sha512-2dBX3Sj99H96uwJKvc2w9NOiNgbvAO6mOFJFramNkKfS9O4Um+VWgpnlAazoYjT6kUJ1MP70KQ5ngD4ed+4NUw== + dependencies: + "@opentelemetry/semantic-conventions" "1.13.0" + +"@opentelemetry/resources@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.13.0.tgz#436b33ea950004e66fce6575f2776a05faca7f8e" + integrity sha512-euqjOkiN6xhjE//0vQYGvbStxoD/WWQRhDiO0OTLlnLBO9Yw2Gd/VoSx2H+svsebjzYk5OxLuREBmcdw6rbUNg== + dependencies: + "@opentelemetry/core" "1.13.0" + "@opentelemetry/semantic-conventions" "1.13.0" + +"@opentelemetry/sdk-trace-base@^1.0.1": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.13.0.tgz#096cc2759430d880c5d886e009df2605097403dc" + integrity sha512-moTiQtc0uPR1hQLt6gLDJH9IIkeBhgRb71OKjNHZPE1VF45fHtD6nBDi5J/DkTHTwYP5X3kBJLa3xN7ub6J4eg== + dependencies: + "@opentelemetry/core" "1.13.0" + "@opentelemetry/resources" "1.13.0" + "@opentelemetry/semantic-conventions" "1.13.0" + +"@opentelemetry/semantic-conventions@1.13.0", "@opentelemetry/semantic-conventions@^1.0.1": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.13.0.tgz#0290398b3eaebc6029c348988a78c3b688fe9219" + integrity sha512-LMGqfSZkaMQXqewO0o1wvWr/2fQdCh4a3Sqlxka/UsJCe0cfLulh6x2aqnKLnsrSGiCq5rSCwvINd152i0nCqw== "@tokenizer/token@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@types/byline@4.2.31": version "4.2.31" resolved "https://registry.yarnpkg.com/@types/byline/-/byline-4.2.31.tgz#0e61fcb9c03e047d21c4496554c7116297ab60cd" @@ -80,24 +206,125 @@ resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY= -"@vscode/extension-telemetry@0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" - integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== +"@vscode/extension-telemetry@0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.7.5.tgz#bf965731816e08c3f146f96d901ec67954fc913b" + integrity sha512-fJ5y3TcpqqkFYHneabYaoB4XAhDdVflVm+TDKshw9VOs77jkgNS4UA7LNXrWeO0eDne3Sh3JgURf+xzc1rk69w== dependencies: - "@microsoft/1ds-core-js" "^3.2.3" - "@microsoft/1ds-post-js" "^3.2.3" + "@microsoft/1ds-core-js" "^3.2.8" + "@microsoft/1ds-post-js" "^3.2.8" + "@microsoft/applicationinsights-web-basic" "^2.8.9" + applicationinsights "2.4.1" "@vscode/iconv-lite-umd@0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +applicationinsights@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-2.4.1.tgz#4de4c4dd3c7c4a44445cfbf3d15808fc0dcc423d" + integrity sha512-0n0Ikd0gzSm460xm+M0UTWIwXrhrH/0bqfZatcJjYObWyefxfAxapGEyNnSGd1Tg90neHz+Yhf+Ff/zgvPiQYA== + dependencies: + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.10.0" + "@microsoft/applicationinsights-web-snippet" "^1.0.1" + "@opentelemetry/api" "^1.0.4" + "@opentelemetry/core" "^1.0.1" + "@opentelemetry/sdk-trace-base" "^1.0.1" + "@opentelemetry/semantic-conventions" "^1.0.1" + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "1.1.0" + diagnostic-channel-publishers "1.0.5" + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + byline@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +diagnostic-channel-publishers@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz#df8c317086c50f5727fdfb5d2fce214d2e4130ae" + integrity sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg== + +diagnostic-channel@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz#6985e9dfedfbc072d91dc4388477e4087147756e" + integrity sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ== + dependencies: + semver "^5.3.0" + +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + file-type@16.5.4: version "16.5.4" resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" @@ -107,6 +334,32 @@ file-type@16.5.4: strtok3 "^6.2.4" token-types "^4.1.1" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -127,6 +380,23 @@ jschardet@3.0.0: resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882" integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +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== + peek-readable@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" @@ -158,6 +428,21 @@ safe-buffer@~5.2.0: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +semver@^5.3.0, semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -181,16 +466,16 @@ token-types@^4.1.1: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" +tslib@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -vscode-nls@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002" - integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw== - vscode-uri@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.0.0.tgz#2df704222f72b8a71ff266ba0830ed6c51ac1542" diff --git a/extensions/github-authentication/extension-browser.webpack.config.js b/extensions/github-authentication/extension-browser.webpack.config.js index 7f45b4b11d..0b0e132393 100644 --- a/extensions/github-authentication/extension-browser.webpack.config.js +++ b/extensions/github-authentication/extension-browser.webpack.config.js @@ -16,14 +16,12 @@ module.exports = withBrowserDefaults({ entry: { extension: './src/extension.ts', }, - externals: { - 'keytar': 'commonjs keytar', - }, resolve: { alias: { - 'node-fetch': path.resolve(__dirname, 'node_modules/node-fetch/browser.js'), 'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js'), - './authServer': path.resolve(__dirname, 'src/env/browser/authServer'), + './node/authServer': path.resolve(__dirname, 'src/browser/authServer'), + './node/crypto': path.resolve(__dirname, 'src/browser/crypto'), + './node/fetch': path.resolve(__dirname, 'src/browser/fetch') } } }); diff --git a/extensions/github-authentication/extension.webpack.config.js b/extensions/github-authentication/extension.webpack.config.js index aba62f39e2..c73dd937e2 100644 --- a/extensions/github-authentication/extension.webpack.config.js +++ b/extensions/github-authentication/extension.webpack.config.js @@ -13,8 +13,5 @@ module.exports = withDefaults({ context: __dirname, entry: { extension: './src/extension.ts', - }, - externals: { - 'keytar': 'commonjs keytar' } }); diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index a130c19992..97b728c3a1 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -17,14 +17,14 @@ "ui", "workspace" ], - "activationEvents": [ - "onAuthenticationRequest:github", - "onAuthenticationRequest:github-enterprise" - ], + "activationEvents": [], "capabilities": { "virtualWorkspaces": true, "untrustedWorkspaces": { - "supported": true + "supported": "limited", + "restrictedConfigurations": [ + "github-enterprise.uri" + ] } }, "contributes": { @@ -34,16 +34,16 @@ "id": "github" }, { - "label": "GitHub Enterprise", + "label": "GitHub Enterprise Server", "id": "github-enterprise" } ], "configuration": { - "title": "GitHub Enterprise Authentication Provider", + "title": "GitHub Enterprise Server Authentication Provider", "properties": { "github-enterprise.uri": { "type": "string", - "description": "URI of your GitHub Enterprise Instance" + "description": "GitHub Enterprise Server URI" } } } @@ -60,15 +60,12 @@ }, "dependencies": { "node-fetch": "2.6.7", - "uuid": "8.1.0", - "@vscode/extension-telemetry": "0.6.2", - "vscode-nls": "^5.0.0", + "@vscode/extension-telemetry": "0.7.5", "vscode-tas-client": "^0.1.47" }, "devDependencies": { "@types/node": "16.x", - "@types/node-fetch": "^2.5.7", - "@types/uuid": "8.0.0" + "@types/node-fetch": "^2.5.7" }, "repository": { "type": "git", diff --git a/extensions/github-authentication/src/env/browser/authServer.ts b/extensions/github-authentication/src/browser/authServer.ts similarity index 100% rename from extensions/github-authentication/src/env/browser/authServer.ts rename to extensions/github-authentication/src/browser/authServer.ts diff --git a/src/vs/editor/contrib/inlineCompletions/browser/consts.ts b/extensions/github-authentication/src/browser/crypto.ts similarity index 82% rename from src/vs/editor/contrib/inlineCompletions/browser/consts.ts rename to extensions/github-authentication/src/browser/crypto.ts index 67f7e4fe9a..988eb6727d 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/consts.ts +++ b/extensions/github-authentication/src/browser/crypto.ts @@ -3,4 +3,4 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export const inlineSuggestCommitId = 'editor.action.inlineSuggest.commit'; +export const crypto = globalThis.crypto; diff --git a/extensions/github-authentication/src/common/env.ts b/extensions/github-authentication/src/common/env.ts index 87ec6e2aee..fa7c99c0d1 100644 --- a/extensions/github-authentication/src/common/env.ts +++ b/extensions/github-authentication/src/common/env.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Uri } from 'vscode'; +import { AuthProviderType } from '../github'; const VALID_DESKTOP_CALLBACK_SCHEMES = [ 'vscode', @@ -15,7 +16,7 @@ const VALID_DESKTOP_CALLBACK_SCHEMES = [ 'vscode-exploration' ]; -export function isSupportedEnvironment(uri: Uri): boolean { +export function isSupportedClient(uri: Uri): boolean { return ( VALID_DESKTOP_CALLBACK_SCHEMES.includes(uri.scheme) || // vscode.dev & insiders.vscode.dev @@ -24,3 +25,10 @@ export function isSupportedEnvironment(uri: Uri): boolean { /(?:^|\.)github\.dev$/.test(uri.authority) ); } + +export function isSupportedTarget(type: AuthProviderType, gheUri?: Uri): boolean { + return ( + type === AuthProviderType.github || + /\.ghe\.com$/.test(gheUri!.authority) + ); +} diff --git a/extensions/github-authentication/src/experimentationService.ts b/extensions/github-authentication/src/common/experimentationService.ts similarity index 98% rename from extensions/github-authentication/src/experimentationService.ts rename to extensions/github-authentication/src/common/experimentationService.ts index b8760a9d05..9a2e22275f 100644 --- a/extensions/github-authentication/src/experimentationService.ts +++ b/extensions/github-authentication/src/common/experimentationService.ts @@ -18,14 +18,19 @@ export class ExperimentationTelemetry implements IExperimentationTelemetry { switch (vscode.env.uriScheme) { case 'vscode': targetPopulation = TargetPopulation.Public; + break; case 'vscode-insiders': targetPopulation = TargetPopulation.Insiders; + break; case 'vscode-exploration': targetPopulation = TargetPopulation.Internal; + break; case 'code-oss': targetPopulation = TargetPopulation.Team; + break; default: targetPopulation = TargetPopulation.Public; + break; } const id = this.context.extension.id; diff --git a/extensions/github-authentication/src/common/logger.ts b/extensions/github-authentication/src/common/logger.ts index 0eb99fa87b..6d8e94980a 100644 --- a/extensions/github-authentication/src/common/logger.ts +++ b/extensions/github-authentication/src/common/logger.ts @@ -6,53 +6,24 @@ import * as vscode from 'vscode'; import { AuthProviderType } from '../github'; -type LogLevel = 'Trace' | 'Info' | 'Error'; - export class Log { - private output: vscode.OutputChannel; + private output: vscode.LogOutputChannel; constructor(private readonly type: AuthProviderType) { const friendlyName = this.type === AuthProviderType.github ? 'GitHub' : 'GitHub Enterprise'; - this.output = vscode.window.createOutputChannel(`${friendlyName} Authentication`); + this.output = vscode.window.createOutputChannel(`${friendlyName} Authentication`, { log: true }); } - private data2String(data: any): string { - if (data instanceof Error) { - return data.stack || data.message; - } - if (data.success === false && data.message) { - return data.message; - } - return data.toString(); + public trace(message: string): void { + this.output.trace(message); } - public trace(message: string, data?: any): void { - this.logLevel('Trace', message, data); + public info(message: string): void { + this.output.info(message); } - public info(message: string, data?: any): void { - this.logLevel('Info', message, data); + public error(message: string): void { + this.output.error(message); } - public error(message: string, data?: any): void { - this.logLevel('Error', message, data); - } - - public logLevel(level: LogLevel, message: string, data?: any): void { - this.output.appendLine(`[${level} - ${this.now()}] ${message}`); - if (data) { - this.output.appendLine(this.data2String(data)); - } - } - - private now(): string { - const now = new Date(); - return padLeft(now.getUTCHours() + '', 2, '0') - + ':' + padLeft(now.getMinutes() + '', 2, '0') - + ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds(); - } -} - -function padLeft(s: string, n: number, pad = ' ') { - return pad.repeat(Math.max(0, n - s.length)) + s; } diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index 4e005f0c01..864dc3532e 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -4,22 +4,42 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { GitHubAuthenticationProvider, AuthProviderType } from './github'; +import { GitHubAuthenticationProvider, UriEventHandler } from './github'; + +function initGHES(context: vscode.ExtensionContext, uriHandler: UriEventHandler) { + const settingValue = vscode.workspace.getConfiguration().get('github-enterprise.uri'); + if (!settingValue) { + return undefined; + } + + // validate user value + let uri: vscode.Uri; + try { + uri = vscode.Uri.parse(settingValue, true); + } catch (e) { + vscode.window.showErrorMessage(vscode.l10n.t('GitHub Enterprise Server URI is not a valid URI: {0}', e.message ?? e)); + return; + } + + const githubEnterpriseAuthProvider = new GitHubAuthenticationProvider(context, uriHandler, uri); + context.subscriptions.push(githubEnterpriseAuthProvider); + return githubEnterpriseAuthProvider; +} export function activate(context: vscode.ExtensionContext) { - context.subscriptions.push(new GitHubAuthenticationProvider(context, AuthProviderType.github)); + const uriHandler = new UriEventHandler(); + context.subscriptions.push(uriHandler); + context.subscriptions.push(vscode.window.registerUriHandler(uriHandler)); - let githubEnterpriseAuthProvider: GitHubAuthenticationProvider | undefined; - if (vscode.workspace.getConfiguration().get('github-enterprise.uri')) { - githubEnterpriseAuthProvider = new GitHubAuthenticationProvider(context, AuthProviderType.githubEnterprise); - context.subscriptions.push(githubEnterpriseAuthProvider); - } + context.subscriptions.push(new GitHubAuthenticationProvider(context, uriHandler)); + + let githubEnterpriseAuthProvider: GitHubAuthenticationProvider | undefined = initGHES(context, uriHandler); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async e => { if (e.affectsConfiguration('github-enterprise.uri')) { - if (!githubEnterpriseAuthProvider && vscode.workspace.getConfiguration().get('github-enterprise.uri')) { - githubEnterpriseAuthProvider = new GitHubAuthenticationProvider(context, AuthProviderType.githubEnterprise); - context.subscriptions.push(githubEnterpriseAuthProvider); + if (vscode.workspace.getConfiguration().get('github-enterprise.uri')) { + githubEnterpriseAuthProvider?.dispose(); + githubEnterpriseAuthProvider = initGHES(context, uriHandler); } } })); diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index 7c917e6f34..67648d86b4 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { v4 as uuid } from 'uuid'; -import { Keychain } from './common/keychain'; -import { GitHubEnterpriseServer, GitHubServer, IGitHubServer } from './githubServer'; -import { arrayEquals } from './common/utils'; -import { ExperimentationTelemetry } from './experimentationService'; import TelemetryReporter from '@vscode/extension-telemetry'; +import { Keychain } from './common/keychain'; +import { GitHubServer, IGitHubServer } from './githubServer'; +import { arrayEquals } from './common/utils'; +import { ExperimentationTelemetry } from './common/experimentationService'; import { Log } from './common/logger'; +import { crypto } from './node/crypto'; interface SessionData { id: string; @@ -28,30 +28,48 @@ export enum AuthProviderType { githubEnterprise = 'github-enterprise' } +export class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { + public handleUri(uri: vscode.Uri) { + this.fire(uri); + } +} + export class GitHubAuthenticationProvider implements vscode.AuthenticationProvider, vscode.Disposable { - private _sessionChangeEmitter = new vscode.EventEmitter(); - private _logger = new Log(this.type); - private _githubServer: IGitHubServer; - private _telemetryReporter: ExperimentationTelemetry; + private readonly _sessionChangeEmitter = new vscode.EventEmitter(); + private readonly _logger: Log; + private readonly _githubServer: IGitHubServer; + private readonly _telemetryReporter: ExperimentationTelemetry; + private readonly _keychain: Keychain; + private readonly _accountsSeen = new Set(); + private readonly _disposable: vscode.Disposable | undefined; - private _keychain: Keychain = new Keychain(this.context, `${this.type}.auth`, this._logger); private _sessionsPromise: Promise; - private _accountsSeen = new Set(); - private _disposable: vscode.Disposable; - constructor(private readonly context: vscode.ExtensionContext, private readonly type: AuthProviderType) { - const { name, version, aiKey } = context.extension.packageJSON as { name: string; version: string; aiKey: string }; - this._telemetryReporter = new ExperimentationTelemetry(context, new TelemetryReporter(name, version, aiKey)); + constructor( + private readonly context: vscode.ExtensionContext, + uriHandler: UriEventHandler, + ghesUri?: vscode.Uri + ) { + const { aiKey } = context.extension.packageJSON as { name: string; version: string; aiKey: string }; + this._telemetryReporter = new ExperimentationTelemetry(context, new TelemetryReporter(aiKey)); - if (this.type === AuthProviderType.github) { - this._githubServer = new GitHubServer( - // We only can use the Device Code flow when we have a full node environment because of CORS. - context.extension.extensionKind === vscode.ExtensionKind.Workspace || vscode.env.uiKind === vscode.UIKind.Desktop, - this._logger, - this._telemetryReporter); - } else { - this._githubServer = new GitHubEnterpriseServer(this._logger, this._telemetryReporter); - } + const type = ghesUri ? AuthProviderType.githubEnterprise : AuthProviderType.github; + + this._logger = new Log(type); + + this._keychain = new Keychain( + this.context, + type === AuthProviderType.github + ? `${type}.auth` + : `${ghesUri?.authority}${ghesUri?.path}.ghes.auth`, + this._logger); + + this._githubServer = new GitHubServer( + this._logger, + this._telemetryReporter, + uriHandler, + context.extension.extensionKind, + ghesUri); // Contains the current state of the sessions we have available. this._sessionsPromise = this.readSessions().then((sessions) => { @@ -62,14 +80,13 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid this._disposable = vscode.Disposable.from( this._telemetryReporter, - this._githubServer, vscode.authentication.registerAuthenticationProvider(type, this._githubServer.friendlyName, this, { supportsMultipleAccounts: false }), this.context.secrets.onDidChange(() => this.checkForUpdates()) ); } dispose() { - this._disposable.dispose(); + this._disposable?.dispose(); } get onDidChangeSessions() { @@ -93,7 +110,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid // We only want to fire a telemetry if we haven't seen this account yet in this session. if (!this._accountsSeen.has(session.account.id)) { this._accountsSeen.add(session.account.id); - this._githubServer.sendAdditionalTelemetryInfo(session.accessToken); + this._githubServer.sendAdditionalTelemetryInfo(session); } } @@ -259,7 +276,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid */ this._telemetryReporter?.sendTelemetryEvent('loginFailed'); - vscode.window.showErrorMessage(`Sign in failed: ${e}`); + vscode.window.showErrorMessage(vscode.l10n.t('Sign in failed: {0}', `${e}`)); this._logger.error(e); throw e; } @@ -268,7 +285,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid private async tokenToSession(token: string, scopes: string[]): Promise { const userInfo = await this._githubServer.getUserInfo(token); return { - id: uuid(), + id: crypto.getRandomValues(new Uint32Array(2)).reduce((prev, curr) => prev += curr.toString(16), ''), accessToken: token, account: { label: userInfo.accountName, id: userInfo.id }, scopes @@ -302,7 +319,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid */ this._telemetryReporter?.sendTelemetryEvent('logoutFailed'); - vscode.window.showErrorMessage(`Sign out failed: ${e}`); + vscode.window.showErrorMessage(vscode.l10n.t('Sign out failed: {0}', `${e}`)); this._logger.error(e); throw e; } diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 2ccf5e2530..af1ed77d81 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -3,45 +3,36 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vscode-nls'; import * as vscode from 'vscode'; -import fetch, { Response } from 'node-fetch'; -import { v4 as uuid } from 'uuid'; +import * as path from 'path'; import { PromiseAdapter, promiseFromEvent } from './common/utils'; -import { ExperimentationTelemetry } from './experimentationService'; -import { AuthProviderType } from './github'; +import { ExperimentationTelemetry } from './common/experimentationService'; +import { AuthProviderType, UriEventHandler } from './github'; import { Log } from './common/logger'; -import { isSupportedEnvironment } from './common/env'; -import { LoopbackAuthServer } from './authServer'; -import path = require('path'); +import { isSupportedClient, isSupportedTarget } from './common/env'; +import { LoopbackAuthServer } from './node/authServer'; +import { crypto } from './node/crypto'; +import { fetching } from './node/fetch'; -const localize = nls.loadMessageBundle(); const CLIENT_ID = '01ab8ac9400c4e429b23'; -const GITHUB_AUTHORIZE_URL = 'https://github.com/login/oauth/authorize'; -// TODO: change to stable when that happens const GITHUB_TOKEN_URL = 'https://vscode.dev/codeExchangeProxyEndpoints/github/login/oauth/access_token'; + +// This is the error message that we throw if the login was cancelled for any reason. Extensions +// calling `getSession` can handle this error to know that the user cancelled the login. +const CANCELLATION_ERROR = 'Cancelled'; +// These error messages are internal and should not be shown to the user in any way. +const TIMED_OUT_ERROR = 'Timed out'; +const USER_CANCELLATION_ERROR = 'User Cancelled'; const NETWORK_ERROR = 'network error'; const REDIRECT_URL_STABLE = 'https://vscode.dev/redirect'; const REDIRECT_URL_INSIDERS = 'https://insiders.vscode.dev/redirect'; -class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { - constructor(private readonly Logger: Log) { - super(); - } - - public handleUri(uri: vscode.Uri) { - this.Logger.trace('Handling Uri...'); - this.fire(uri); - } -} - -export interface IGitHubServer extends vscode.Disposable { +export interface IGitHubServer { login(scopes: string): Promise; getUserInfo(token: string): Promise<{ id: string; accountName: string }>; - sendAdditionalTelemetryInfo(token: string): Promise; + sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise; friendlyName: string; - type: AuthProviderType; } interface IGitHubDeviceCodeResponse { @@ -54,10 +45,10 @@ interface IGitHubDeviceCodeResponse { async function getScopes(token: string, serverUri: vscode.Uri, logger: Log): Promise { try { logger.info('Getting token scopes...'); - const result = await fetch(serverUri.toString(), { + const result = await fetching(serverUri.toString(), { headers: { Authorization: `token ${token}`, - 'User-Agent': 'Visual-Studio-Code' + 'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})` } }); @@ -74,71 +65,55 @@ async function getScopes(token: string, serverUri: vscode.Uri, logger: Log): Pro } } -async function getUserInfo(token: string, serverUri: vscode.Uri, logger: Log): Promise<{ id: string; accountName: string }> { - let result: Response; - try { - logger.info('Getting user info...'); - result = await fetch(serverUri.toString(), { - headers: { - Authorization: `token ${token}`, - 'User-Agent': 'Visual-Studio-Code' - } - }); - } catch (ex) { - logger.error(ex.message); - throw new Error(NETWORK_ERROR); - } - - if (result.ok) { - try { - const json = await result.json(); - logger.info('Got account info!'); - return { id: json.id, accountName: json.login }; - } catch (e) { - logger.error(`Unexpected error parsing response from GitHub: ${e.message ?? e}`); - throw e; - } - } else { - // either display the response message or the http status text - let errorMessage = result.statusText; - try { - const json = await result.json(); - if (json.message) { - errorMessage = json.message; - } - } catch (err) { - // noop - } - logger.error(`Getting account info failed: ${errorMessage}`); - throw new Error(errorMessage); - } -} - export class GitHubServer implements IGitHubServer { - friendlyName = 'GitHub'; - type = AuthProviderType.github; + readonly friendlyName: string; - private _pendingNonces = new Map(); - private _codeExchangePromises = new Map; cancel: vscode.EventEmitter }>(); - private _disposable: vscode.Disposable; - private _uriHandler = new UriEventHandler(this._logger); - private readonly getRedirectEndpoint: Thenable; + private readonly _pendingNonces = new Map(); + private readonly _codeExchangePromises = new Map; cancel: vscode.EventEmitter }>(); + private readonly _type: AuthProviderType; - constructor(private readonly _supportDeviceCodeFlow: boolean, private readonly _logger: Log, private readonly _telemetryReporter: ExperimentationTelemetry) { - this._disposable = vscode.window.registerUriHandler(this._uriHandler); + private _redirectEndpoint: string | undefined; - this.getRedirectEndpoint = vscode.commands.executeCommand<{ [providerId: string]: string } | undefined>('workbench.getCodeExchangeProxyEndpoints').then((proxyEndpoints) => { - // If we are running in insiders vscode.dev, then ensure we use the redirect route on that. - let redirectUri = REDIRECT_URL_STABLE; - if (proxyEndpoints?.github && new URL(proxyEndpoints.github).hostname === 'insiders.vscode.dev') { - redirectUri = REDIRECT_URL_INSIDERS; - } - return redirectUri; - }); + constructor( + private readonly _logger: Log, + private readonly _telemetryReporter: ExperimentationTelemetry, + private readonly _uriHandler: UriEventHandler, + private readonly _extensionKind: vscode.ExtensionKind, + private readonly _ghesUri?: vscode.Uri + ) { + this._type = _ghesUri ? AuthProviderType.githubEnterprise : AuthProviderType.github; + this.friendlyName = this._type === AuthProviderType.github ? 'GitHub' : _ghesUri?.authority!; } - dispose() { - this._disposable.dispose(); + get baseUri() { + if (this._type === AuthProviderType.github) { + return vscode.Uri.parse('https://github.com/'); + } + return this._ghesUri!; + } + + private async getRedirectEndpoint(): Promise { + if (this._redirectEndpoint) { + return this._redirectEndpoint; + } + if (this._type === AuthProviderType.github) { + const proxyEndpoints = await vscode.commands.executeCommand<{ [providerId: string]: string } | undefined>('workbench.getCodeExchangeProxyEndpoints'); + // If we are running in insiders vscode.dev, then ensure we use the redirect route on that. + this._redirectEndpoint = REDIRECT_URL_STABLE; + if (proxyEndpoints?.github && new URL(proxyEndpoints.github).hostname === 'insiders.vscode.dev') { + this._redirectEndpoint = REDIRECT_URL_INSIDERS; + } + } else { + // GHE only supports a single redirect endpoint, so we can't use + // insiders.vscode.dev/redirect when we're running in Insiders, unfortunately. + // Additionally, we make the assumption that this function will only be used + // in flows that target supported GHE targets, not on-prem GHES. Because of this + // assumption, we can assume that the GHE version used is at least 3.8 which is + // the version that changed the redirect endpoint to this URI from the old + // GitHub maintained server. + this._redirectEndpoint = 'https://vscode.dev/redirect'; + } + return this._redirectEndpoint; } // TODO@joaomoreno TODO@TylerLeonhardt @@ -152,99 +127,117 @@ export class GitHubServer implements IGitHubServer { // Used for showing a friendlier message to the user when the explicitly cancel a flow. let userCancelled: boolean | undefined; - const yes = localize('yes', "Yes"); - const no = localize('no', "No"); - const promptToContinue = async () => { + const yes = vscode.l10n.t('Yes'); + const no = vscode.l10n.t('No'); + const promptToContinue = async (mode: string) => { if (userCancelled === undefined) { // We haven't had a failure yet so wait to prompt return; } const message = userCancelled - ? localize('userCancelledMessage', "Having trouble logging in? Would you like to try a different way?") - : localize('otherReasonMessage', "You have not yet finished authorizing this extension to use GitHub. Would you like to keep trying?"); + ? vscode.l10n.t('Having trouble logging in? Would you like to try a different way? ({0})', mode) + : vscode.l10n.t('You have not yet finished authorizing this extension to use GitHub. Would you like to try a different way? ({0})', mode); const result = await vscode.window.showWarningMessage(message, yes, no); if (result !== yes) { - throw new Error('Cancelled'); + throw new Error(CANCELLATION_ERROR); } }; - const nonce = uuid(); + const nonce: string = crypto.getRandomValues(new Uint32Array(2)).reduce((prev, curr) => prev += curr.toString(16), ''); const callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.github-authentication/did-authenticate?nonce=${encodeURIComponent(nonce)}`)); - const supported = isSupportedEnvironment(callbackUri); - if (supported) { + const supportedClient = isSupportedClient(callbackUri); + const supportedTarget = isSupportedTarget(this._type, this._ghesUri); + if (supportedClient && supportedTarget) { try { return await this.doLoginWithoutLocalServer(scopes, nonce, callbackUri); } catch (e) { this._logger.error(e); - userCancelled = e.message ?? e === 'User Cancelled'; + userCancelled = e.message ?? e === USER_CANCELLATION_ERROR; } } - // Starting a local server isn't supported in web - if (vscode.env.uiKind === vscode.UIKind.Desktop) { + // Starting a local server is only supported if: + // 1. We are in a UI extension because we need to open a port on the machine that has the browser + // 2. We are in a node runtime because we need to open a port on the machine + // 3. code exchange can only be done with a supported target + if ( + this._extensionKind === vscode.ExtensionKind.UI && + typeof navigator === 'undefined' && + supportedTarget + ) { try { - await promptToContinue(); + await promptToContinue(vscode.l10n.t('local server')); return await this.doLoginWithLocalServer(scopes); } catch (e) { - this._logger.error(e); - userCancelled = e.message ?? e === 'User Cancelled'; + userCancelled = this.processLoginError(e); } } - if (this._supportDeviceCodeFlow) { + // We only can use the Device Code flow when we have a full node environment because of CORS. + if (typeof navigator === 'undefined') { try { - await promptToContinue(); + await promptToContinue(vscode.l10n.t('device code')); return await this.doLoginDeviceCodeFlow(scopes); } catch (e) { - this._logger.error(e); - userCancelled = e.message ?? e === 'User Cancelled'; - } - } else if (!supported) { - try { - await promptToContinue(); - return await this.doLoginWithPat(scopes); - } catch (e) { - this._logger.error(e); - userCancelled = e.message ?? e === 'User Cancelled'; + userCancelled = this.processLoginError(e); } } - throw new Error(userCancelled ? 'Cancelled' : 'No auth flow succeeded.'); + // In a supported environment, we can't use PAT auth because we use this auth for Settings Sync and it doesn't support PATs. + // With that said, GitHub Enterprise isn't used by Settings Sync so we can use PATs for that. + if (!supportedClient || this._type === AuthProviderType.githubEnterprise) { + try { + await promptToContinue(vscode.l10n.t('personal access token')); + return await this.doLoginWithPat(scopes); + } catch (e) { + userCancelled = this.processLoginError(e); + } + } + + throw new Error(userCancelled ? CANCELLATION_ERROR : 'No auth flow succeeded.'); } private async doLoginWithoutLocalServer(scopes: string, nonce: string, callbackUri: vscode.Uri): Promise { this._logger.info(`Trying without local server... (${scopes})`); return await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, - title: localize('signingIn', "Signing in to github.com..."), + title: vscode.l10n.t({ + message: 'Signing in to {0}...', + args: [this.baseUri.authority], + comment: ['The {0} will be a url, e.g. github.com'] + }), cancellable: true }, async (_, token) => { const existingNonces = this._pendingNonces.get(scopes) || []; this._pendingNonces.set(scopes, [...existingNonces, nonce]); - const redirectUri = await this.getRedirectEndpoint; + const redirectUri = await this.getRedirectEndpoint(); const searchParams = new URLSearchParams([ ['client_id', CLIENT_ID], ['redirect_uri', redirectUri], ['scope', scopes], ['state', encodeURIComponent(callbackUri.toString(true))] ]); - const uri = vscode.Uri.parse(`${GITHUB_AUTHORIZE_URL}?${searchParams.toString()}`); + + const uri = vscode.Uri.parse(this.baseUri.with({ + path: '/login/oauth/authorize', + query: searchParams.toString() + }).toString(true)); await vscode.env.openExternal(uri); // Register a single listener for the URI callback, in case the user starts the login process multiple times // before completing it. let codeExchangePromise = this._codeExchangePromises.get(scopes); if (!codeExchangePromise) { - codeExchangePromise = promiseFromEvent(this._uriHandler.event, this.handleUri(scopes)); + codeExchangePromise = promiseFromEvent(this._uriHandler!.event, this.handleUri(scopes)); this._codeExchangePromises.set(scopes, codeExchangePromise); } try { return await Promise.race([ codeExchangePromise.promise, - new Promise((_, reject) => setTimeout(() => reject('Cancelled'), 60000)), - promiseFromEvent(token.onCancellationRequested, (_, __, reject) => { reject('User Cancelled'); }).promise + new Promise((_, reject) => setTimeout(() => reject(TIMED_OUT_ERROR), 300_000)), // 5min timeout + promiseFromEvent(token.onCancellationRequested, (_, __, reject) => { reject(USER_CANCELLATION_ERROR); }).promise ]); } finally { this._pendingNonces.delete(scopes); @@ -258,17 +251,25 @@ export class GitHubServer implements IGitHubServer { this._logger.info(`Trying with local server... (${scopes})`); return await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, - title: localize('signingInAnotherWay', "Signing in to github.com..."), + title: vscode.l10n.t({ + message: 'Signing in to {0}...', + args: [this.baseUri.authority], + comment: ['The {0} will be a url, e.g. github.com'] + }), cancellable: true }, async (_, token) => { - const redirectUri = await this.getRedirectEndpoint; + const redirectUri = await this.getRedirectEndpoint(); const searchParams = new URLSearchParams([ ['client_id', CLIENT_ID], ['redirect_uri', redirectUri], ['scope', scopes], ]); - const loginUrl = `${GITHUB_AUTHORIZE_URL}?${searchParams.toString()}`; - const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl); + + const loginUrl = this.baseUri.with({ + path: '/login/oauth/authorize', + query: searchParams.toString() + }); + const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl.toString(true)); const port = await server.start(); let codeToExchange; @@ -276,8 +277,8 @@ export class GitHubServer implements IGitHubServer { vscode.env.openExternal(vscode.Uri.parse(`http://127.0.0.1:${port}/signin?nonce=${encodeURIComponent(server.nonce)}`)); const { code } = await Promise.race([ server.waitForOAuthResponse(), - new Promise((_, reject) => setTimeout(() => reject('Cancelled'), 60000)), - promiseFromEvent(token.onCancellationRequested, (_, __, reject) => { reject('User Cancelled'); }).promise + new Promise((_, reject) => setTimeout(() => reject(TIMED_OUT_ERROR), 300_000)), // 5min timeout + promiseFromEvent(token.onCancellationRequested, (_, __, reject) => { reject(USER_CANCELLATION_ERROR); }).promise ]); codeToExchange = code; } finally { @@ -295,8 +296,11 @@ export class GitHubServer implements IGitHubServer { this._logger.info(`Trying device code flow... (${scopes})`); // Get initial device code - const uri = `https://github.com/login/device/code?client_id=${CLIENT_ID}&scope=${scopes}`; - const result = await fetch(uri, { + const uri = this.baseUri.with({ + path: '/login/device/code', + query: `client_id=${CLIENT_ID}&scope=${scopes}` + }); + const result = await fetching(uri.toString(true), { method: 'POST', headers: { Accept: 'application/json' @@ -308,16 +312,16 @@ export class GitHubServer implements IGitHubServer { const json = await result.json() as IGitHubDeviceCodeResponse; - + const button = vscode.l10n.t('Copy & Continue to GitHub'); const modalResult = await vscode.window.showInformationMessage( - localize('code.title', "Your Code: {0}", json.user_code), + vscode.l10n.t({ message: 'Your Code: {0}', args: [json.user_code], comment: ['The {0} will be a code, e.g. 123-456'] }), { modal: true, - detail: localize('code.detail', "To finish authenticating, navigate to GitHub and paste in the above one-time code.") - }, 'Copy & Continue to GitHub'); + detail: vscode.l10n.t('To finish authenticating, navigate to GitHub and paste in the above one-time code.') + }, button); - if (modalResult !== 'Copy & Continue to GitHub') { - throw new Error('User Cancelled'); + if (modalResult !== button) { + throw new Error(USER_CANCELLATION_ERROR); } await vscode.env.clipboard.writeText(json.user_code); @@ -330,8 +334,24 @@ export class GitHubServer implements IGitHubServer { private async doLoginWithPat(scopes: string): Promise { this._logger.info(`Trying to retrieve PAT... (${scopes})`); - const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true }); - if (!token) { throw new Error('User Cancelled'); } + + const button = vscode.l10n.t('Continue to GitHub'); + const modalResult = await vscode.window.showInformationMessage( + vscode.l10n.t('Continue to GitHub to create a Personal Access Token (PAT)'), + { + modal: true, + detail: vscode.l10n.t('To finish authenticating, navigate to GitHub to create a PAT then paste the PAT into the input box.') + }, button); + + if (modalResult !== button) { + throw new Error(USER_CANCELLATION_ERROR); + } + + const description = `${vscode.env.appName} (${scopes})`; + const uriToOpen = await vscode.env.asExternalUri(this.baseUri.with({ path: '/settings/tokens/new', query: `description=${description}&scopes=${scopes.split(' ').join(',')}` })); + await vscode.env.openExternal(uriToOpen); + const token = await vscode.window.showInputBox({ placeHolder: `ghp_1a2b3c4...`, prompt: `GitHub Personal Access Token - ${scopes}`, ignoreFocusOut: true }); + if (!token) { throw new Error(USER_CANCELLATION_ERROR); } const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user'] const scopesList = scopes.split(' '); // Example: 'read:user repo user:email' @@ -357,24 +377,30 @@ export class GitHubServer implements IGitHubServer { return await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: true, - title: localize( - 'progress', - "Open [{0}]({0}) in a new tab and paste your one-time code: {1}", - json.verification_uri, - json.user_code) + title: vscode.l10n.t({ + message: 'Open [{0}]({0}) in a new tab and paste your one-time code: {1}', + args: [json.verification_uri, json.user_code], + comment: [ + 'The [{0}]({0}) will be a url and the {1} will be a code, e.g. 123-456', + '{Locked="[{0}]({0})"}' + ] + }) }, async (_, token) => { - const refreshTokenUri = `https://github.com/login/oauth/access_token?client_id=${CLIENT_ID}&device_code=${json.device_code}&grant_type=urn:ietf:params:oauth:grant-type:device_code`; + const refreshTokenUri = this.baseUri.with({ + path: '/login/oauth/access_token', + query: `client_id=${CLIENT_ID}&device_code=${json.device_code}&grant_type=urn:ietf:params:oauth:grant-type:device_code` + }); // Try for 2 minutes const attempts = 120 / json.interval; for (let i = 0; i < attempts; i++) { await new Promise(resolve => setTimeout(resolve, json.interval * 1000)); if (token.isCancellationRequested) { - throw new Error('User Cancelled'); + throw new Error(USER_CANCELLATION_ERROR); } let accessTokenResult; try { - accessTokenResult = await fetch(refreshTokenUri, { + accessTokenResult = await fetching(refreshTokenUri.toString(true), { method: 'POST', headers: { Accept: 'application/json' @@ -401,7 +427,7 @@ export class GitHubServer implements IGitHubServer { return accessTokenJson.access_token; } - throw new Error('Cancelled'); + throw new Error(TIMED_OUT_ERROR); }); } @@ -439,8 +465,12 @@ export class GitHubServer implements IGitHubServer { const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints'); const endpointUrl = proxyEndpoints?.github ? `${proxyEndpoints.github}login/oauth/access_token` : GITHUB_TOKEN_URL; - const body = `code=${code}`; - const result = await fetch(endpointUrl, { + const body = new URLSearchParams([['code', code]]); + if (this._type === AuthProviderType.githubEnterprise) { + body.append('github_enterprise', this.baseUri.toString(true)); + body.append('redirect_uri', await this.getRedirectEndpoint()); + } + const result = await fetching(endpointUrl, { method: 'POST', headers: { Accept: 'application/json', @@ -448,7 +478,7 @@ export class GitHubServer implements IGitHubServer { 'Content-Length': body.toString() }, - body + body: body.toString() }); if (result.ok) { @@ -464,15 +494,56 @@ export class GitHubServer implements IGitHubServer { } private getServerUri(path: string = '') { - const apiUri = vscode.Uri.parse('https://api.github.com'); - return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}${path}`); + const apiUri = this.baseUri; + // github.com and Hosted GitHub Enterprise instances + if (isSupportedTarget(this._type, this._ghesUri)) { + return vscode.Uri.parse(`${apiUri.scheme}://api.${apiUri.authority}`).with({ path }); + } + // GitHub Enterprise Server (aka on-prem) + return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}/api/v3${path}`); } - public getUserInfo(token: string): Promise<{ id: string; accountName: string }> { - return getUserInfo(token, this.getServerUri('/user'), this._logger); + public async getUserInfo(token: string): Promise<{ id: string; accountName: string }> { + let result; + try { + this._logger.info('Getting user info...'); + result = await fetching(this.getServerUri('/user').toString(), { + headers: { + Authorization: `token ${token}`, + 'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})` + } + }); + } catch (ex) { + this._logger.error(ex.message); + throw new Error(NETWORK_ERROR); + } + + if (result.ok) { + try { + const json = await result.json(); + this._logger.info('Got account info!'); + return { id: json.id, accountName: json.login }; + } catch (e) { + this._logger.error(`Unexpected error parsing response from GitHub: ${e.message ?? e}`); + throw e; + } + } else { + // either display the response message or the http status text + let errorMessage = result.statusText; + try { + const json = await result.json(); + if (json.message) { + errorMessage = json.message; + } + } catch (err) { + // noop + } + this._logger.error(`Getting account info failed: ${errorMessage}`); + throw new Error(errorMessage); + } } - public async sendAdditionalTelemetryInfo(token: string): Promise { + public async sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise { if (!vscode.env.isTelemetryEnabled) { return; } @@ -482,53 +553,75 @@ export class GitHubServer implements IGitHubServer { return; } + if (this._type === AuthProviderType.github) { + return await this.checkUserDetails(session); + } + + // GHES + await this.checkEnterpriseVersion(session.accessToken); + } + + private async checkUserDetails(session: vscode.AuthenticationSession): Promise { + let edu: string | undefined; + try { - const result = await fetch('https://education.github.com/api/user', { + const result = await fetching('https://education.github.com/api/user', { headers: { - Authorization: `token ${token}`, + Authorization: `token ${session.accessToken}`, 'faculty-check-preview': 'true', - 'User-Agent': 'Visual-Studio-Code' + 'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})` } }); if (result.ok) { const json: { student: boolean; faculty: boolean } = await result.json(); - - /* __GDPR__ - "session" : { - "owner": "TylerLeonhardt", - "isEdu": { "classification": "NonIdentifiableDemographicInfo", "purpose": "FeatureInsight" } - } - */ - this._telemetryReporter.sendTelemetryEvent('session', { - isEdu: json.student - ? 'student' - : json.faculty - ? 'faculty' - : 'none' - }); + edu = json.student + ? 'student' + : json.faculty + ? 'faculty' + : 'none'; + } else { + edu = 'unknown'; } } catch (e) { - // No-op + edu = 'unknown'; } + + /* __GDPR__ + "session" : { + "owner": "TylerLeonhardt", + "isEdu": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "isManaged": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this._telemetryReporter.sendTelemetryEvent('session', { + isEdu: edu, + // Apparently, this is how you tell if a user is an EMU... + isManaged: session.account.label.includes('_') ? 'true' : 'false' + }); } - public async checkEnterpriseVersion(token: string): Promise { + private async checkEnterpriseVersion(token: string): Promise { try { + let version: string; + if (!isSupportedTarget(this._type, this._ghesUri)) { + const result = await fetching(this.getServerUri('/meta').toString(), { + headers: { + Authorization: `token ${token}`, + 'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})` + } + }); - const result = await fetch(this.getServerUri('/meta').toString(), { - headers: { - Authorization: `token ${token}`, - 'User-Agent': 'Visual-Studio-Code' + if (!result.ok) { + return; } - }); - if (!result.ok) { - return; + const json: { verifiable_password_authentication: boolean; installed_version: string } = await result.json(); + version = json.installed_version; + } else { + version = 'hosted'; } - const json: { verifiable_password_authentication: boolean; installed_version: string } = await result.json(); - /* __GDPR__ "ghe-session" : { "owner": "TylerLeonhardt", @@ -536,82 +629,18 @@ export class GitHubServer implements IGitHubServer { } */ this._telemetryReporter.sendTelemetryEvent('ghe-session', { - version: json.installed_version + version }); } catch { // No-op } } -} -export class GitHubEnterpriseServer implements IGitHubServer { - friendlyName = 'GitHub Enterprise'; - type = AuthProviderType.githubEnterprise; - - constructor(private readonly _logger: Log, private readonly telemetryReporter: ExperimentationTelemetry) { } - - dispose() { } - - public async login(scopes: string): Promise { - this._logger.info(`Logging in for the following scopes: ${scopes}`); - - const token = await vscode.window.showInputBox({ prompt: 'GitHub Personal Access Token', ignoreFocusOut: true }); - if (!token) { throw new Error('Sign in failed: No token provided'); } - - const tokenScopes = await getScopes(token, this.getServerUri('/'), this._logger); // Example: ['repo', 'user'] - const scopesList = scopes.split(' '); // Example: 'read:user repo user:email' - if (!scopesList.every(scope => { - const included = tokenScopes.includes(scope); - if (included || !scope.includes(':')) { - return included; - } - - return scope.split(':').some(splitScopes => { - return tokenScopes.includes(splitScopes); - }); - })) { - throw new Error(`The provided token does not match the requested scopes: ${scopes}`); - } - - return token; - } - - private getServerUri(path: string = '') { - const apiUri = vscode.Uri.parse(vscode.workspace.getConfiguration('github-enterprise').get('uri') || '', true); - return vscode.Uri.parse(`${apiUri.scheme}://${apiUri.authority}/api/v3${path}`); - } - - public async getUserInfo(token: string): Promise<{ id: string; accountName: string }> { - return getUserInfo(token, this.getServerUri('/user'), this._logger); - } - - public async sendAdditionalTelemetryInfo(token: string): Promise { - try { - - const result = await fetch(this.getServerUri('/meta').toString(), { - headers: { - Authorization: `token ${token}`, - 'User-Agent': 'Visual-Studio-Code' - } - }); - - if (!result.ok) { - return; - } - - const json: { verifiable_password_authentication: boolean; installed_version: string } = await result.json(); - - /* __GDPR__ - "ghe-session" : { - "owner": "TylerLeonhardt", - "version": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryReporter.sendTelemetryEvent('ghe-session', { - version: json.installed_version - }); - } catch { - // No-op + private processLoginError(error: Error): boolean { + if (error.message === CANCELLATION_ERROR) { + throw error; } + this._logger.error(error.message ?? error); + return error.message === USER_CANCELLATION_ERROR; } } diff --git a/extensions/github-authentication/src/authServer.ts b/extensions/github-authentication/src/node/authServer.ts similarity index 100% rename from extensions/github-authentication/src/authServer.ts rename to extensions/github-authentication/src/node/authServer.ts diff --git a/extensions/github-authentication/src/node/crypto.ts b/extensions/github-authentication/src/node/crypto.ts new file mode 100644 index 0000000000..0cb300e6a6 --- /dev/null +++ b/extensions/github-authentication/src/node/crypto.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { webcrypto } from 'crypto'; + +export const crypto = webcrypto as any as Crypto; diff --git a/src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts b/extensions/github-authentication/src/node/fetch.ts similarity index 80% rename from src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts rename to extensions/github-authentication/src/node/fetch.ts index cfee414aec..b7aa39ecbe 100644 --- a/src/vscode-dts/vscode.proposed.contribWebviewContext.d.ts +++ b/extensions/github-authentication/src/node/fetch.ts @@ -2,5 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import fetch from 'node-fetch'; -// empty placeholder declaration for the `webview/context`-menu contribution point +export const fetching = fetch; diff --git a/extensions/github-authentication/yarn.lock b/extensions/github-authentication/yarn.lock index f515adebd2..8a7108e02e 100644 --- a/extensions/github-authentication/yarn.lock +++ b/extensions/github-authentication/yarn.lock @@ -2,41 +2,167 @@ # yarn lockfile v1 -"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" - integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + tslib "^2.2.0" -"@microsoft/1ds-post-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" - integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== +"@azure/core-auth@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e" + integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ== dependencies: - "@microsoft/1ds-core-js" "3.2.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== +"@azure/core-rest-pipeline@^1.10.0": + version "1.10.3" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.3.tgz#7603afd71ff3c290351dbeeab2c814832e47b8ef" + integrity sha512-AMQb0ttiGJ0MIV/r+4TVra6U4+90mPeOveehFnrqKlo7dknPJYdJ61wOzYJXJjDxF8LcCtSogfRelkq+fCGFTw== dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.3.0" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== +"@azure/core-tracing@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" + integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw== + dependencies: + tslib "^2.2.0" -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== +"@azure/core-util@^1.3.0": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.3.2.tgz#3f8cfda1e87fac0ce84f8c1a42fcd6d2a986632d" + integrity sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.4.tgz#28bc6d0e5b3c38ef29296b32d35da4e483593fa1" + integrity sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg== + dependencies: + tslib "^2.2.0" + +"@microsoft/1ds-core-js@3.2.11", "@microsoft/1ds-core-js@^3.2.8": + version "3.2.11" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.11.tgz#0a3857562a9bde7c15a2fa141c647f40aabd52e2" + integrity sha512-UuqZ1iWjaEFsBsnB+J0Q4IbgJdvJAq3LcNArAdMQQq9+wBWpjyGG4yu9gL6fS5AKfpF6yy73mtgWD7WzvphMLQ== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.13" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/1ds-post-js@^3.2.8": + version "3.2.11" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.11.tgz#bbfc935bafb43982ccaf94ebd12f1fabf5ef8ebe" + integrity sha512-86prrN8cKjfpqfeyC4k0rFqg7fJDkTwPeKLHnkUgfq9mKVzu+BdtlEzaJtc56MroKWNYPkitZ/PWSwwpTPIgMA== + dependencies: + "@microsoft/1ds-core-js" "3.2.11" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-channel-js@2.8.13": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.13.tgz#bd117cc7b00ec929e74a6555cb7462ab4fccdf62" + integrity sha512-zc2BSsHk4HAqK5STdNzKGV817jKNbiTZPYpNt3zuE+jO5druJgloqrvclUfLnCoa7zwrQ2UxoAXlpJGmroGZPA== + dependencies: + "@microsoft/applicationinsights-common" "2.8.13" + "@microsoft/applicationinsights-core-js" "2.8.13" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.9" + +"@microsoft/applicationinsights-common@2.8.13": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.13.tgz#e3457bdc54a61cbfa481494da25e55bb9156096e" + integrity sha512-UYLLGVtuzrWUEmGYRroMzLyTi2fHqL6SwJUlmVWPJrmdK43PGpviRix/sBW0Qs+6qjiI1Z6CiG4Xah6w/HylhA== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.13" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.9" + +"@microsoft/applicationinsights-core-js@2.8.13": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.13.tgz#45c2b8fff35e5aa519355dd3a69e3758293b08f4" + integrity sha512-PP7Xjplvy0d5G2Tk7DcSDYRmgDYRv+7n3wEiqgm63DrSoa8rEuoODavjWunhX058zPNIeKbus59NE+DusLLyZg== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.9" + +"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085" + integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg== + +"@microsoft/applicationinsights-web-basic@^2.8.9": + version "2.8.13" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.13.tgz#a3934fa7b7f221d09fbf403fcfeb06a8c18ca246" + integrity sha512-DgPx1ryZucLWc285qkAEBG6LCcTSG6Gdb4u4yAlmAW0G+Qau49GoJnfJGR+cDXvyXSwHcH0dsainqzeYYY1K7A== + dependencies: + "@microsoft/applicationinsights-channel-js" "2.8.13" + "@microsoft/applicationinsights-common" "2.8.13" + "@microsoft/applicationinsights-core-js" "2.8.13" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.9" + +"@microsoft/applicationinsights-web-snippet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz#6bb788b2902e48bf5d460c38c6bb7fedd686ddd7" + integrity sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ== + +"@microsoft/dynamicproto-js@^1.1.7", "@microsoft/dynamicproto-js@^1.1.9": + version "1.1.9" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.9.tgz#7437db7aa061162ee94e4131b69a62b8dad5dea6" + integrity sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ== + +"@opentelemetry/api@^1.0.4": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f" + integrity sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA== + +"@opentelemetry/core@1.13.0", "@opentelemetry/core@^1.0.1": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.13.0.tgz#7cdcb4176d260d279b0aa31456c4ce2ba7f410aa" + integrity sha512-2dBX3Sj99H96uwJKvc2w9NOiNgbvAO6mOFJFramNkKfS9O4Um+VWgpnlAazoYjT6kUJ1MP70KQ5ngD4ed+4NUw== + dependencies: + "@opentelemetry/semantic-conventions" "1.13.0" + +"@opentelemetry/resources@1.13.0": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.13.0.tgz#436b33ea950004e66fce6575f2776a05faca7f8e" + integrity sha512-euqjOkiN6xhjE//0vQYGvbStxoD/WWQRhDiO0OTLlnLBO9Yw2Gd/VoSx2H+svsebjzYk5OxLuREBmcdw6rbUNg== + dependencies: + "@opentelemetry/core" "1.13.0" + "@opentelemetry/semantic-conventions" "1.13.0" + +"@opentelemetry/sdk-trace-base@^1.0.1": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.13.0.tgz#096cc2759430d880c5d886e009df2605097403dc" + integrity sha512-moTiQtc0uPR1hQLt6gLDJH9IIkeBhgRb71OKjNHZPE1VF45fHtD6nBDi5J/DkTHTwYP5X3kBJLa3xN7ub6J4eg== + dependencies: + "@opentelemetry/core" "1.13.0" + "@opentelemetry/resources" "1.13.0" + "@opentelemetry/semantic-conventions" "1.13.0" + +"@opentelemetry/semantic-conventions@1.13.0", "@opentelemetry/semantic-conventions@^1.0.1": + version "1.13.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.13.0.tgz#0290398b3eaebc6029c348988a78c3b688fe9219" + integrity sha512-LMGqfSZkaMQXqewO0o1wvWr/2fQdCh4a3Sqlxka/UsJCe0cfLulh6x2aqnKLnsrSGiCq5rSCwvINd152i0nCqw== + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@types/node-fetch@^2.5.7": version "2.5.7" @@ -56,18 +182,54 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -"@types/uuid@8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" - integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== - -"@vscode/extension-telemetry@0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" - integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== +"@vscode/extension-telemetry@0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.7.5.tgz#bf965731816e08c3f146f96d901ec67954fc913b" + integrity sha512-fJ5y3TcpqqkFYHneabYaoB4XAhDdVflVm+TDKshw9VOs77jkgNS4UA7LNXrWeO0eDne3Sh3JgURf+xzc1rk69w== dependencies: - "@microsoft/1ds-core-js" "^3.2.3" - "@microsoft/1ds-post-js" "^3.2.3" + "@microsoft/1ds-core-js" "^3.2.8" + "@microsoft/1ds-post-js" "^3.2.8" + "@microsoft/applicationinsights-web-basic" "^2.8.9" + applicationinsights "2.4.1" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +applicationinsights@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-2.4.1.tgz#4de4c4dd3c7c4a44445cfbf3d15808fc0dcc423d" + integrity sha512-0n0Ikd0gzSm460xm+M0UTWIwXrhrH/0bqfZatcJjYObWyefxfAxapGEyNnSGd1Tg90neHz+Yhf+Ff/zgvPiQYA== + dependencies: + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.10.0" + "@microsoft/applicationinsights-web-snippet" "^1.0.1" + "@opentelemetry/api" "^1.0.4" + "@opentelemetry/core" "^1.0.1" + "@opentelemetry/sdk-trace-base" "^1.0.1" + "@opentelemetry/semantic-conventions" "^1.0.1" + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "1.1.0" + diagnostic-channel-publishers "1.0.5" + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" asynckit@^0.4.0: version "0.4.0" @@ -81,6 +243,15 @@ axios@^0.26.1: dependencies: follow-redirects "^1.14.8" +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + combined-stream@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -88,11 +259,45 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +diagnostic-channel-publishers@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz#df8c317086c50f5727fdfb5d2fce214d2e4130ae" + integrity sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg== + +diagnostic-channel@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz#6985e9dfedfbc072d91dc4388477e4087147756e" + integrity sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ== + dependencies: + semver "^5.3.0" + +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + follow-redirects@^1.14.8: version "1.15.1" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" @@ -107,6 +312,32 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + mime-db@1.44.0: version "1.44.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" @@ -119,6 +350,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.44.0" +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + node-fetch@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -126,6 +362,21 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" +semver@^5.3.0, semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== + tas-client@0.1.58: version "0.1.58" resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.58.tgz#67d66bf0e27df5276ebc751105e6ad47791c36d8" @@ -138,15 +389,10 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -uuid@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" - integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== - -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== +tslib@^2.2.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== vscode-tas-client@^0.1.47: version "0.1.63" diff --git a/extensions/github/package.json b/extensions/github/package.json index a5de87a74b..64916932d3 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -27,7 +27,9 @@ }, "enabledApiProposals": [ "contribShareMenu", - "contribEditSessions" + "contribEditSessions", + "canonicalUriProvider", + "shareProvider" ], "contributes": { "commands": [ @@ -43,22 +45,30 @@ "command": "github.copyVscodeDevLinkFile", "title": "Copy vscode.dev Link" }, + { + "command": "github.copyVscodeDevLinkWithoutRange", + "title": "Copy vscode.dev Link" + }, { "command": "github.openOnVscodeDev", - "title": "Open on vscode.dev" + "title": "Open in vscode.dev", + "icon": "$(globe)" } ], "continueEditSession": [ { "command": "github.openOnVscodeDev", - "when": "github.hasGitHubRepo" + "when": "github.hasGitHubRepo", + "qualifiedName": "Continue Working in vscode.dev", + "category": "Remote Repositories", + "remoteGroup": "virtualfs_44_vscode-vfs_2_web@2" } ], "menus": { "commandPalette": [ { "command": "github.publish", - "when": "git-base.gitEnabled" + "when": "git-base.gitEnabled && remoteName != 'codespaces'" }, { "command": "github.copyVscodeDevLink", @@ -68,6 +78,10 @@ "command": "github.copyVscodeDevLinkFile", "when": "false" }, + { + "command": "github.copyVscodeDevLinkWithoutRange", + "when": "false" + }, { "command": "github.openOnVscodeDev", "when": "false" @@ -75,14 +89,42 @@ ], "file/share": [ { - "command": "github.copyVscodeDevLinkFile", - "when": "github.hasGitHubRepo" + "command": "github.copyVscodeDevLinkFile", + "when": "github.hasGitHubRepo && remoteName != 'codespaces'", + "group": "0_vscode@0" } ], "editor/context/share": [ { - "command": "github.copyVscodeDevLink", - "when": "github.hasGitHubRepo && resourceScheme != untitled" + "command": "github.copyVscodeDevLink", + "when": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'", + "group": "0_vscode@0" + } + ], + "explorer/context/share": [ + { + "command": "github.copyVscodeDevLinkWithoutRange", + "when": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'", + "group": "0_vscode@0" + } + ], + "editor/lineNumber/context": [ + { + "command": "github.copyVscodeDevLink", + "when": "github.hasGitHubRepo && resourceScheme != untitled && activeEditor == workbench.editors.files.textFileEditor && config.editor.lineNumbers == on && remoteName != 'codespaces'", + "group": "1_cutcopypaste@2" + }, + { + "command": "github.copyVscodeDevLink", + "when": "github.hasGitHubRepo && resourceScheme != untitled && activeEditor == workbench.editor.notebook && remoteName != 'codespaces'", + "group": "1_cutcopypaste@2" + } + ], + "editor/title/context/share": [ + { + "command": "github.copyVscodeDevLinkWithoutRange", + "when": "github.hasGitHubRepo && resourceScheme != untitled && remoteName != 'codespaces'", + "group": "0_vscode@0" } ] }, @@ -90,6 +132,12 @@ { "title": "GitHub", "properties": { + "github.branchProtection": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%config.branchProtection%" + }, "github.gitAuthentication": { "type": "boolean", "scope": "resource", @@ -112,12 +160,12 @@ { "view": "scm", "contents": "%welcome.publishFolder%", - "when": "config.git.enabled && git.state == initialized && workbenchState == folder" + "when": "config.git.enabled && git.state == initialized && workbenchState == folder && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0" }, { "view": "scm", "contents": "%welcome.publishWorkspaceFolder%", - "when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0" + "when": "config.git.enabled && git.state == initialized && workbenchState == workspace && workspaceFolderCount != 0 && git.parentRepositoryCount == 0 && git.unsafeRepositoryCount == 0" } ], "markdown.previewStyles": [ @@ -130,9 +178,10 @@ "watch": "gulp watch-extension:github" }, "dependencies": { - "@octokit/rest": "^18.0.1", - "tunnel": "^0.0.6", - "vscode-nls": "^4.1.2" + "@octokit/graphql": "5.0.5", + "@octokit/graphql-schema": "14.4.0", + "@octokit/rest": "19.0.4", + "tunnel": "^0.0.6" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index 2e4869a234..f3ce0c6bbf 100644 --- a/extensions/github/package.nls.json +++ b/extensions/github/package.nls.json @@ -1,10 +1,11 @@ { "displayName": "GitHub", "description": "GitHub features for Azure Data Studio", + "config.branchProtection": "Controls whether to query repository rules for GitHub repositories", "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within Azure Data Studio.", "config.gitProtocol": "Controls which protocol is used to clone a GitHub repository", "welcome.publishFolder": { - "message": "You can also directly publish this folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", + "message": "You can directly publish this folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", "comment": [ "{Locked='$(github)'}", "Do not translate '$(github)'. It will be rendered as an icon", @@ -14,7 +15,7 @@ ] }, "welcome.publishWorkspaceFolder": { - "message": "You can also directly publish a workspace folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", + "message": "You can directly publish a workspace folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", "comment": [ "{Locked='$(github)'}", "Do not translate '$(github)'. It will be rendered as an icon", diff --git a/extensions/github/src/auth.ts b/extensions/github/src/auth.ts index d143423ccd..3fcc0b5c14 100644 --- a/extensions/github/src/auth.ts +++ b/extensions/github/src/auth.ts @@ -5,10 +5,13 @@ import { AuthenticationSession, authentication, window } from 'vscode'; import { Agent, globalAgent } from 'https'; +import { graphql } from '@octokit/graphql/dist-types/types'; import { Octokit } from '@octokit/rest'; import { httpsOverHttp } from 'tunnel'; import { URL } from 'url'; +export class AuthenticationError extends Error { } + function getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent { if (!url) { return globalAgent; @@ -53,3 +56,34 @@ export function getOctokit(): Promise { return _octokit; } + +let _octokitGraphql: Promise | undefined; + +export async function getOctokitGraphql(silent = false): Promise { + if (!_octokitGraphql) { + try { + const session = await authentication.getSession('github', scopes, { silent }); + + if (!session) { + throw new AuthenticationError('No GitHub authentication session available.'); + } + + const token = session.accessToken; + const { graphql } = await import('@octokit/graphql'); + + return graphql.defaults({ + headers: { + authorization: `token ${token}` + }, + request: { + agent: getAgent() + } + }); + } catch (err) { + _octokitGraphql = undefined; + throw err; + } + } + + return _octokitGraphql; +} diff --git a/extensions/github/src/branchProtection.ts b/extensions/github/src/branchProtection.ts new file mode 100644 index 0000000000..fbb4c66e09 --- /dev/null +++ b/extensions/github/src/branchProtection.ts @@ -0,0 +1,231 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { authentication, EventEmitter, LogOutputChannel, Memento, Uri, workspace } from 'vscode'; +import { Repository as GitHubRepository, RepositoryRuleset } from '@octokit/graphql-schema'; +import { AuthenticationError, getOctokitGraphql } from './auth'; +import { API, BranchProtection, BranchProtectionProvider, BranchProtectionRule, Repository } from './typings/git'; +import { DisposableStore, getRepositoryFromUrl } from './util'; + +const REPOSITORY_QUERY = ` + query repositoryPermissions($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + defaultBranchRef { + name + }, + viewerPermission + } + } +`; + +const REPOSITORY_RULESETS_QUERY = ` + query repositoryRulesets($owner: String!, $repo: String!, $cursor: String, $limit: Int = 100) { + repository(owner: $owner, name: $repo) { + rulesets(includeParents: true, first: $limit, after: $cursor) { + nodes { + name + enforcement + rules(type: PULL_REQUEST) { + totalCount + } + conditions { + refName { + include + exclude + } + } + target + }, + pageInfo { + endCursor, + hasNextPage + } + } + } + } +`; + +export class GithubBranchProtectionProviderManager { + + private readonly disposables = new DisposableStore(); + private readonly providerDisposables = new DisposableStore(); + + private _enabled = false; + private set enabled(enabled: boolean) { + if (this._enabled === enabled) { + return; + } + + if (enabled) { + for (const repository of this.gitAPI.repositories) { + this.providerDisposables.add(this.gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository, this.globalState, this.logger))); + } + } else { + this.providerDisposables.dispose(); + } + + this._enabled = enabled; + } + + constructor( + private readonly gitAPI: API, + private readonly globalState: Memento, + private readonly logger: LogOutputChannel) { + this.disposables.add(this.gitAPI.onDidOpenRepository(repository => { + if (this._enabled) { + this.providerDisposables.add(gitAPI.registerBranchProtectionProvider(repository.rootUri, new GithubBranchProtectionProvider(repository, this.globalState, this.logger))); + } + })); + + this.disposables.add(workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('github.branchProtection')) { + this.updateEnablement(); + } + })); + + this.updateEnablement(); + } + + private updateEnablement(): void { + const config = workspace.getConfiguration('github', null); + this.enabled = config.get('branchProtection', true) === true; + } + + dispose(): void { + this.enabled = false; + this.disposables.dispose(); + } + +} + +export class GithubBranchProtectionProvider implements BranchProtectionProvider { + private readonly _onDidChangeBranchProtection = new EventEmitter(); + onDidChangeBranchProtection = this._onDidChangeBranchProtection.event; + + private branchProtection: BranchProtection[]; + private readonly globalStateKey = `branchProtection:${this.repository.rootUri.toString()}`; + + constructor( + private readonly repository: Repository, + private readonly globalState: Memento, + private readonly logger: LogOutputChannel) { + // Restore branch protection from global state + this.branchProtection = this.globalState.get(this.globalStateKey, []); + + repository.status().then(() => { + authentication.onDidChangeSessions(e => { + if (e.provider.id === 'github') { + this.updateRepositoryBranchProtection(true); + } + }); + this.updateRepositoryBranchProtection(); + }); + } + + provideBranchProtection(): BranchProtection[] { + return this.branchProtection; + } + + private async getRepositoryDetails(owner: string, repo: string, silent: boolean): Promise { + const graphql = await getOctokitGraphql(silent); + const { repository } = await graphql<{ repository: GitHubRepository }>(REPOSITORY_QUERY, { owner, repo }); + + return repository; + } + + private async getRepositoryRulesets(owner: string, repo: string, silent: boolean): Promise { + const rulesets: RepositoryRuleset[] = []; + + let cursor: string | undefined = undefined; + const graphql = await getOctokitGraphql(silent); + + while (true) { + const { repository } = await graphql<{ repository: GitHubRepository }>(REPOSITORY_RULESETS_QUERY, { owner, repo, cursor }); + + rulesets.push(...(repository.rulesets?.nodes ?? []) + // Active branch ruleset that contains the pull request required rule + .filter(node => node && node.target === 'BRANCH' && node.enforcement === 'ACTIVE' && (node.rules?.totalCount ?? 0) > 0) as RepositoryRuleset[]); + + if (repository.rulesets?.pageInfo.hasNextPage) { + cursor = repository.rulesets.pageInfo.endCursor as string | undefined; + } else { + break; + } + } + + return rulesets; + } + + private async updateRepositoryBranchProtection(silent = false): Promise { + const branchProtection: BranchProtection[] = []; + + try { + for (const remote of this.repository.state.remotes) { + const repository = getRepositoryFromUrl(remote.pushUrl ?? remote.fetchUrl ?? ''); + + if (!repository) { + continue; + } + + // Repository details + this.logger.trace(`Fetching repository details for "${repository.owner}/${repository.repo}".`); + const repositoryDetails = await this.getRepositoryDetails(repository.owner, repository.repo, silent); + + // Check repository write permission + if (repositoryDetails.viewerPermission !== 'ADMIN' && repositoryDetails.viewerPermission !== 'MAINTAIN' && repositoryDetails.viewerPermission !== 'WRITE') { + this.logger.trace(`Skipping branch protection for "${repository.owner}/${repository.repo}" due to missing repository write permission.`); + continue; + } + + // Get repository rulesets + const branchProtectionRules: BranchProtectionRule[] = []; + const repositoryRulesets = await this.getRepositoryRulesets(repository.owner, repository.repo, silent); + + for (const ruleset of repositoryRulesets) { + branchProtectionRules.push({ + include: (ruleset.conditions.refName?.include ?? []).map(r => this.parseRulesetRefName(repositoryDetails, r)), + exclude: (ruleset.conditions.refName?.exclude ?? []).map(r => this.parseRulesetRefName(repositoryDetails, r)) + }); + } + + branchProtection.push({ remote: remote.name, rules: branchProtectionRules }); + } + + this.branchProtection = branchProtection; + this._onDidChangeBranchProtection.fire(this.repository.rootUri); + + // Save branch protection to global state + await this.globalState.update(this.globalStateKey, branchProtection); + this.logger.trace(`Branch protection for "${this.repository.rootUri.toString()}": ${JSON.stringify(branchProtection)}.`); + } catch (err) { + this.logger.warn(`Failed to update repository branch protection: ${err.message}`); + + if (err instanceof AuthenticationError) { + // A GitHub authentication session could be missing if the user has not yet + // signed in with their GitHub account or they have signed out. In this case + // we have to clear the branch protection information. + this.branchProtection = branchProtection; + this._onDidChangeBranchProtection.fire(this.repository.rootUri); + + await this.globalState.update(this.globalStateKey, undefined); + } + } + } + + private parseRulesetRefName(repository: GitHubRepository, refName: string): string { + if (refName.startsWith('refs/heads/')) { + return refName.substring(11); + } + + switch (refName) { + case '~ALL': + return '**/*'; + case '~DEFAULT_BRANCH': + return repository.defaultBranchRef!.name; + default: + return refName; + } + } +} diff --git a/extensions/github/src/canonicalUriProvider.ts b/extensions/github/src/canonicalUriProvider.ts new file mode 100644 index 0000000000..577ffdb115 --- /dev/null +++ b/extensions/github/src/canonicalUriProvider.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, CanonicalUriProvider, CanonicalUriRequestOptions, Disposable, ProviderResult, Uri, workspace } from 'vscode'; +import { API } from './typings/git'; + +const SUPPORTED_SCHEMES = ['ssh', 'https', 'file']; + +export class GitHubCanonicalUriProvider implements CanonicalUriProvider { + + private disposables: Disposable[] = []; + constructor(private gitApi: API) { + this.disposables.push(...SUPPORTED_SCHEMES.map((scheme) => workspace.registerCanonicalUriProvider(scheme, this))); + } + + dispose() { this.disposables.forEach((disposable) => disposable.dispose()); } + + provideCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, _token: CancellationToken): ProviderResult { + if (options.targetScheme !== 'https') { + return; + } + + switch (uri.scheme) { + case 'file': { + const repository = this.gitApi.getRepository(uri); + const remote = repository?.state.remotes.find((remote) => remote.name === repository.state.HEAD?.remote)?.pushUrl?.replace(/^(git@[^\/:]+)(:)/i, 'ssh://$1/'); + if (remote) { + return toHttpsGitHubRemote(uri); + } + } + default: + return toHttpsGitHubRemote(uri); + } + } +} + +function toHttpsGitHubRemote(uri: Uri) { + if (uri.scheme === 'ssh' && uri.authority === 'git@github.com') { + // if this is a git@github.com URI, return the HTTPS equivalent + const [owner, repo] = (uri.path.endsWith('.git') ? uri.path.slice(0, -4) : uri.path).split('/').filter((segment) => segment.length > 0); + return Uri.parse(`https://github.com/${owner}/${repo}`); + } + if (uri.scheme === 'https' && uri.authority === 'github.com') { + return uri; + } + return undefined; +} diff --git a/extensions/github/src/commands.ts b/extensions/github/src/commands.ts index bd7b0a48d4..f91f747e35 100644 --- a/extensions/github/src/commands.ts +++ b/extensions/github/src/commands.ts @@ -7,15 +7,11 @@ import * as vscode from 'vscode'; import { API as GitAPI } from './typings/git'; import { publishRepository } from './publish'; import { DisposableStore } from './util'; -import { getPermalink } from './links'; +import { LinkContext, getLink, getVscodeDevHost } from './links'; -function getVscodeDevHost(): string { - return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`; -} - -async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) { +async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean, context: LinkContext, includeRange = true) { try { - const permalink = getPermalink(gitAPI, useSelection, getVscodeDevHost()); + const permalink = getLink(gitAPI, useSelection, getVscodeDevHost(), 'headlink', context, includeRange); if (permalink) { return vscode.env.clipboard.writeText(permalink); } @@ -26,8 +22,8 @@ async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) { async function openVscodeDevLink(gitAPI: GitAPI): Promise { try { - const permalink = getPermalink(gitAPI, true, getVscodeDevHost()); - return permalink ? vscode.Uri.parse(permalink) : undefined; + const headlink = getLink(gitAPI, true, getVscodeDevHost(), 'headlink'); + return headlink ? vscode.Uri.parse(headlink) : undefined; } catch (err) { vscode.window.showErrorMessage(err.message); return undefined; @@ -45,12 +41,16 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable { } })); - disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async () => { - return copyVscodeDevLink(gitAPI, true); + disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async (context: LinkContext) => { + return copyVscodeDevLink(gitAPI, true, context); })); - disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkFile', async () => { - return copyVscodeDevLink(gitAPI, false); + disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkFile', async (context: LinkContext) => { + return copyVscodeDevLink(gitAPI, false, context); + })); + + disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkWithoutRange', async (context: LinkContext) => { + return copyVscodeDevLink(gitAPI, true, context, false); })); disposables.add(vscode.commands.registerCommand('github.openOnVscodeDev', async () => { diff --git a/extensions/github/src/extension.ts b/extensions/github/src/extension.ts index cdb8e6c299..d0f745877c 100644 --- a/extensions/github/src/extension.ts +++ b/extensions/github/src/extension.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, Disposable, ExtensionContext, extensions } from 'vscode'; +import { commands, Disposable, ExtensionContext, extensions, l10n, LogLevel, LogOutputChannel, window } from 'vscode'; import { GithubRemoteSourceProvider } from './remoteSourceProvider'; import { API, GitExtension } from './typings/git'; import { registerCommands } from './commands'; @@ -12,10 +12,25 @@ import { DisposableStore, repositoryHasGitHubRemote } from './util'; import { GithubPushErrorHandler } from './pushErrorHandler'; import { GitBaseExtension } from './typings/git-base'; import { GithubRemoteSourcePublisher } from './remoteSourcePublisher'; +import { GithubBranchProtectionProviderManager } from './branchProtection'; +import { GitHubCanonicalUriProvider } from './canonicalUriProvider'; +import { VscodeDevShareProvider } from './shareProviders'; export function activate(context: ExtensionContext): void { - context.subscriptions.push(initializeGitBaseExtension()); - context.subscriptions.push(initializeGitExtension()); + const disposables: Disposable[] = []; + context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose())); + + const logger = window.createOutputChannel('GitHub', { log: true }); + disposables.push(logger); + + const onDidChangeLogLevel = (logLevel: LogLevel) => { + logger.appendLine(l10n.t('Log level: {0}', LogLevel[logLevel])); + }; + disposables.push(logger.onDidChangeLogLevel(onDidChangeLogLevel)); + onDidChangeLogLevel(logger.logLevel); + + disposables.push(initializeGitBaseExtension()); + disposables.push(initializeGitExtension(context, logger)); } function initializeGitBaseExtension(): Disposable { @@ -63,7 +78,7 @@ function setGitHubContext(gitAPI: API, disposables: DisposableStore) { } } -function initializeGitExtension(): Disposable { +function initializeGitExtension(context: ExtensionContext, logger: LogOutputChannel): Disposable { const disposables = new DisposableStore(); let gitExtension = extensions.getExtension('vscode.git'); @@ -77,8 +92,11 @@ function initializeGitExtension(): Disposable { disposables.add(registerCommands(gitAPI)); disposables.add(new GithubCredentialProviderManager(gitAPI)); + disposables.add(new GithubBranchProtectionProviderManager(gitAPI, context.globalState, logger)); disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler())); disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI))); + disposables.add(new GitHubCanonicalUriProvider(gitAPI)); + disposables.add(new VscodeDevShareProvider(gitAPI)); setGitHubContext(gitAPI, disposables); commands.executeCommand('setContext', 'git-base.gitEnabled', true); diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts index 51e88652f4..60892452d4 100644 --- a/extensions/github/src/links.ts +++ b/extensions/github/src/links.ts @@ -40,22 +40,40 @@ interface INotebookPosition { range: vscode.Range | undefined; } -function getFileAndPosition(): IFilePosition | INotebookPosition | undefined { - let uri: vscode.Uri | undefined; - let range: vscode.Range | undefined; - if (vscode.window.activeTextEditor) { - uri = vscode.window.activeTextEditor.document.uri; +interface EditorLineNumberContext { + uri: vscode.Uri; + lineNumber: number; +} +export type LinkContext = vscode.Uri | EditorLineNumberContext | undefined; +function extractContext(context: LinkContext): { fileUri: vscode.Uri | undefined; lineNumber: number | undefined } { + if (context instanceof vscode.Uri) { + return { fileUri: context, lineNumber: undefined }; + } else if (context !== undefined && 'lineNumber' in context && 'uri' in context) { + return { fileUri: context.uri, lineNumber: context.lineNumber }; + } else { + return { fileUri: undefined, lineNumber: undefined }; + } +} + +function getFileAndPosition(context: LinkContext): IFilePosition | INotebookPosition | undefined { + let range: vscode.Range | undefined; + + const { fileUri, lineNumber } = extractContext(context); + const uri = fileUri ?? vscode.window.activeTextEditor?.document.uri; + + if (uri) { if (uri.scheme === 'vscode-notebook-cell' && vscode.window.activeNotebookEditor?.notebook.uri.fsPath === uri.fsPath) { // if the active editor is a notebook editor and the focus is inside any a cell text editor // generate deep link for text selection for the notebook cell. const cell = vscode.window.activeNotebookEditor.notebook.getCells().find(cell => cell.document.uri.fragment === uri?.fragment); const cellIndex = cell?.index ?? vscode.window.activeNotebookEditor.selection.start; - const range = cell !== undefined ? vscode.window.activeTextEditor.selection : undefined; + + const range = getRangeOrSelection(lineNumber); return { type: LinkType.Notebook, uri, cellIndex, range }; } else { // the active editor is a text editor - range = vscode.window.activeTextEditor.selection; + range = getRangeOrSelection(lineNumber); return { type: LinkType.File, uri, range }; } } @@ -68,7 +86,13 @@ function getFileAndPosition(): IFilePosition | INotebookPosition | undefined { return undefined; } -function rangeString(range: vscode.Range | undefined) { +function getRangeOrSelection(lineNumber: number | undefined) { + return lineNumber !== undefined && (!vscode.window.activeTextEditor || vscode.window.activeTextEditor.selection.isEmpty || !vscode.window.activeTextEditor.selection.contains(new vscode.Position(lineNumber - 1, 0))) + ? new vscode.Range(lineNumber - 1, 0, lineNumber - 1, 1) + : vscode.window.activeTextEditor?.selection; +} + +export function rangeString(range: vscode.Range | undefined) { if (!range) { return ''; } @@ -95,9 +119,19 @@ export function notebookCellRangeString(index: number | undefined, range: vscode return hash; } -export function getPermalink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string): string | undefined { +export function encodeURIComponentExceptSlashes(path: string) { + // There may be special characters like # and whitespace in the path. + // These characters are not escaped by encodeURI(), so it is not sufficient to + // feed the full URI to encodeURI(). + // Additonally, if we feed the full path into encodeURIComponent(), + // this will also encode the path separators, leading to an invalid path. + // Therefore, split on the path separator and encode each segment individually. + return path.split('/').map((segment) => encodeURIComponent(segment)).join('/'); +} + +export function getLink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string, linkType: 'permalink' | 'headlink' = 'permalink', context?: LinkContext, useRange?: boolean): string | undefined { hostPrefix = hostPrefix ?? 'https://github.com'; - const fileAndPosition = getFileAndPosition(); + const fileAndPosition = getFileAndPosition(context); if (!fileAndPosition) { return; } @@ -125,11 +159,26 @@ export function getPermalink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: return; } - const commitHash = (gitRepo.state.HEAD?.ahead === 0) ? `/blob/${gitRepo.state.HEAD?.commit}` : ''; + const blobSegment = gitRepo.state.HEAD ? (`/blob/${linkType === 'headlink' && gitRepo.state.HEAD.name ? encodeURIComponentExceptSlashes(gitRepo.state.HEAD.name) : gitRepo.state.HEAD?.commit}`) : ''; + const encodedFilePath = encodeURIComponentExceptSlashes(uri.path.substring(gitRepo.rootUri.path.length)); const fileSegments = fileAndPosition.type === LinkType.File - ? (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${rangeString(fileAndPosition.range)}` : '') - : (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range)}` : ''); + ? (useSelection ? `${encodedFilePath}${useRange ? rangeString(fileAndPosition.range) : ''}` : '') + : (useSelection ? `${encodedFilePath}${useRange ? notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range) : ''}` : ''); - return `${hostPrefix}/${repo.owner}/${repo.repo}${commitHash + return `${hostPrefix}/${repo.owner}/${repo.repo}${blobSegment }${fileSegments}`; } + +export function getBranchLink(url: string, branch: string, hostPrefix: string = 'https://github.com') { + const repo = getRepositoryFromUrl(url); + if (!repo) { + throw new Error('Invalid repository URL provided'); + } + + branch = encodeURIComponentExceptSlashes(branch); + return `${hostPrefix}/${repo.owner}/${repo.repo}/tree/${branch}`; +} + +export function getVscodeDevHost(): string { + return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`; +} diff --git a/extensions/github/src/publish.ts b/extensions/github/src/publish.ts index 51a12f05c3..d43e6e9a24 100644 --- a/extensions/github/src/publish.ts +++ b/extensions/github/src/publish.ts @@ -4,14 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; 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(); +import { isInCodespaces } from './pushErrorHandler'; function sanitizeRepositoryName(value: string): string { return value.trim().replace(/[^a-z0-9_.]/ig, '-'); @@ -40,7 +38,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) folder = vscode.workspace.workspaceFolders[0].uri; } else { const picks = vscode.workspace.workspaceFolders.map(folder => ({ label: folder.name, folder })); - const placeHolder = localize('pick folder', "Pick a folder to publish to GitHub"); + const placeHolder = vscode.l10n.t('Pick a folder to publish to GitHub'); const pick = await vscode.window.showQuickPick(picks, { placeHolder }); if (!pick) { @@ -129,7 +127,7 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) if (shouldGenerateGitignore) { quickpick = vscode.window.createQuickPick(); - quickpick.placeholder = localize('ignore', "Select which files should be included in the repository."); + quickpick.placeholder = vscode.l10n.t('Select which files should be included in the repository.'); quickpick.canSelectMany = true; quickpick.show(); @@ -170,37 +168,45 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) const githubRepository = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, cancellable: false, title: 'Publish to GitHub' }, async progress => { progress.report({ message: isPrivate - ? localize('publishing_private', "Publishing to a private GitHub repository") - : localize('publishing_public', "Publishing to a public GitHub repository"), + ? vscode.l10n.t('Publishing to a private GitHub repository') + : vscode.l10n.t('Publishing to a public GitHub repository'), increment: 25 }); - const res = await octokit.repos.createForAuthenticatedUser({ - name: repo!, - private: isPrivate - }); + type CreateRepositoryResponseData = Awaited>['data']; + let createdGithubRepository: CreateRepositoryResponseData | undefined = undefined; - const createdGithubRepository = res.data; - - progress.report({ message: localize('publishing_firstcommit', "Creating first commit"), increment: 25 }); - - if (!repository) { - repository = await gitAPI.init(folder) || undefined; - - if (!repository) { - return; - } - - await repository.commit('first commit', { all: true }); + if (isInCodespaces()) { + createdGithubRepository = await vscode.commands.executeCommand('github.codespaces.publish', { name: repo!, isPrivate }); + } else { + const res = await octokit.repos.createForAuthenticatedUser({ + name: repo!, + private: isPrivate + }); + createdGithubRepository = res.data; } - progress.report({ message: localize('publishing_uploading', "Uploading files"), increment: 25 }); + if (createdGithubRepository) { + progress.report({ message: vscode.l10n.t('Creating first commit'), increment: 25 }); - const branch = await repository.getBranch('HEAD'); - const protocol = vscode.workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); - const remoteUrl = protocol === 'https' ? createdGithubRepository.clone_url : createdGithubRepository.ssh_url; - await repository.addRemote('origin', remoteUrl); - await repository.push('origin', branch.name, true); + if (!repository) { + repository = await gitAPI.init(folder, { defaultBranch: createdGithubRepository.default_branch }) || undefined; + + if (!repository) { + return; + } + + await repository.commit('first commit', { all: true, postCommitCommand: null }); + } + + progress.report({ message: vscode.l10n.t('Uploading files'), increment: 25 }); + + const branch = await repository.getBranch('HEAD'); + const protocol = vscode.workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); + const remoteUrl = protocol === 'https' ? createdGithubRepository.clone_url : createdGithubRepository.ssh_url; + await repository.addRemote('origin', remoteUrl); + await repository.push('origin', branch.name, true); + } return createdGithubRepository; }); @@ -209,8 +215,8 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) return; } - const openOnGitHub = localize('openingithub', "Open on GitHub"); - vscode.window.showInformationMessage(localize('publishing_done', "Successfully published the '{0}' repository to GitHub.", `${owner}/${repo}`), openOnGitHub).then(action => { + const openOnGitHub = vscode.l10n.t('Open on GitHub'); + vscode.window.showInformationMessage(vscode.l10n.t('Successfully published the "{0}" repository to GitHub.', `${owner}/${repo}`), openOnGitHub).then(action => { if (action === openOnGitHub) { 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 435abf3d62..46e4d80b07 100644 --- a/extensions/github/src/pushErrorHandler.ts +++ b/extensions/github/src/pushErrorHandler.ts @@ -4,13 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { TextDecoder } from 'util'; -import { commands, env, ProgressLocation, Uri, window, workspace, QuickPickOptions, FileType } from 'vscode'; -import * as nls from 'vscode-nls'; +import { commands, env, ProgressLocation, Uri, window, workspace, QuickPickOptions, FileType, l10n } from 'vscode'; import { getOctokit } from './auth'; import { GitErrorCodes, PushErrorHandler, Remote, Repository } from './typings/git'; -import path = require('path'); - -const localize = nls.loadMessageBundle(); +import * as path from 'path'; type Awaited = T extends PromiseLike ? Awaited : T; @@ -19,11 +16,11 @@ export function isInCodespaces(): boolean { } async function handlePushError(repository: Repository, remote: Remote, refspec: string, owner: string, repo: string): Promise { - const yes = localize('create a fork', "Create Fork"); - const no = localize('no', "No"); - const askFork = localize('fork', "You don't have permissions to push to '{0}/{1}' on GitHub. Would you like to create a fork and push to it instead?", owner, repo); + const yes = l10n.t('Create Fork'); + const no = l10n.t('No'); + const askFork = l10n.t('You don\'t have permissions to push to "{0}/{1}" on GitHub. Would you like to create a fork and push to it instead?', owner, repo); - const answer = await window.showInformationMessage(askFork, yes, no); + const answer = await window.showWarningMessage(askFork, { modal: true }, yes, no); if (answer !== yes) { return; } @@ -32,8 +29,8 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: const localName = match ? match[1] : refspec; let remoteName = match ? match[2] : refspec; - const [octokit, ghRepository] = await window.withProgress({ location: ProgressLocation.Notification, cancellable: false, title: localize('create fork', 'Create GitHub fork') }, async progress => { - progress.report({ message: localize('forking', "Forking '{0}/{1}'...", owner, repo), increment: 33 }); + const [octokit, ghRepository] = await window.withProgress({ location: ProgressLocation.Notification, cancellable: false, title: l10n.t('Create GitHub fork') }, async progress => { + progress.report({ message: l10n.t('Forking "{0}/{1}"...', owner, repo), increment: 33 }); const octokit = await getOctokit(); @@ -68,7 +65,7 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: throw ex; } - progress.report({ message: localize('forking_pushing', "Pushing changes..."), increment: 33 }); + progress.report({ message: l10n.t('Pushing changes...'), increment: 33 }); // Issue: what if there's already an `upstream` repo? await repository.renameRemote(remote.name, 'upstream'); @@ -92,14 +89,14 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: // yield (async () => { - const openOnGitHub = localize('openingithub', "Open on GitHub"); - const createPR = localize('createpr', "Create PR"); - const action = await window.showInformationMessage(localize('forking_done', "The fork '{0}' was successfully created on GitHub.", ghRepository.full_name), openOnGitHub, createPR); + const openOnGitHub = l10n.t('Open on GitHub'); + const createPR = l10n.t('Create PR'); + const action = await window.showInformationMessage(l10n.t('The fork "{0}" was successfully created on GitHub.', ghRepository.full_name), openOnGitHub, createPR); if (action === openOnGitHub) { await commands.executeCommand('vscode.open', Uri.parse(ghRepository.html_url)); } else if (action === createPR) { - const pr = await window.withProgress({ location: ProgressLocation.Notification, cancellable: false, title: localize('createghpr', "Creating GitHub Pull Request...") }, async _ => { + const pr = await window.withProgress({ location: ProgressLocation.Notification, cancellable: false, title: l10n.t('Creating GitHub Pull Request...') }, async _ => { let title = `Update ${remoteName}`; const head = repository.state.HEAD?.name; @@ -138,8 +135,8 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: return pr; }); - const openPR = localize('openpr', "Open PR"); - const action = await window.showInformationMessage(localize('donepr', "The PR '{0}/{1}#{2}' was successfully created on GitHub.", owner, repo, pr.number), openPR); + const openPR = l10n.t('Open PR'); + const action = await window.showInformationMessage(l10n.t('The PR "{0}/{1}#{2}" was successfully created on GitHub.', owner, repo, pr.number), openPR); if (action === openPR) { await commands.executeCommand('vscode.open', Uri.parse(pr.html_url)); @@ -196,14 +193,14 @@ export async function pickPullRequestTemplate(repositoryRootUri: Uri, templates: const quickPickItemFromUri = (x: Uri) => ({ label: path.relative(repositoryRootUri.path, x.path), template: x }); const quickPickItems = [ { - label: localize('no pr template', "No template"), + label: l10n.t('No template'), picked: true, template: undefined, }, ...templates.map(quickPickItemFromUri) ]; const quickPickOptions: QuickPickOptions = { - placeHolder: localize('select pr template', "Select the Pull Request template"), + placeHolder: l10n.t('Select the Pull Request template'), ignoreFocusOut: true }; const pickedTemplate = await window.showQuickPick(quickPickItems, quickPickOptions); diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index a7e8d231b6..4a75f9fccd 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -3,11 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workspace } from 'vscode'; -import { RemoteSourceProvider, RemoteSource } from './typings/git-base'; +import { Uri, env, l10n, workspace } from 'vscode'; +import { RemoteSourceProvider, RemoteSource, RemoteSourceAction } from './typings/git-base'; import { getOctokit } from './auth'; import { Octokit } from '@octokit/rest'; import { getRepositoryFromQuery, getRepositoryFromUrl } from './util'; +import { getBranchLink, getVscodeDevHost } from './links'; function asRemoteSource(raw: any): RemoteSource { const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); @@ -112,4 +113,27 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider { return branches.sort((a, b) => a === defaultBranch ? -1 : b === defaultBranch ? 1 : 0); } + + async getRemoteSourceActions(url: string): Promise { + const repository = getRepositoryFromUrl(url); + if (!repository) { + return []; + } + + return [{ + label: l10n.t('Open on GitHub'), + icon: 'github', + run(branch: string) { + const link = getBranchLink(url, branch); + env.openExternal(Uri.parse(link)); + } + }, { + label: l10n.t('Checkout on vscode.dev'), + icon: 'globe', + run(branch: string) { + const link = getBranchLink(url, branch, getVscodeDevHost()); + env.openExternal(Uri.parse(link)); + } + }]; + } } diff --git a/extensions/github/src/shareProviders.ts b/extensions/github/src/shareProviders.ts new file mode 100644 index 0000000000..8e1a806032 --- /dev/null +++ b/extensions/github/src/shareProviders.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { API } from './typings/git'; +import { getRepositoryFromUrl, repositoryHasGitHubRemote } from './util'; +import { encodeURIComponentExceptSlashes, getRepositoryForFile, notebookCellRangeString, rangeString } from './links'; + +export class VscodeDevShareProvider implements vscode.ShareProvider, vscode.Disposable { + readonly id: string = 'copyVscodeDevLink'; + readonly label: string = vscode.l10n.t('Copy vscode.dev Link'); + readonly priority: number = 10; + + + private _hasGitHubRepositories: boolean = false; + private set hasGitHubRepositories(value: boolean) { + vscode.commands.executeCommand('setContext', 'github.hasGitHubRepo', value); + this._hasGitHubRepositories = value; + this.ensureShareProviderRegistration(); + } + + private shareProviderRegistration: vscode.Disposable | undefined; + private disposables: vscode.Disposable[] = []; + + constructor(private readonly gitAPI: API) { + this.initializeGitHubRepoContext(); + } + + dispose() { + this.disposables.forEach(d => d.dispose()); + } + + private initializeGitHubRepoContext() { + if (this.gitAPI.repositories.find(repo => repositoryHasGitHubRemote(repo))) { + this.hasGitHubRepositories = true; + vscode.commands.executeCommand('setContext', 'github.hasGitHubRepo', true); + } else { + this.disposables.push(this.gitAPI.onDidOpenRepository(async e => { + await e.status(); + if (repositoryHasGitHubRemote(e)) { + vscode.commands.executeCommand('setContext', 'github.hasGitHubRepo', true); + this.hasGitHubRepositories = true; + } + })); + } + this.disposables.push(this.gitAPI.onDidCloseRepository(() => { + if (!this.gitAPI.repositories.find(repo => repositoryHasGitHubRemote(repo))) { + this.hasGitHubRepositories = false; + } + })); + } + + private ensureShareProviderRegistration() { + if (vscode.env.appHost !== 'codespaces' && !this.shareProviderRegistration && this._hasGitHubRepositories) { + const shareProviderRegistration = vscode.window.registerShareProvider({ scheme: 'file' }, this); + this.shareProviderRegistration = shareProviderRegistration; + this.disposables.push(shareProviderRegistration); + } else if (this.shareProviderRegistration && !this._hasGitHubRepositories) { + this.shareProviderRegistration.dispose(); + this.shareProviderRegistration = undefined; + } + } + + provideShare(item: vscode.ShareableItem, _token: vscode.CancellationToken): vscode.ProviderResult { + const repository = getRepositoryForFile(this.gitAPI, item.resourceUri); + if (!repository) { + return; + } + + let repo: { owner: string; repo: string } | undefined; + repository.state.remotes.find(remote => { + if (remote.fetchUrl) { + const foundRepo = getRepositoryFromUrl(remote.fetchUrl); + if (foundRepo && (remote.name === repository.state.HEAD?.upstream?.remote)) { + repo = foundRepo; + return; + } else if (foundRepo && !repo) { + repo = foundRepo; + } + } + return; + }); + + if (!repo) { + return; + } + + const blobSegment = repository?.state.HEAD?.name ? encodeURIComponentExceptSlashes(repository.state.HEAD?.name) : repository?.state.HEAD?.commit; + const filepathSegment = encodeURIComponentExceptSlashes(item.resourceUri.path.substring(repository?.rootUri.path.length)); + const rangeSegment = getRangeSegment(item); + return vscode.Uri.parse(`${this.getVscodeDevHost()}/${repo.owner}/${repo.repo}/blob/${blobSegment}${filepathSegment}${rangeSegment}${rangeSegment}`); + + } + + private getVscodeDevHost(): string { + return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`; + } +} + +function getRangeSegment(item: vscode.ShareableItem) { + if (item.resourceUri.scheme === 'vscode-notebook-cell') { + const notebookEditor = vscode.window.visibleNotebookEditors.find(editor => editor.notebook.uri.fsPath === item.resourceUri.fsPath); + const cell = notebookEditor?.notebook.getCells().find(cell => cell.document.uri.fragment === item.resourceUri?.fragment); + const cellIndex = cell?.index ?? notebookEditor?.selection.start; + return notebookCellRangeString(cellIndex, item.selection); + } + + return rangeString(item.selection); +} diff --git a/extensions/github/src/test/index.ts b/extensions/github/src/test/index.ts index 452c487755..17da6adaf9 100644 --- a/extensions/github/src/test/index.ts +++ b/extensions/github/src/test/index.ts @@ -3,12 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const path = require('path'); -const testRunner = require('../../../../test/integration/electron/testrunner'); +import * as path from 'path'; +import * as testRunner from '../../../../test/integration/electron/testrunner'; const suite = 'Github Tests'; -const options: any = { +const options: import('mocha').MochaOptions = { ui: 'tdd', color: true, timeout: 60000 diff --git a/extensions/github/src/typings/git-base.d.ts b/extensions/github/src/typings/git-base.d.ts index b003b3dfc1..74f3e7ff55 100644 --- a/extensions/github/src/typings/git-base.d.ts +++ b/extensions/github/src/typings/git-base.d.ts @@ -44,6 +44,15 @@ export interface PickRemoteSourceResult { readonly branch?: string; } +export interface RemoteSourceAction { + readonly label: string; + /** + * Codicon name + */ + readonly icon: string; + run(branch: string): void; +} + export interface RemoteSource { readonly name: string; readonly description?: string; @@ -70,6 +79,7 @@ export interface RemoteSourceProvider { readonly supportsQuery?: boolean; getBranches?(url: string): ProviderResult; + getRemoteSourceActions?(url: string): ProviderResult; getRecentRemoteSources?(query?: string): ProviderResult; getRemoteSources(query?: string): ProviderResult; } diff --git a/extensions/github/src/typings/git.d.ts b/extensions/github/src/typings/git.d.ts index 5f78baf490..c1fe85d9ba 100644 --- a/extensions/github/src/typings/git.d.ts +++ b/extensions/github/src/typings/git.d.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Uri, Event, Disposable, ProviderResult } from 'vscode'; +import { Uri, Event, Disposable, ProviderResult, Command } from 'vscode'; export { ProviderResult } from 'vscode'; export interface Git { @@ -14,6 +14,11 @@ export interface InputBox { value: string; } +export const enum ForcePushMode { + Force, + ForceWithLease +} + export const enum RefType { Head, RemoteHead, @@ -73,6 +78,7 @@ export const enum Status { UNTRACKED, IGNORED, INTENT_TO_ADD, + INTENT_TO_RENAME, ADDED_BY_US, ADDED_BY_THEM, @@ -131,6 +137,28 @@ export interface CommitOptions { signCommit?: boolean; empty?: boolean; noVerify?: boolean; + requireUserConfig?: boolean; + useEditor?: boolean; + verbose?: boolean; + /** + * string - execute the specified command after the commit operation + * undefined - execute the command specified in git.postCommitCommand + * after the commit operation + * null - do not execute any command after the commit operation + */ + postCommitCommand?: string | null; +} + +export interface FetchOptions { + remote?: string; + ref?: string; + all?: boolean; + prune?: boolean; + depth?: number; +} + +export interface InitOptions { + defaultBranch?: string; } export interface BranchQuery { @@ -158,6 +186,8 @@ export interface Repository { show(ref: string, path: string): Promise; getCommit(ref: string): Promise; + add(paths: string[]): Promise; + revert(paths: string[]): Promise; clean(paths: string[]): Promise; apply(patch: string, reverse?: boolean): Promise; @@ -184,6 +214,9 @@ export interface Repository { getMergeBase(ref1: string, ref2: string): Promise; + tag(name: string, upstream: string): Promise; + deleteTag(name: string): Promise; + status(): Promise; checkout(treeish: string): Promise; @@ -191,9 +224,10 @@ export interface Repository { removeRemote(name: string): Promise; renameRemote(name: string, newName: string): Promise; + fetch(options?: FetchOptions): Promise; fetch(remote?: string, ref?: string, depth?: number): Promise; pull(unshallow?: boolean): Promise; - push(remoteName?: string, branchName?: string, setUpstream?: boolean): Promise; + push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise; blame(path: string): Promise; log(options?: LogOptions): Promise; @@ -231,15 +265,40 @@ export interface CredentialsProvider { getCredentials(host: Uri): ProviderResult; } +export interface PostCommitCommandsProvider { + getCommands(repository: Repository): Command[]; +} + export interface PushErrorHandler { handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; } +export interface BranchProtection { + readonly remote: string; + readonly rules: BranchProtectionRule[]; +} + +export interface BranchProtectionRule { + readonly include?: string[]; + readonly exclude?: string[]; +} + +export interface BranchProtectionProvider { + onDidChangeBranchProtection: Event; + provideBranchProtection(): BranchProtection[]; +} + export type APIState = 'uninitialized' | 'initialized'; +export interface PublishEvent { + repository: Repository; + branch?: string; +} + export interface API { readonly state: APIState; readonly onDidChangeState: Event; + readonly onDidPublish: Event; readonly git: Git; readonly repositories: Repository[]; readonly onDidOpenRepository: Event; @@ -247,12 +306,15 @@ export interface API { toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; - init(root: Uri): Promise; + init(root: Uri, options?: InitOptions): Promise; + openRepository(root: Uri): Promise registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; registerCredentialsProvider(provider: CredentialsProvider): Disposable; + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; registerPushErrorHandler(handler: PushErrorHandler): Disposable; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; } export interface GitExtension { @@ -263,7 +325,7 @@ export interface GitExtension { /** * Returns a specific API version. * - * Throws error if git extension is disabled. You can listed to the + * Throws error if git extension is disabled. You can listen to the * [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event * to know when the extension becomes enabled/disabled. * @@ -309,4 +371,5 @@ export const enum GitErrorCodes { PatchDoesNotApply = 'PatchDoesNotApply', NoPathFound = 'NoPathFound', UnknownPath = 'UnknownPath', + EmptyCommitMessage = 'EmptyCommitMessage' } diff --git a/extensions/github/src/typings/vscode.proposed.canonicalUriProvider.d.ts b/extensions/github/src/typings/vscode.proposed.canonicalUriProvider.d.ts new file mode 100644 index 0000000000..d52dfa0ee8 --- /dev/null +++ b/extensions/github/src/typings/vscode.proposed.canonicalUriProvider.d.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/180582 + + export namespace workspace { + /** + * + * @param scheme The URI scheme that this provider can provide canonical URIs for. + * A canonical URI represents the conversion of a resource's alias into a source of truth URI. + * Multiple aliases may convert to the same source of truth URI. + * @param provider A provider which can convert URIs of scheme @param scheme to + * a canonical URI which is stable across machines. + */ + export function registerCanonicalUriProvider(scheme: string, provider: CanonicalUriProvider): Disposable; + + /** + * + * @param uri The URI to provide a canonical URI for. + * @param token A cancellation token for the request. + */ + export function getCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, token: CancellationToken): ProviderResult; + } + + export interface CanonicalUriProvider { + /** + * + * @param uri The URI to provide a canonical URI for. + * @param options Options that the provider should honor in the URI it returns. + * @param token A cancellation token for the request. + * @returns The canonical URI for the requested URI or undefined if no canonical URI can be provided. + */ + provideCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, token: CancellationToken): ProviderResult; + } + + export interface CanonicalUriRequestOptions { + /** + * + * The desired scheme of the canonical URI. + */ + targetScheme: string; + } +} diff --git a/extensions/github/src/typings/vscode.proposed.shareProvider.d.ts b/extensions/github/src/typings/vscode.proposed.shareProvider.d.ts new file mode 100644 index 0000000000..4dcb613007 --- /dev/null +++ b/extensions/github/src/typings/vscode.proposed.shareProvider.d.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/176316 + +declare module 'vscode' { + export interface TreeItem { + shareableItem?: ShareableItem; + } + + export interface ShareableItem { + resourceUri: Uri; + selection?: Range; + } + + export interface ShareProvider { + readonly id: string; + readonly label: string; + readonly priority: number; + + provideShare(item: ShareableItem, token: CancellationToken): ProviderResult; + } + + export namespace window { + export function registerShareProvider(selector: DocumentSelector, provider: ShareProvider): Disposable; + } +} diff --git a/extensions/github/yarn.lock b/extensions/github/yarn.lock index 9933736b08..6d2eafc2bf 100644 --- a/extensions/github/yarn.lock +++ b/extensions/github/yarn.lock @@ -2,129 +2,169 @@ # yarn lockfile v1 -"@octokit/auth-token@^2.4.0": - version "2.4.2" - resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.2.tgz#10d0ae979b100fa6b72fa0e8e63e27e6d0dbff8a" - integrity sha512-jE/lE/IKIz2v1+/P0u4fJqv0kYwXOTujKemJMFr6FeopsxlIK3+wKDCJGnysg81XID5TgZQbIfuJ5J0lnTiuyQ== +"@octokit/auth-token@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-3.0.1.tgz#88bc2baf5d706cb258474e722a720a8365dff2ec" + integrity sha512-/USkK4cioY209wXRpund6HZzHo9GmjakpV9ycOkpMcMxMk7QVcVFVyCMtzvXYiHsB2crgDgrtNYSELYFBXhhaA== dependencies: - "@octokit/types" "^5.0.0" + "@octokit/types" "^7.0.0" -"@octokit/core@^3.0.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.1.1.tgz#1856745aa8fb154cf1544a2a1b82586c809c5e66" - integrity sha512-cQ2HGrtyNJ1IBxpTP1U5m/FkMAJvgw7d2j1q3c9P0XUuYilEgF6e4naTpsgm4iVcQeOnccZlw7XHRIUBy0ymcg== +"@octokit/core@^4.0.0": + version "4.0.5" + resolved "https://registry.yarnpkg.com/@octokit/core/-/core-4.0.5.tgz#589e68c0a35d2afdcd41dafceab072c2fbc6ab5f" + integrity sha512-4R3HeHTYVHCfzSAi0C6pbGXV8UDI5Rk+k3G7kLVNckswN9mvpOzW9oENfjfH3nEmzg8y3AmKmzs8Sg6pLCeOCA== dependencies: - "@octokit/auth-token" "^2.4.0" - "@octokit/graphql" "^4.3.1" - "@octokit/request" "^5.4.0" - "@octokit/types" "^5.0.0" - before-after-hook "^2.1.0" + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^7.0.0" + before-after-hook "^2.2.0" universal-user-agent "^6.0.0" -"@octokit/endpoint@^6.0.1": - version "6.0.4" - resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.4.tgz#da3eafdee1fabd6e5b6ca311efcba26f0dd99848" - integrity sha512-ZJHIsvsClEE+6LaZXskDvWIqD3Ao7+2gc66pRG5Ov4MQtMvCU9wGu1TItw9aGNmRuU9x3Fei1yb+uqGaQnm0nw== +"@octokit/endpoint@^7.0.0": + version "7.0.1" + resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-7.0.1.tgz#cb0d03e62e8762f3c80e52b025179de81899a823" + integrity sha512-/wTXAJwt0HzJ2IeE4kQXO+mBScfzyCkI0hMtkIaqyXd9zg76OpOfNQfHL9FlaxAV2RsNiOXZibVWloy8EexENg== dependencies: - "@octokit/types" "^5.0.0" - is-plain-object "^3.0.0" + "@octokit/types" "^7.0.0" + is-plain-object "^5.0.0" universal-user-agent "^6.0.0" -"@octokit/graphql@^4.3.1": - version "4.5.2" - resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.2.tgz#33021ebf94939cf47562823851ab11fe64392274" - integrity sha512-SpB/JGdB7bxRj8qowwfAXjMpICUYSJqRDj26MKJAryRQBqp/ZzARsaO2LEFWzDaps0FLQoPYVGppS0HQXkBhdg== +"@octokit/graphql-schema@14.4.0": + version "14.4.0" + resolved "https://registry.yarnpkg.com/@octokit/graphql-schema/-/graphql-schema-14.4.0.tgz#9336f64c3103a2e82ee3ce060c3ccf99d177d7f0" + integrity sha512-+O6/dsLlR6V9gv+t1lqsN+x73TLwyQWZpd3M8/eYnuny7VaznV9TAyUxf18tX8WBBS5IqtlLDk1nG+aSTPRZzQ== dependencies: - "@octokit/request" "^5.3.0" - "@octokit/types" "^5.0.0" + graphql "^16.0.0" + graphql-tag "^2.10.3" + +"@octokit/graphql@5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.5.tgz#a4cb3ea73f83b861893a6370ee82abb36e81afd2" + integrity sha512-Qwfvh3xdqKtIznjX9lz2D458r7dJPP8l6r4GQkIdWQouZwHQK0mVT88uwiU2bdTU2OtT1uOlKpRciUWldpG0yQ== + dependencies: + "@octokit/request" "^6.0.0" + "@octokit/types" "^9.0.0" universal-user-agent "^6.0.0" -"@octokit/plugin-paginate-rest@^2.2.0": - version "2.2.3" - resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.2.3.tgz#a6ad4377e7e7832fb4bdd9d421e600cb7640ac27" - integrity sha512-eKTs91wXnJH8Yicwa30jz6DF50kAh7vkcqCQ9D7/tvBAP5KKkg6I2nNof8Mp/65G0Arjsb4QcOJcIEQY+rK1Rg== +"@octokit/graphql@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-5.0.1.tgz#a06982514ad131fb6fbb9da968653b2233fade9b" + integrity sha512-sxmnewSwAixkP1TrLdE6yRG53eEhHhDTYUykUwdV9x8f91WcbhunIHk9x1PZLALdBZKRPUO2HRcm4kezZ79HoA== dependencies: - "@octokit/types" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/types" "^7.0.0" + universal-user-agent "^6.0.0" -"@octokit/plugin-request-log@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.0.tgz#eef87a431300f6148c39a7f75f8cfeb218b2547e" - integrity sha512-ywoxP68aOT3zHCLgWZgwUJatiENeHE7xJzYjfz8WI0goynp96wETBF+d95b8g/uL4QmS6owPVlaxiz3wyMAzcw== +"@octokit/openapi-types@^13.6.0": + version "13.6.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-13.6.0.tgz#381884008e23fd82fd444553f6b4dcd24a5c4a4d" + integrity sha512-bxftLwoZ2J6zsU1rzRvk0O32j7lVB0NWWn+P5CDHn9zPzytasR3hdAeXlTngRDkqv1LyEeuy5psVnDkmOSwrcQ== -"@octokit/plugin-rest-endpoint-methods@4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.1.0.tgz#338c568177c4d4d753f9525af88b29cd0f091734" - integrity sha512-zbRTjm+xplSNlixotTVMvLJe8aRogUXS+r37wZK5EjLsNYH4j02K5XLMOWyYaSS4AJEZtPmzCcOcui4VzVGq+A== +"@octokit/openapi-types@^17.1.0": + version "17.1.0" + resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-17.1.0.tgz#9a712b5bb9d644940d8a1f24115c798c317a64a5" + integrity sha512-rnI26BAITDZTo5vqFOmA7oX4xRd18rO+gcK4MiTpJmsRMxAw0JmevNjPsjpry1bb9SVNo56P/0kbiyXXa4QluA== + +"@octokit/plugin-paginate-rest@^4.0.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-4.2.0.tgz#41fc6ca312446a85a4275aca698b4d9c4c5e06ab" + integrity sha512-8otLCIK9esfmOCY14CBnG/xPqv0paf14rc+s9tHpbOpeFwrv5CnECKW1qdqMAT60ngAa9eB1bKQ+l2YCpi0HPQ== dependencies: - "@octokit/types" "^5.1.0" + "@octokit/types" "^7.2.0" + +"@octokit/plugin-request-log@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz#5e50ed7083a613816b1e4a28aeec5fb7f1462e85" + integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== + +"@octokit/plugin-rest-endpoint-methods@^6.0.0": + version "6.4.0" + resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-6.4.0.tgz#09584dd4e85fc4fe04ade45620b105af582c20ba" + integrity sha512-YP4eUqZ6vORy/eZOTdil1ZSrMt0kv7i/CVw+HhC2C0yJN+IqTc/rot957JQ7JfyeJD6HZOjLg6Jp1o9cPhI9KA== + dependencies: + "@octokit/types" "^7.2.0" deprecation "^2.3.1" -"@octokit/request-error@^2.0.0": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" - integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== +"@octokit/request-error@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-3.0.1.tgz#3fd747913c06ab2195e52004a521889dadb4b295" + integrity sha512-ym4Bp0HTP7F3VFssV88WD1ZyCIRoE8H35pXSKwLeMizcdZAYc/t6N9X9Yr9n6t3aG9IH75XDnZ6UeZph0vHMWQ== dependencies: - "@octokit/types" "^5.0.1" + "@octokit/types" "^7.0.0" deprecation "^2.0.0" once "^1.4.0" -"@octokit/request@^5.3.0", "@octokit/request@^5.4.0": - version "5.4.6" - resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.6.tgz#e8cc8d4cfc654d30428ea92aaa62168fd5ead7eb" - integrity sha512-9r8Sn4CvqFI9LDLHl9P17EZHwj3ehwQnTpTE+LEneb0VBBqSiI/VS4rWIBfBhDrDs/aIGEGZRSB0QWAck8u+2g== +"@octokit/request@^6.0.0": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@octokit/request/-/request-6.2.1.tgz#3ceeb22dab09a29595d96594b6720fc14495cf4e" + integrity sha512-gYKRCia3cpajRzDSU+3pt1q2OcuC6PK8PmFIyxZDWCzRXRSIBH8jXjFJ8ZceoygBIm0KsEUg4x1+XcYBz7dHPQ== dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.0.0" - "@octokit/types" "^5.0.0" - deprecation "^2.0.0" - is-plain-object "^3.0.0" - node-fetch "^2.3.0" - once "^1.4.0" + "@octokit/endpoint" "^7.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^7.0.0" + is-plain-object "^5.0.0" + node-fetch "^2.6.7" universal-user-agent "^6.0.0" -"@octokit/rest@^18.0.1": - version "18.0.1" - resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.1.tgz#46ee234770c5ff4c646f7e18708c56b6d7fa3c66" - integrity sha512-KLlJpgsJx88OZ0VLBH3gvUK4sfcXjr/nE0Qzyoe76dNqMzDzkSmmvILF3f2XviGgrzuP6Ie0ay/QX478Vrpn9A== +"@octokit/rest@19.0.4": + version "19.0.4" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-19.0.4.tgz#fd8bed1cefffa486e9ae46a9dc608ce81bcfcbdd" + integrity sha512-LwG668+6lE8zlSYOfwPj4FxWdv/qFXYBpv79TWIQEpBLKA9D/IMcWsF/U9RGpA3YqMVDiTxpgVpEW3zTFfPFTA== dependencies: - "@octokit/core" "^3.0.0" - "@octokit/plugin-paginate-rest" "^2.2.0" - "@octokit/plugin-request-log" "^1.0.0" - "@octokit/plugin-rest-endpoint-methods" "4.1.0" + "@octokit/core" "^4.0.0" + "@octokit/plugin-paginate-rest" "^4.0.0" + "@octokit/plugin-request-log" "^1.0.4" + "@octokit/plugin-rest-endpoint-methods" "^6.0.0" -"@octokit/types@^5.0.0", "@octokit/types@^5.0.1", "@octokit/types@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-5.1.0.tgz#4377a3f39edad3e60753fb5c3c310756f1ded57f" - integrity sha512-OFxUBgrEllAbdEmWp/wNmKIu5EuumKHG4sgy56vjZ8lXPgMhF05c76hmulfOdFHHYRpPj49ygOZJ8wgVsPecuA== +"@octokit/types@^7.0.0", "@octokit/types@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-7.2.0.tgz#7ee0fc27f9f463d7ccf12ca5956988d498b3c6c4" + integrity sha512-pYQ/a1U6mHptwhGyp6SvsiM4bWP2s3V95olUeTxas85D/2kN78yN5C8cGN+P4LwJSWUqIEyvq0Qn2WUn6NQRjw== dependencies: - "@types/node" ">= 8" + "@octokit/openapi-types" "^13.6.0" + +"@octokit/types@^9.0.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@octokit/types/-/types-9.2.0.tgz#0358e3de070b1d43c5a8af63b9951c88a09fc9ed" + integrity sha512-xySzJG4noWrIBFyMu4lg4tu9vAgNg9S0aoLRONhAEz6ueyi1evBzb40HitIosaYS4XOexphG305IVcLrIX/30g== + dependencies: + "@octokit/openapi-types" "^17.1.0" "@types/node@16.x": version "16.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -"@types/node@>= 8": - version "14.0.23" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.23.tgz#676fa0883450ed9da0bb24156213636290892806" - integrity sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw== - -before-after-hook@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635" - integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A== +before-after-hook@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.2.tgz#a6e8ca41028d90ee2c24222f201c90956091613e" + integrity sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ== deprecation@^2.0.0, deprecation@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -is-plain-object@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-3.0.1.tgz#662d92d24c0aa4302407b0d45d21f2251c85f85b" - integrity sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g== +graphql-tag@^2.10.3: + version "2.12.6" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1" + integrity sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg== + dependencies: + tslib "^2.1.0" -node-fetch@^2.3.0: +graphql@^16.0.0: + version "16.6.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" + integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== + +is-plain-object@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== @@ -143,6 +183,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= +tslib@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" + integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== + tunnel@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" @@ -153,11 +198,6 @@ universal-user-agent@^6.0.0: resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee" integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w== -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== - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" diff --git a/extensions/html-language-features/.eslintrc.json b/extensions/html-language-features/.eslintrc.json deleted file mode 100644 index ce28ab7a81..0000000000 --- a/extensions/html-language-features/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rules": { - "@typescript-eslint/explicit-function-return-type": ["off"], - "@typescript-eslint/await-thenable": ["off"], - "@typescript-eslint/no-unsafe-assignment": "off" - } -} diff --git a/extensions/html-language-features/client/src/languageParticipants.ts b/extensions/html-language-features/client/src/languageParticipants.ts deleted file mode 100644 index 005299308d..0000000000 --- a/extensions/html-language-features/client/src/languageParticipants.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { DocumentSelector } from 'vscode-languageclient'; -import { Event, EventEmitter, extensions } from 'vscode'; - -/** - * HTML language participant contribution. - */ -interface LanguageParticipantContribution { - /** - * The id of the language which participates with the HTML language server. - */ - languageId: string; - /** - * true if the language activates the auto insertion and false otherwise. - */ - autoInsert?: boolean; -} - -export interface LanguageParticipants { - readonly onDidChange: Event; - readonly documentSelector: DocumentSelector; - hasLanguage(languageId: string): boolean; - useAutoInsert(languageId: string): boolean; - dispose(): void; -} - -export function getLanguageParticipants(): LanguageParticipants { - const onDidChangeEmmiter = new EventEmitter(); - let languages = new Set(); - let autoInsert = new Set(); - - function update() { - const oldLanguages = languages, oldAutoInsert = autoInsert; - - languages = new Set(); - languages.add('html'); - autoInsert = new Set(); - autoInsert.add('html'); - - for (const extension of extensions.allAcrossExtensionHosts) { - const htmlLanguageParticipants = extension.packageJSON?.contributes?.htmlLanguageParticipants as LanguageParticipantContribution[]; - if (Array.isArray(htmlLanguageParticipants)) { - for (const htmlLanguageParticipant of htmlLanguageParticipants) { - const languageId = htmlLanguageParticipant.languageId; - if (typeof languageId === 'string') { - languages.add(languageId); - if (htmlLanguageParticipant.autoInsert !== false) { - autoInsert.add(languageId); - } - } - } - } - } - return !isEqualSet(languages, oldLanguages) || !isEqualSet(oldLanguages, oldAutoInsert); - } - update(); - - const changeListener = extensions.onDidChange(_ => { - if (update()) { - onDidChangeEmmiter.fire(); - } - }); - - return { - onDidChange: onDidChangeEmmiter.event, - get documentSelector() { return Array.from(languages); }, - hasLanguage(languageId: string) { return languages.has(languageId); }, - useAutoInsert(languageId: string) { return autoInsert.has(languageId); }, - dispose: () => changeListener.dispose() - }; -} - -function isEqualSet(s1: Set, s2: Set) { - if (s1.size !== s2.size) { - return false; - } - for (const e of s1) { - if (!s2.has(e)) { - return false; - } - } - return true; -} diff --git a/extensions/html-language-features/server/src/utils/validation.ts b/extensions/html-language-features/server/src/utils/validation.ts deleted file mode 100644 index 5738e7e501..0000000000 --- a/extensions/html-language-features/server/src/utils/validation.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver'; -import { TextDocument } from 'vscode-html-languageservice'; -import { formatError, runSafe } from './runner'; -import { RuntimeEnvironment } from '../htmlServer'; - -export type Validator = (textDocument: TextDocument) => Promise; -export type DiagnosticsSupport = { - dispose(): void; - requestRefresh(): void; -}; - -export function registerDiagnosticsPushSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { - - const pendingValidationRequests: { [uri: string]: Disposable } = {}; - const validationDelayMs = 500; - - const disposables: Disposable[] = []; - - // The content of a text document has changed. This event is emitted - // when the text document first opened or when its content has changed. - documents.onDidChangeContent(change => { - triggerValidation(change.document); - }, undefined, disposables); - - // a document has closed: clear all diagnostics - documents.onDidClose(event => { - cleanPendingValidation(event.document); - connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); - }, undefined, disposables); - - function cleanPendingValidation(textDocument: TextDocument): void { - const request = pendingValidationRequests[textDocument.uri]; - if (request) { - request.dispose(); - delete pendingValidationRequests[textDocument.uri]; - } - } - - function triggerValidation(textDocument: TextDocument): void { - cleanPendingValidation(textDocument); - const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => { - if (request === pendingValidationRequests[textDocument.uri]) { - try { - const diagnostics = await validate(textDocument); - if (request === pendingValidationRequests[textDocument.uri]) { - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); - } - delete pendingValidationRequests[textDocument.uri]; - } catch (e) { - connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); - } - } - }, validationDelayMs); - } - - return { - requestRefresh: () => { - documents.all().forEach(triggerValidation); - }, - dispose: () => { - disposables.forEach(d => d.dispose()); - disposables.length = 0; - const keys = Object.keys(pendingValidationRequests); - for (const key of keys) { - pendingValidationRequests[key].dispose(); - delete pendingValidationRequests[key]; - } - } - }; -} - -export function registerDiagnosticsPullSupport(documents: TextDocuments, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport { - - function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport { - return { - kind: DocumentDiagnosticReportKind.Full, - items: diagnostics - }; - } - - const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => { - return runSafe(runtime, async () => { - const document = documents.get(params.textDocument.uri); - if (document) { - return newDocumentDiagnosticReport(await validate(document)); - } - return newDocumentDiagnosticReport([]); - - }, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token); - }); - - function requestRefresh(): void { - connection.languages.diagnostics.refresh(); - } - - return { - requestRefresh, - dispose: () => { - registration.dispose(); - } - }; - -} diff --git a/extensions/html/.vscodeignore b/extensions/html/.vscodeignore deleted file mode 100644 index d42f161c71..0000000000 --- a/extensions/html/.vscodeignore +++ /dev/null @@ -1,3 +0,0 @@ -build/** -test/** -cgmanifest.json diff --git a/extensions/html/build/update-grammar.mjs b/extensions/html/build/update-grammar.mjs deleted file mode 100644 index 9e8e06b523..0000000000 --- a/extensions/html/build/update-grammar.mjs +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check - -import * as vscodeGrammarUpdater from 'vscode-grammar-updater'; - -function patchGrammar(grammar) { - let patchCount = 0; - - let visit = function (rule, parent) { - if (rule.name === 'source.js' || rule.name === 'source.css') { - if (parent.parent && parent.parent.property === 'endCaptures') { - rule.name = rule.name + '-ignored-vscode'; - patchCount++; - } - } - for (let property in rule) { - let value = rule[property]; - if (typeof value === 'object') { - visit(value, { node: rule, property: property, parent: parent }); - } - } - }; - - let repository = grammar.repository; - for (let key in repository) { - visit(repository[key], { node: repository, property: key, parent: undefined }); - } - if (patchCount !== 6) { - console.warn(`Expected to patch 6 occurrences of source.js & source.css: Was ${patchCount}`); - } - - - return grammar; -} - -const tsGrammarRepo = 'textmate/html.tmbundle'; -const grammarPath = 'Syntaxes/HTML.plist'; -vscodeGrammarUpdater.update(tsGrammarRepo, grammarPath, './syntaxes/html.tmLanguage.json', grammar => patchGrammar(grammar)); - - diff --git a/extensions/html/cgmanifest.json b/extensions/html/cgmanifest.json deleted file mode 100644 index 78125476f1..0000000000 --- a/extensions/html/cgmanifest.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "registrations": [ - { - "component": { - "type": "git", - "git": { - "name": "textmate/html.tmbundle", - "repositoryUrl": "https://github.com/textmate/html.tmbundle", - "commitHash": "0c3d5ee54de3a993f747f54186b73a4d2d3c44a2" - } - }, - "licenseDetail": [ - "Copyright (c) textmate-html.tmbundle project authors", - "", - "If not otherwise specified (see below), files in this repository fall under the following license:", - "", - "Permission to copy, use, modify, sell and distribute this", - "software is granted. This software is provided \"as is\" without", - "express or implied warranty, and with no claim as to its", - "suitability for any purpose.", - "", - "An exception is made for files in readable text which contain their own license information,", - "or files where an accompanying file exists (in the same directory) with a \"-license\" suffix added", - "to the base-name name of the original file, and an extension of txt, html, or similar. For example", - "\"tidy\" is accompanied by \"tidy-license.txt\"." - ], - "license": "TextMate Bundle License", - "version": "0.0.0" - } - ], - "version": 1 -} \ No newline at end of file diff --git a/extensions/html/language-configuration.json b/extensions/html/language-configuration.json deleted file mode 100644 index 628396b091..0000000000 --- a/extensions/html/language-configuration.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "comments": { - "blockComment": [ "" ] - }, - "brackets": [ - [""], - ["<", ">"], - ["{", "}"], - ["(", ")"] - ], - "autoClosingPairs": [ - { "open": "{", "close": "}"}, - { "open": "[", "close": "]"}, - { "open": "(", "close": ")" }, - { "open": "'", "close": "'" }, - { "open": "\"", "close": "\"" }, - { "open": "", "notIn": [ "comment", "string" ]} - ], - "surroundingPairs": [ - { "open": "'", "close": "'" }, - { "open": "\"", "close": "\"" }, - { "open": "{", "close": "}"}, - { "open": "[", "close": "]"}, - { "open": "(", "close": ")" }, - { "open": "<", "close": ">" } - ], - "colorizedBracketPairs": [ - ], - "folding": { - "markers": { - "start": "^\\s*", - "end": "^\\s*" - } - }, - "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\s]+)", - "onEnterRules": [ - { - "beforeText": { "pattern": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr))([_:\\w][_:\\w-.\\d]*)(?:(?:[^'\"/>]|\"[^\"]*\"|'[^']*')*?(?!\\/)>)[^<]*$", "flags": "i" }, - "afterText": { "pattern": "^<\\/([_:\\w][_:\\w-.\\d]*)\\s*>", "flags": "i" }, - "action": { - "indent": "indentOutdent" - } - }, - { - "beforeText": { "pattern": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr))([_:\\w][_:\\w-.\\d]*)(?:(?:[^'\"/>]|\"[^\"]*\"|'[^']*')*?(?!\\/)>)[^<]*$", "flags": "i" }, - "action": { - "indent": "indent" - } - } - ], - "indentationRules": { - "increaseIndentPattern": "<(?!\\?|(?:area|base|br|col|frame|hr|html|img|input|keygen|link|menuitem|meta|param|source|track|wbr)\\b|[^>]*\\/>)([-_\\.A-Za-z0-9]+)(?=\\s|>)\\b[^>]*>(?!.*<\\/\\1>)|)|\\{[^}\"']*$", - "decreaseIndentPattern": "^\\s*(<\\/(?!html)[-_\\.A-Za-z0-9]+\\b[^>]*>|-->|\\})" - } -} diff --git a/extensions/html/package.json b/extensions/html/package.json deleted file mode 100644 index aad035c05c..0000000000 --- a/extensions/html/package.json +++ /dev/null @@ -1,85 +0,0 @@ -{ - "name": "html", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { - "vscode": "0.10.x" - }, - "scripts": { - "update-grammar": "node ./build/update-grammar.mjs" - }, - "contributes": { - "languages": [ - { - "id": "html", - "extensions": [ - ".html", - ".htm", - ".shtml", - ".xhtml", - ".xht", - ".mdoc", - ".jsp", - ".asp", - ".aspx", - ".jshtm", - ".volt", - ".ejs", - ".rhtml" - ], - "aliases": [ - "HTML", - "htm", - "html", - "xhtml" - ], - "mimetypes": [ - "text/html", - "text/x-jshtm", - "text/template", - "text/ng-template", - "application/xhtml+xml" - ], - "configuration": "./language-configuration.json" - } - ], - "grammars": [ - { - "scopeName": "text.html.basic", - "path": "./syntaxes/html.tmLanguage.json", - "embeddedLanguages": { - "text.html": "html", - "source.css": "css", - "source.js": "javascript", - "source.python": "python", - "source.smarty": "smarty" - }, - "tokenTypes": { - "meta.tag string.quoted": "other" - } - }, - { - "language": "html", - "scopeName": "text.html.derivative", - "path": "./syntaxes/html-derivative.tmLanguage.json", - "embeddedLanguages": { - "text.html": "html", - "source.css": "css", - "source.js": "javascript", - "source.python": "python", - "source.smarty": "smarty" - }, - "tokenTypes": { - "meta.tag string.quoted": "other" - } - } - ] - }, - "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode.git" - } -} diff --git a/extensions/html/package.nls.json b/extensions/html/package.nls.json deleted file mode 100644 index aca7ad3caf..0000000000 --- a/extensions/html/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "HTML Language Basics", - "description": "Provides syntax highlighting, bracket matching & snippets in HTML files." -} diff --git a/extensions/html/syntaxes/html-derivative.tmLanguage.json b/extensions/html/syntaxes/html-derivative.tmLanguage.json deleted file mode 100644 index dc73025b9d..0000000000 --- a/extensions/html/syntaxes/html-derivative.tmLanguage.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "information_for_contributors": [ - "This file has been converted from https://github.com/textmate/html.tmbundle/blob/master/Syntaxes/HTML%20%28Derivative%29.tmLanguage", - "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/textmate/html.tmbundle/commit/390c8870273a2ae80244dae6db6ba064a802f407", - "name": "HTML (Derivative)", - "scopeName": "text.html.derivative", - "injections": { - "R:text.html - (comment.block, text.html meta.embedded, meta.tag.*.*.html, meta.tag.*.*.*.html, meta.tag.*.*.*.*.html)": { - "comment": "Uses R: to ensure this matches after any other injections.", - "patterns": [ - { - "match": "<", - "name": "invalid.illegal.bad-angle-bracket.html" - } - ] - } - }, - "patterns": [ - { - "include": "text.html.basic#core-minus-invalid" - }, - { - "begin": "(]*)(?)", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.other.unrecognized.html.derivative", - "patterns": [ - { - "include": "text.html.basic#attribute" - } - ] - } - ] -} \ No newline at end of file diff --git a/extensions/html/syntaxes/html.tmLanguage.json b/extensions/html/syntaxes/html.tmLanguage.json deleted file mode 100644 index 1e1c85c899..0000000000 --- a/extensions/html/syntaxes/html.tmLanguage.json +++ /dev/null @@ -1,2643 +0,0 @@ -{ - "information_for_contributors": [ - "This file has been converted from https://github.com/textmate/html.tmbundle/blob/master/Syntaxes/HTML.plist", - "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/textmate/html.tmbundle/commit/0c3d5ee54de3a993f747f54186b73a4d2d3c44a2", - "name": "HTML", - "scopeName": "text.html.basic", - "injections": { - "R:text.html - (comment.block, text.html meta.embedded, meta.tag.*.*.html, meta.tag.*.*.*.html, meta.tag.*.*.*.*.html)": { - "comment": "Uses R: to ensure this matches after any other injections.", - "patterns": [ - { - "match": "<", - "name": "invalid.illegal.bad-angle-bracket.html" - } - ] - } - }, - "patterns": [ - { - "include": "#xml-processing" - }, - { - "include": "#comment" - }, - { - "include": "#doctype" - }, - { - "include": "#cdata" - }, - { - "include": "#tags-valid" - }, - { - "include": "#tags-invalid" - }, - { - "include": "#entities" - } - ], - "repository": { - "attribute": { - "patterns": [ - { - "begin": "(s(hape|cope|t(ep|art)|ize(s)?|p(ellcheck|an)|elected|lot|andbox|rc(set|doc|lang)?)|h(ttp-equiv|i(dden|gh)|e(ight|aders)|ref(lang)?)|n(o(nce|validate|module)|ame)|c(h(ecked|arset)|ite|o(nt(ent(editable)?|rols)|ords|l(s(pan)?|or))|lass|rossorigin)|t(ype(mustmatch)?|itle|a(rget|bindex)|ranslate)|i(s(map)?|n(tegrity|putmode)|tem(scope|type|id|prop|ref)|d)|op(timum|en)|d(i(sabled|r(name)?)|ownload|e(coding|f(er|ault))|at(etime|a)|raggable)|usemap|p(ing|oster|la(ysinline|ceholder)|attern|reload)|enctype|value|kind|for(m(novalidate|target|enctype|action|method)?)?|w(idth|rap)|l(ist|o(op|w)|a(ng|bel))|a(s(ync)?|c(ce(sskey|pt(-charset)?)|tion)|uto(c(omplete|apitalize)|play|focus)|l(t|low(usermedia|paymentrequest|fullscreen))|bbr)|r(ows(pan)?|e(versed|quired|ferrerpolicy|l|adonly))|m(in(length)?|u(ted|ltiple)|e(thod|dia)|a(nifest|x(length)?)))(?![\\w:-])", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "comment": "HTML5 attributes, not event handlers", - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.$1.html", - "patterns": [ - { - "include": "#attribute-interior" - } - ] - }, - { - "begin": "style(?![\\w:-])", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "comment": "HTML5 style attribute", - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.style.html", - "patterns": [ - { - "begin": "=", - "beginCaptures": { - "0": { - "name": "punctuation.separator.key-value.html" - } - }, - "end": "(?<=[^\\s=])(?!\\s*=)|(?=/?>)", - "patterns": [ - { - "begin": "(?=[^\\s=<>`/]|/(?!>))", - "end": "(?!\\G)", - "name": "meta.embedded.line.css", - "patterns": [ - { - "captures": { - "0": { - "name": "source.css" - } - }, - "match": "([^\\s\"'=<>`/]|/(?!>))+", - "name": "string.unquoted.html" - }, - { - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.html" - } - }, - "contentName": "source.css", - "end": "(\")", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.html" - }, - "1": { - "name": "source.css-ignored-vscode" - } - }, - "name": "string.quoted.double.html", - "patterns": [ - { - "include": "#entities" - } - ] - }, - { - "begin": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.html" - } - }, - "contentName": "source.css", - "end": "(')", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.html" - }, - "1": { - "name": "source.css-ignored-vscode" - } - }, - "name": "string.quoted.single.html", - "patterns": [ - { - "include": "#entities" - } - ] - } - ] - }, - { - "match": "=", - "name": "invalid.illegal.unexpected-equals-sign.html" - } - ] - } - ] - }, - { - "begin": "on(s(croll|t(orage|alled)|u(spend|bmit)|e(curitypolicyviolation|ek(ing|ed)|lect))|hashchange|c(hange|o(ntextmenu|py)|u(t|echange)|l(ick|ose)|an(cel|play(through)?))|t(imeupdate|oggle)|in(put|valid)|o(nline|ffline)|d(urationchange|r(op|ag(start|over|e(n(ter|d)|xit)|leave)?)|blclick)|un(handledrejection|load)|p(opstate|lay(ing)?|a(ste|use|ge(show|hide))|rogress)|e(nded|rror|mptied)|volumechange|key(down|up|press)|focus|w(heel|aiting)|l(oad(start|e(nd|d(data|metadata)))?|anguagechange)|a(uxclick|fterprint|bort)|r(e(s(ize|et)|jectionhandled)|atechange)|m(ouse(o(ut|ver)|down|up|enter|leave|move)|essage(error)?)|b(efore(unload|print)|lur))(?![\\w:-])", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "comment": "HTML5 attributes, event handlers", - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.event-handler.$1.html", - "patterns": [ - { - "begin": "=", - "beginCaptures": { - "0": { - "name": "punctuation.separator.key-value.html" - } - }, - "end": "(?<=[^\\s=])(?!\\s*=)|(?=/?>)", - "patterns": [ - { - "begin": "(?=[^\\s=<>`/]|/(?!>))", - "end": "(?!\\G)", - "name": "meta.embedded.line.js", - "patterns": [ - { - "captures": { - "0": { - "name": "source.js" - }, - "1": { - "patterns": [ - { - "include": "source.js" - } - ] - } - }, - "match": "(([^\\s\"'=<>`/]|/(?!>))+)", - "name": "string.unquoted.html" - }, - { - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.html" - } - }, - "contentName": "source.js", - "end": "(\")", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.html" - }, - "1": { - "name": "source.js-ignored-vscode" - } - }, - "name": "string.quoted.double.html", - "patterns": [ - { - "captures": { - "0": { - "patterns": [ - { - "include": "source.js" - } - ] - } - }, - "match": "([^\\n\"/]|/(?![/*]))+" - }, - { - "begin": "//", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.js" - } - }, - "end": "(?=\")|\\n", - "name": "comment.line.double-slash.js" - }, - { - "begin": "/\\*", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.begin.js" - } - }, - "end": "(?=\")|\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.end.js" - } - }, - "name": "comment.block.js" - } - ] - }, - { - "begin": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.html" - } - }, - "contentName": "source.js", - "end": "(')", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.html" - }, - "1": { - "name": "source.js-ignored-vscode" - } - }, - "name": "string.quoted.single.html", - "patterns": [ - { - "captures": { - "0": { - "patterns": [ - { - "include": "source.js" - } - ] - } - }, - "match": "([^\\n'/]|/(?![/*]))+" - }, - { - "begin": "//", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.js" - } - }, - "end": "(?=')|\\n", - "name": "comment.line.double-slash.js" - }, - { - "begin": "/\\*", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.begin.js" - } - }, - "end": "(?=')|\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.end.js" - } - }, - "name": "comment.block.js" - } - ] - } - ] - }, - { - "match": "=", - "name": "invalid.illegal.unexpected-equals-sign.html" - } - ] - } - ] - }, - { - "begin": "(data-[a-z\\-]+)(?![\\w:-])", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "comment": "HTML5 attributes, data-*", - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.data-x.$1.html", - "patterns": [ - { - "include": "#attribute-interior" - } - ] - }, - { - "begin": "(align|bgcolor|border)(?![\\w:-])", - "beginCaptures": { - "0": { - "name": "invalid.deprecated.entity.other.attribute-name.html" - } - }, - "comment": "HTML attributes, deprecated", - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.$1.html", - "patterns": [ - { - "include": "#attribute-interior" - } - ] - }, - { - "begin": "([^\\x{0020}\"'<>/=\\x{0000}-\\x{001F}\\x{007F}-\\x{009F}\\x{FDD0}-\\x{FDEF}\\x{FFFE}\\x{FFFF}\\x{1FFFE}\\x{1FFFF}\\x{2FFFE}\\x{2FFFF}\\x{3FFFE}\\x{3FFFF}\\x{4FFFE}\\x{4FFFF}\\x{5FFFE}\\x{5FFFF}\\x{6FFFE}\\x{6FFFF}\\x{7FFFE}\\x{7FFFF}\\x{8FFFE}\\x{8FFFF}\\x{9FFFE}\\x{9FFFF}\\x{AFFFE}\\x{AFFFF}\\x{BFFFE}\\x{BFFFF}\\x{CFFFE}\\x{CFFFF}\\x{DFFFE}\\x{DFFFF}\\x{EFFFE}\\x{EFFFF}\\x{FFFFE}\\x{FFFFF}\\x{10FFFE}\\x{10FFFF}]+)", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "comment": "Anything else that is valid", - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.unrecognized.$1.html", - "patterns": [ - { - "include": "#attribute-interior" - } - ] - }, - { - "match": "[^\\s>]+", - "name": "invalid.illegal.character-not-allowed-here.html" - } - ] - }, - "attribute-interior": { - "patterns": [ - { - "begin": "=", - "beginCaptures": { - "0": { - "name": "punctuation.separator.key-value.html" - } - }, - "end": "(?<=[^\\s=])(?!\\s*=)|(?=/?>)", - "patterns": [ - { - "match": "([^\\s\"'=<>`/]|/(?!>))+", - "name": "string.unquoted.html" - }, - { - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.html" - } - }, - "end": "\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.html" - } - }, - "name": "string.quoted.double.html", - "patterns": [ - { - "include": "#entities" - } - ] - }, - { - "begin": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.html" - } - }, - "end": "'", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.html" - } - }, - "name": "string.quoted.single.html", - "patterns": [ - { - "include": "#entities" - } - ] - }, - { - "match": "=", - "name": "invalid.illegal.unexpected-equals-sign.html" - } - ] - } - ] - }, - "cdata": { - "begin": "", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.cdata.html" - }, - "comment": { - "begin": "", - "name": "comment.block.html", - "patterns": [ - { - "match": "\\G-?>", - "name": "invalid.illegal.characters-not-allowed-here.html" - }, - { - "match": ")", - "name": "invalid.illegal.characters-not-allowed-here.html" - }, - { - "match": "--!>", - "name": "invalid.illegal.characters-not-allowed-here.html" - } - ] - }, - "core-minus-invalid": { - "comment": "This should be the root pattern array includes minus #tags-invalid", - "patterns": [ - { - "include": "#xml-processing" - }, - { - "include": "#comment" - }, - { - "include": "#doctype" - }, - { - "include": "#cdata" - }, - { - "include": "#tags-valid" - }, - { - "include": "#entities" - } - ] - }, - "doctype": { - "begin": "", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.doctype.html", - "patterns": [ - { - "match": "\\G(?i:DOCTYPE)", - "name": "entity.name.tag.html" - }, - { - "begin": "\"", - "end": "\"", - "name": "string.quoted.double.html" - }, - { - "match": "[^\\s>]+", - "name": "entity.other.attribute-name.html" - } - ] - }, - "entities": { - "patterns": [ - { - "captures": { - "1": { - "name": "punctuation.definition.entity.html" - }, - "912": { - "name": "punctuation.definition.entity.html" - } - }, - "comment": "Yes this is a bit ridiculous, there are quite a lot of these", - "match": "(?x)\n\t\t\t\t\t\t(&)\t(?=[a-zA-Z])\n\t\t\t\t\t\t(\n\t\t\t\t\t\t\t(a(s(ymp(eq)?|cr|t)|n(d(slope|d|v|and)?|g(s(t|ph)|zarr|e|le|rt(vb(d)?)?|msd(a(h|c|d|e|f|a|g|b))?)?)|c(y|irc|d|ute|E)?|tilde|o(pf|gon)|uml|p(id|os|prox(eq)?|e|E|acir)?|elig|f(r)?|w(conint|int)|l(pha|e(ph|fsym))|acute|ring|grave|m(p|a(cr|lg))|breve)|A(s(sign|cr)|nd|MP|c(y|irc)|tilde|o(pf|gon)|uml|pplyFunction|fr|Elig|lpha|acute|ring|grave|macr|breve))\n\t\t\t\t\t\t | (B(scr|cy|opf|umpeq|e(cause|ta|rnoullis)|fr|a(ckslash|r(v|wed))|reve)|b(s(cr|im(e)?|ol(hsub|b)?|emi)|n(ot|e(quiv)?)|c(y|ong)|ig(s(tar|qcup)|c(irc|up|ap)|triangle(down|up)|o(times|dot|plus)|uplus|vee|wedge)|o(t(tom)?|pf|wtie|x(h(d|u|D|U)?|times|H(d|u|D|U)?|d(R|l|r|L)|u(R|l|r|L)|plus|D(R|l|r|L)|v(R|h|H|l|r|L)?|U(R|l|r|L)|V(R|h|H|l|r|L)?|minus|box))|Not|dquo|u(ll(et)?|mp(e(q)?|E)?)|prime|e(caus(e)?|t(h|ween|a)|psi|rnou|mptyv)|karow|fr|l(ock|k(1(2|4)|34)|a(nk|ck(square|triangle(down|left|right)?|lozenge)))|a(ck(sim(eq)?|cong|prime|epsilon)|r(vee|wed(ge)?))|r(eve|vbar)|brk(tbrk)?))\n\t\t\t\t\t\t | (c(s(cr|u(p(e)?|b(e)?))|h(cy|i|eck(mark)?)|ylcty|c(irc|ups(sm)?|edil|a(ps|ron))|tdot|ir(scir|c(eq|le(d(R|circ|S|dash|ast)|arrow(left|right)))?|e|fnint|E|mid)?|o(n(int|g(dot)?)|p(y(sr)?|f|rod)|lon(e(q)?)?|m(p(fn|le(xes|ment))?|ma(t)?))|dot|u(darr(l|r)|p(s|c(up|ap)|or|dot|brcap)?|e(sc|pr)|vee|wed|larr(p)?|r(vearrow(left|right)|ly(eq(succ|prec)|vee|wedge)|arr(m)?|ren))|e(nt(erdot)?|dil|mptyv)|fr|w(conint|int)|lubs(uit)?|a(cute|p(s|c(up|ap)|dot|and|brcup)?|r(on|et))|r(oss|arr))|C(scr|hi|c(irc|onint|edil|aron)|ircle(Minus|Times|Dot|Plus)|Hcy|o(n(tourIntegral|int|gruent)|unterClockwiseContourIntegral|p(f|roduct)|lon(e)?)|dot|up(Cap)?|OPY|e(nterDot|dilla)|fr|lo(seCurly(DoubleQuote|Quote)|ckwiseContourIntegral)|a(yleys|cute|p(italDifferentialD)?)|ross))\n\t\t\t\t\t\t | (d(s(c(y|r)|trok|ol)|har(l|r)|c(y|aron)|t(dot|ri(f)?)|i(sin|e|v(ide(ontimes)?|onx)?|am(s|ond(suit)?)?|gamma)|Har|z(cy|igrarr)|o(t(square|plus|eq(dot)?|minus)?|ublebarwedge|pf|wn(harpoon(left|right)|downarrows|arrow)|llar)|d(otseq|a(rr|gger))?|u(har|arr)|jcy|e(lta|g|mptyv)|f(isht|r)|wangle|lc(orn|rop)|a(sh(v)?|leth|rr|gger)|r(c(orn|rop)|bkarow)|b(karow|lac)|Arr)|D(s(cr|trok)|c(y|aron)|Scy|i(fferentialD|a(critical(Grave|Tilde|Do(t|ubleAcute)|Acute)|mond))|o(t(Dot|Equal)?|uble(Right(Tee|Arrow)|ContourIntegral|Do(t|wnArrow)|Up(DownArrow|Arrow)|VerticalBar|L(ong(RightArrow|Left(RightArrow|Arrow))|eft(RightArrow|Tee|Arrow)))|pf|wn(Right(TeeVector|Vector(Bar)?)|Breve|Tee(Arrow)?|arrow|Left(RightVector|TeeVector|Vector(Bar)?)|Arrow(Bar|UpArrow)?))|Zcy|el(ta)?|D(otrahd)?|Jcy|fr|a(shv|rr|gger)))\n\t\t\t\t\t\t | (e(s(cr|im|dot)|n(sp|g)|c(y|ir(c)?|olon|aron)|t(h|a)|o(pf|gon)|dot|u(ro|ml)|p(si(v|lon)?|lus|ar(sl)?)|e|D(ot|Dot)|q(s(im|lant(less|gtr))|c(irc|olon)|u(iv(DD)?|est|als)|vparsl)|f(Dot|r)|l(s(dot)?|inters|l)?|a(ster|cute)|r(Dot|arr)|g(s(dot)?|rave)?|x(cl|ist|p(onentiale|ectation))|m(sp(1(3|4))?|pty(set|v)?|acr))|E(s(cr|im)|c(y|irc|aron)|ta|o(pf|gon)|NG|dot|uml|TH|psilon|qu(ilibrium|al(Tilde)?)|fr|lement|acute|grave|x(ists|ponentialE)|m(pty(SmallSquare|VerySmallSquare)|acr)))\n\t\t\t\t\t\t | (f(scr|nof|cy|ilig|o(pf|r(k(v)?|all))|jlig|partint|emale|f(ilig|l(ig|lig)|r)|l(tns|lig|at)|allingdotseq|r(own|a(sl|c(1(2|8|3|4|5|6)|78|2(3|5)|3(8|4|5)|45|5(8|6)))))|F(scr|cy|illed(SmallSquare|VerySmallSquare)|o(uriertrf|pf|rAll)|fr))\n\t\t\t\t\t\t | (G(scr|c(y|irc|edil)|t|opf|dot|T|Jcy|fr|amma(d)?|reater(Greater|SlantEqual|Tilde|Equal(Less)?|FullEqual|Less)|g|breve)|g(s(cr|im(e|l)?)|n(sim|e(q(q)?)?|E|ap(prox)?)|c(y|irc)|t(c(c|ir)|dot|quest|lPar|r(sim|dot|eq(qless|less)|less|a(pprox|rr)))?|imel|opf|dot|jcy|e(s(cc|dot(o(l)?)?|l(es)?)?|q(slant|q)?|l)?|v(nE|ertneqq)|fr|E(l)?|l(j|E|a)?|a(cute|p|mma(d)?)|rave|g(g)?|breve))\n\t\t\t\t\t\t | (h(s(cr|trok|lash)|y(phen|bull)|circ|o(ok(leftarrow|rightarrow)|pf|arr|rbar|mtht)|e(llip|arts(uit)?|rcon)|ks(earow|warow)|fr|a(irsp|lf|r(dcy|r(cir|w)?)|milt)|bar|Arr)|H(s(cr|trok)|circ|ilbertSpace|o(pf|rizontalLine)|ump(DownHump|Equal)|fr|a(cek|t)|ARDcy))\n\t\t\t\t\t\t | (i(s(cr|in(s(v)?|dot|v|E)?)|n(care|t(cal|prod|e(rcal|gers)|larhk)?|odot|fin(tie)?)?|c(y|irc)?|t(ilde)?|i(nfin|i(nt|int)|ota)?|o(cy|ta|pf|gon)|u(kcy|ml)|jlig|prod|e(cy|xcl)|quest|f(f|r)|acute|grave|m(of|ped|a(cr|th|g(part|e|line))))|I(scr|n(t(e(rsection|gral))?|visible(Comma|Times))|c(y|irc)|tilde|o(ta|pf|gon)|dot|u(kcy|ml)|Ocy|Jlig|fr|Ecy|acute|grave|m(plies|a(cr|ginaryI))?))\n\t\t\t\t\t\t | (j(s(cr|ercy)|c(y|irc)|opf|ukcy|fr|math)|J(s(cr|ercy)|c(y|irc)|opf|ukcy|fr))\n\t\t\t\t\t\t | (k(scr|hcy|c(y|edil)|opf|jcy|fr|appa(v)?|green)|K(scr|c(y|edil)|Hcy|opf|Jcy|fr|appa))\n\t\t\t\t\t\t | (l(s(h|cr|trok|im(e|g)?|q(uo(r)?|b)|aquo)|h(ar(d|u(l)?)|blk)|n(sim|e(q(q)?)?|E|ap(prox)?)|c(y|ub|e(il|dil)|aron)|Barr|t(hree|c(c|ir)|imes|dot|quest|larr|r(i(e|f)?|Par))?|Har|o(ng(left(arrow|rightarrow)|rightarrow|mapsto)|times|z(enge|f)?|oparrow(left|right)|p(f|lus|ar)|w(ast|bar)|a(ng|rr)|brk)|d(sh|ca|quo(r)?|r(dhar|ushar))|ur(dshar|uhar)|jcy|par(lt)?|e(s(s(sim|dot|eq(qgtr|gtr)|approx|gtr)|cc|dot(o(r)?)?|g(es)?)?|q(slant|q)?|ft(harpoon(down|up)|threetimes|leftarrows|arrow(tail)?|right(squigarrow|harpoons|arrow(s)?))|g)?|v(nE|ertneqq)|f(isht|loor|r)|E(g)?|l(hard|corner|tri|arr)?|a(ng(d|le)?|cute|t(e(s)?|ail)?|p|emptyv|quo|rr(sim|hk|tl|pl|fs|lp|b(fs)?)?|gran|mbda)|r(har(d)?|corner|tri|arr|m)|g(E)?|m(idot|oust(ache)?)|b(arr|r(k(sl(d|u)|e)|ac(e|k))|brk)|A(tail|arr|rr))|L(s(h|cr|trok)|c(y|edil|aron)|t|o(ng(RightArrow|left(arrow|rightarrow)|rightarrow|Left(RightArrow|Arrow))|pf|wer(RightArrow|LeftArrow))|T|e(ss(Greater|SlantEqual|Tilde|EqualGreater|FullEqual|Less)|ft(Right(Vector|Arrow)|Ceiling|T(ee(Vector|Arrow)?|riangle(Bar|Equal)?)|Do(ubleBracket|wn(TeeVector|Vector(Bar)?))|Up(TeeVector|DownVector|Vector(Bar)?)|Vector(Bar)?|arrow|rightarrow|Floor|A(ngleBracket|rrow(RightArrow|Bar)?)))|Jcy|fr|l(eftarrow)?|a(ng|cute|placetrf|rr|mbda)|midot))\n\t\t\t\t\t\t | (M(scr|cy|inusPlus|opf|u|e(diumSpace|llintrf)|fr|ap)|m(s(cr|tpos)|ho|nplus|c(y|omma)|i(nus(d(u)?|b)?|cro|d(cir|dot|ast)?)|o(dels|pf)|dash|u(ltimap|map)?|p|easuredangle|DDot|fr|l(cp|dr)|a(cr|p(sto(down|up|left)?)?|l(t(ese)?|e)|rker)))\n\t\t\t\t\t\t | (n(s(hort(parallel|mid)|c(cue|e|r)?|im(e(q)?)?|u(cc(eq)?|p(set(eq(q)?)?|e|E)?|b(set(eq(q)?)?|e|E)?)|par|qsu(pe|be)|mid)|Rightarrow|h(par|arr|Arr)|G(t(v)?|g)|c(y|ong(dot)?|up|edil|a(p|ron))|t(ilde|lg|riangle(left(eq)?|right(eq)?)|gl)|i(s(d)?|v)?|o(t(ni(v(c|a|b))?|in(dot|v(c|a|b)|E)?)?|pf)|dash|u(m(sp|ero)?)?|jcy|p(olint|ar(sl|t|allel)?|r(cue|e(c(eq)?)?)?)|e(s(im|ear)|dot|quiv|ar(hk|r(ow)?)|xist(s)?|Arr)?|v(sim|infin|Harr|dash|Dash|l(t(rie)?|e|Arr)|ap|r(trie|Arr)|g(t|e))|fr|w(near|ar(hk|r(ow)?)|Arr)|V(dash|Dash)|l(sim|t(ri(e)?)?|dr|e(s(s)?|q(slant|q)?|ft(arrow|rightarrow))?|E|arr|Arr)|a(ng|cute|tur(al(s)?)?|p(id|os|prox|E)?|bla)|r(tri(e)?|ightarrow|arr(c|w)?|Arr)|g(sim|t(r)?|e(s|q(slant|q)?)?|E)|mid|L(t(v)?|eft(arrow|rightarrow)|l)|b(sp|ump(e)?))|N(scr|c(y|edil|aron)|tilde|o(nBreakingSpace|Break|t(R(ightTriangle(Bar|Equal)?|everseElement)|Greater(Greater|SlantEqual|Tilde|Equal|FullEqual|Less)?|S(u(cceeds(SlantEqual|Tilde|Equal)?|perset(Equal)?|bset(Equal)?)|quareSu(perset(Equal)?|bset(Equal)?))|Hump(DownHump|Equal)|Nested(GreaterGreater|LessLess)|C(ongruent|upCap)|Tilde(Tilde|Equal|FullEqual)?|DoubleVerticalBar|Precedes(SlantEqual|Equal)?|E(qual(Tilde)?|lement|xists)|VerticalBar|Le(ss(Greater|SlantEqual|Tilde|Equal|Less)?|ftTriangle(Bar|Equal)?))?|pf)|u|e(sted(GreaterGreater|LessLess)|wLine|gative(MediumSpace|Thi(nSpace|ckSpace)|VeryThinSpace))|Jcy|fr|acute))\n\t\t\t\t\t\t | (o(s(cr|ol|lash)|h(m|bar)|c(y|ir(c)?)|ti(lde|mes(as)?)|S|int|opf|d(sold|iv|ot|ash|blac)|uml|p(erp|lus|ar)|elig|vbar|f(cir|r)|l(c(ir|ross)|t|ine|arr)|a(st|cute)|r(slope|igof|or|d(er(of)?|f|m)?|v|arr)?|g(t|on|rave)|m(i(nus|cron|d)|ega|acr))|O(s(cr|lash)|c(y|irc)|ti(lde|mes)|opf|dblac|uml|penCurly(DoubleQuote|Quote)|ver(B(ar|rac(e|ket))|Parenthesis)|fr|Elig|acute|r|grave|m(icron|ega|acr)))\n\t\t\t\t\t\t | (p(s(cr|i)|h(i(v)?|one|mmat)|cy|i(tchfork|v)?|o(intint|und|pf)|uncsp|er(cnt|tenk|iod|p|mil)|fr|l(us(sim|cir|two|d(o|u)|e|acir|mn|b)?|an(ck(h)?|kv))|ar(s(im|l)|t|a(llel)?)?|r(sim|n(sim|E|ap)|cue|ime(s)?|o(d|p(to)?|f(surf|line|alar))|urel|e(c(sim|n(sim|eqq|approx)|curlyeq|eq|approx)?)?|E|ap)?|m)|P(s(cr|i)|hi|cy|i|o(incareplane|pf)|fr|lusMinus|artialD|r(ime|o(duct|portion(al)?)|ecedes(SlantEqual|Tilde|Equal)?)?))\n\t\t\t\t\t\t | (q(scr|int|opf|u(ot|est(eq)?|at(int|ernions))|prime|fr)|Q(scr|opf|UOT|fr))\n\t\t\t\t\t\t | (R(s(h|cr)|ho|c(y|edil|aron)|Barr|ight(Ceiling|T(ee(Vector|Arrow)?|riangle(Bar|Equal)?)|Do(ubleBracket|wn(TeeVector|Vector(Bar)?))|Up(TeeVector|DownVector|Vector(Bar)?)|Vector(Bar)?|arrow|Floor|A(ngleBracket|rrow(Bar|LeftArrow)?))|o(undImplies|pf)|uleDelayed|e(verse(UpEquilibrium|E(quilibrium|lement)))?|fr|EG|a(ng|cute|rr(tl)?)|rightarrow)|r(s(h|cr|q(uo(r)?|b)|aquo)|h(o(v)?|ar(d|u(l)?))|nmid|c(y|ub|e(il|dil)|aron)|Barr|t(hree|imes|ri(e|f|ltri)?)|i(singdotseq|ng|ght(squigarrow|harpoon(down|up)|threetimes|left(harpoons|arrows)|arrow(tail)?|rightarrows))|Har|o(times|p(f|lus|ar)|a(ng|rr)|brk)|d(sh|ca|quo(r)?|ldhar)|uluhar|p(polint|ar(gt)?)|e(ct|al(s|ine|part)?|g)|f(isht|loor|r)|l(har|arr|m)|a(ng(d|e|le)?|c(ute|e)|t(io(nals)?|ail)|dic|emptyv|quo|rr(sim|hk|c|tl|pl|fs|w|lp|ap|b(fs)?)?)|rarr|x|moust(ache)?|b(arr|r(k(sl(d|u)|e)|ac(e|k))|brk)|A(tail|arr|rr)))\n\t\t\t\t\t\t | (s(s(cr|tarf|etmn|mile)|h(y|c(hcy|y)|ort(parallel|mid)|arp)|c(sim|y|n(sim|E|ap)|cue|irc|polint|e(dil)?|E|a(p|ron))?|t(ar(f)?|r(ns|aight(phi|epsilon)))|i(gma(v|f)?|m(ne|dot|plus|e(q)?|l(E)?|rarr|g(E)?)?)|zlig|o(pf|ftcy|l(b(ar)?)?)|dot(e|b)?|u(ng|cc(sim|n(sim|eqq|approx)|curlyeq|eq|approx)?|p(s(im|u(p|b)|et(neq(q)?|eq(q)?)?)|hs(ol|ub)|1|n(e|E)|2|d(sub|ot)|3|plus|e(dot)?|E|larr|mult)?|m|b(s(im|u(p|b)|et(neq(q)?|eq(q)?)?)|n(e|E)|dot|plus|e(dot)?|E|rarr|mult)?)|pa(des(uit)?|r)|e(swar|ct|tm(n|inus)|ar(hk|r(ow)?)|xt|mi|Arr)|q(su(p(set(eq)?|e)?|b(set(eq)?|e)?)|c(up(s)?|ap(s)?)|u(f|ar(e|f))?)|fr(own)?|w(nwar|ar(hk|r(ow)?)|Arr)|larr|acute|rarr|m(t(e(s)?)?|i(d|le)|eparsl|a(shp|llsetminus))|bquo)|S(scr|hort(RightArrow|DownArrow|UpArrow|LeftArrow)|c(y|irc|edil|aron)?|tar|igma|H(cy|CHcy)|opf|u(c(hThat|ceeds(SlantEqual|Tilde|Equal)?)|p(set|erset(Equal)?)?|m|b(set(Equal)?)?)|OFTcy|q(uare(Su(perset(Equal)?|bset(Equal)?)|Intersection|Union)?|rt)|fr|acute|mallCircle))\n\t\t\t\t\t\t | (t(s(hcy|c(y|r)|trok)|h(i(nsp|ck(sim|approx))|orn|e(ta(sym|v)?|re(4|fore))|k(sim|ap))|c(y|edil|aron)|i(nt|lde|mes(d|b(ar)?)?)|o(sa|p(cir|f(ork)?|bot)?|ea)|dot|prime|elrec|fr|w(ixt|ohead(leftarrow|rightarrow))|a(u|rget)|r(i(sb|time|dot|plus|e|angle(down|q|left(eq)?|right(eq)?)?|minus)|pezium|ade)|brk)|T(s(cr|trok)|RADE|h(i(nSpace|ckSpace)|e(ta|refore))|c(y|edil|aron)|S(cy|Hcy)|ilde(Tilde|Equal|FullEqual)?|HORN|opf|fr|a(u|b)|ripleDot))\n\t\t\t\t\t\t | (u(scr|h(ar(l|r)|blk)|c(y|irc)|t(ilde|dot|ri(f)?)|Har|o(pf|gon)|d(har|arr|blac)|u(arr|ml)|p(si(h|lon)?|harpoon(left|right)|downarrow|uparrows|lus|arrow)|f(isht|r)|wangle|l(c(orn(er)?|rop)|tri)|a(cute|rr)|r(c(orn(er)?|rop)|tri|ing)|grave|m(l|acr)|br(cy|eve)|Arr)|U(scr|n(ion(Plus)?|der(B(ar|rac(e|ket))|Parenthesis))|c(y|irc)|tilde|o(pf|gon)|dblac|uml|p(si(lon)?|downarrow|Tee(Arrow)?|per(RightArrow|LeftArrow)|DownArrow|Equilibrium|arrow|Arrow(Bar|DownArrow)?)|fr|a(cute|rr(ocir)?)|ring|grave|macr|br(cy|eve)))\n\t\t\t\t\t\t | (v(s(cr|u(pn(e|E)|bn(e|E)))|nsu(p|b)|cy|Bar(v)?|zigzag|opf|dash|prop|e(e(eq|bar)?|llip|r(t|bar))|Dash|fr|ltri|a(ngrt|r(s(igma|u(psetneq(q)?|bsetneq(q)?))|nothing|t(heta|riangle(left|right))|p(hi|i|ropto)|epsilon|kappa|r(ho)?))|rtri|Arr)|V(scr|cy|opf|dash(l)?|e(e|r(yThinSpace|t(ical(Bar|Separator|Tilde|Line))?|bar))|Dash|vdash|fr|bar))\n\t\t\t\t\t\t | (w(scr|circ|opf|p|e(ierp|d(ge(q)?|bar))|fr|r(eath)?)|W(scr|circ|opf|edge|fr))\n\t\t\t\t\t\t | (X(scr|i|opf|fr)|x(s(cr|qcup)|h(arr|Arr)|nis|c(irc|up|ap)|i|o(time|dot|p(f|lus))|dtri|u(tri|plus)|vee|fr|wedge|l(arr|Arr)|r(arr|Arr)|map))\n\t\t\t\t\t\t | (y(scr|c(y|irc)|icy|opf|u(cy|ml)|en|fr|ac(y|ute))|Y(scr|c(y|irc)|opf|uml|Icy|Ucy|fr|acute|Acy))\n\t\t\t\t\t\t | (z(scr|hcy|c(y|aron)|igrarr|opf|dot|e(ta|etrf)|fr|w(nj|j)|acute)|Z(scr|c(y|aron)|Hcy|opf|dot|e(ta|roWidthSpace)|fr|acute))\n\t\t\t\t\t\t)\n\t\t\t\t\t\t(;)\n\t\t\t\t\t", - "name": "constant.character.entity.named.$2.html" - }, - { - "captures": { - "1": { - "name": "punctuation.definition.entity.html" - }, - "3": { - "name": "punctuation.definition.entity.html" - } - }, - "match": "(&)#[0-9]+(;)", - "name": "constant.character.entity.numeric.decimal.html" - }, - { - "captures": { - "1": { - "name": "punctuation.definition.entity.html" - }, - "3": { - "name": "punctuation.definition.entity.html" - } - }, - "match": "(&)#[xX][0-9a-fA-F]+(;)", - "name": "constant.character.entity.numeric.hexadecimal.html" - }, - { - "match": "&(?=[a-zA-Z0-9]+;)", - "name": "invalid.illegal.ambiguous-ampersand.html" - } - ] - }, - "math": { - "patterns": [ - { - "begin": "(?i)(<)(math)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.structure.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()", - "endCaptures": { - "0": { - "name": "meta.tag.structure.$2.end.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.element.structure.$2.html", - "patterns": [ - { - "begin": "(?)\\G", - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - } - ], - "repository": { - "attribute": { - "patterns": [ - { - "begin": "(s(hift|ymmetric|cript(sizemultiplier|level|minsize)|t(ackalign|retchy)|ide|u(pscriptshift|bscriptshift)|e(parator(s)?|lection)|rc)|h(eight|ref)|n(otation|umalign)|c(haralign|olumn(spa(n|cing)|width|lines|align)|lose|rossout)|i(n(dent(shift(first|last)?|target|align(first|last)?)|fixlinebreakstyle)|d)|o(pen|verflow)|d(i(splay(style)?|r)|e(nomalign|cimalpoint|pth))|position|e(dge|qual(columns|rows))|voffset|f(orm|ence|rame(spacing)?)|width|l(space|ine(thickness|leading|break(style|multchar)?)|o(ngdivstyle|cation)|ength|quote|argeop)|a(c(cent(under)?|tiontype)|l(t(text|img(-(height|valign|width))?)|ign(mentscope)?))|r(space|ow(spa(n|cing)|lines|align)|quote)|groupalign|x(link:href|mlns)|m(in(size|labelspacing)|ovablelimits|a(th(size|color|variant|background)|xsize))|bevelled)(?![\\w:-])", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.$1.html", - "patterns": [ - { - "include": "#attribute-interior" - } - ] - }, - { - "begin": "([^\\x{0020}\"'<>/=\\x{0000}-\\x{001F}\\x{007F}-\\x{009F}\\x{FDD0}-\\x{FDEF}\\x{FFFE}\\x{FFFF}\\x{1FFFE}\\x{1FFFF}\\x{2FFFE}\\x{2FFFF}\\x{3FFFE}\\x{3FFFF}\\x{4FFFE}\\x{4FFFF}\\x{5FFFE}\\x{5FFFF}\\x{6FFFE}\\x{6FFFF}\\x{7FFFE}\\x{7FFFF}\\x{8FFFE}\\x{8FFFF}\\x{9FFFE}\\x{9FFFF}\\x{AFFFE}\\x{AFFFF}\\x{BFFFE}\\x{BFFFF}\\x{CFFFE}\\x{CFFFF}\\x{DFFFE}\\x{DFFFF}\\x{EFFFE}\\x{EFFFF}\\x{FFFFE}\\x{FFFFF}\\x{10FFFE}\\x{10FFFF}]+)", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "comment": "Anything else that is valid", - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.unrecognized.$1.html", - "patterns": [ - { - "include": "#attribute-interior" - } - ] - }, - { - "match": "[^\\s>]+", - "name": "invalid.illegal.character-not-allowed-here.html" - } - ] - }, - "tags": { - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#cdata" - }, - { - "captures": { - "0": { - "name": "meta.tag.structure.math.$2.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(annotation|annotation-xml|semantics|menclose|merror|mfenced|mfrac|mpadded|mphantom|mroot|mrow|msqrt|mstyle|mmultiscripts|mover|mprescripts|msub|msubsup|msup|munder|munderover|none|mlabeledtr|mtable|mtd|mtr|mlongdiv|mscarries|mscarry|msgroup|msline|msrow|mstack|maction)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.structure.math.$2.html" - }, - { - "begin": "(?i)(<)(annotation|annotation-xml|semantics|menclose|merror|mfenced|mfrac|mpadded|mphantom|mroot|mrow|msqrt|mstyle|mmultiscripts|mover|mprescripts|msub|msubsup|msup|munder|munderover|none|mlabeledtr|mtable|mtd|mtr|mlongdiv|mscarries|mscarry|msgroup|msline|msrow|mstack|maction)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.structure.math.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "captures": { - "0": { - "name": "meta.tag.inline.math.$2.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(mi|mn|mo|ms|mspace|mtext|maligngroup|malignmark)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.inline.math.$2.html" - }, - { - "begin": "(?i)(<)(mi|mn|mo|ms|mspace|mtext|maligngroup|malignmark)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.inline.math.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.inline.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "captures": { - "0": { - "name": "meta.tag.object.math.$2.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(mglyph)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.object.math.$2.html" - }, - { - "begin": "(?i)(<)(mglyph)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.object.math.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.object.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "captures": { - "0": { - "name": "meta.tag.other.invalid.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.illegal.unrecognized-tag.html" - }, - "4": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "6": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(([\\w:]+))(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.other.invalid.html" - }, - { - "begin": "(?i)(<)((\\w[^\\s>]*))(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.other.invalid.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.illegal.unrecognized-tag.html" - }, - "4": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "6": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.other.invalid.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "include": "#tags-invalid" - } - ] - } - } - }, - "svg": { - "patterns": [ - { - "begin": "(?i)(<)(svg)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.structure.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()", - "endCaptures": { - "0": { - "name": "meta.tag.structure.$2.end.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.element.structure.$2.html", - "patterns": [ - { - "begin": "(?)\\G", - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - } - ], - "repository": { - "attribute": { - "patterns": [ - { - "begin": "(s(hape-rendering|ystemLanguage|cale|t(yle|itchTiles|op-(color|opacity)|dDeviation|em(h|v)|artOffset|r(i(ng|kethrough-(thickness|position))|oke(-(opacity|dash(offset|array)|width|line(cap|join)|miterlimit))?))|urfaceScale|p(e(cular(Constant|Exponent)|ed)|acing|readMethod)|eed|lope)|h(oriz-(origin-x|adv-x)|eight|anging|ref(lang)?)|y(1|2|ChannelSelector)?|n(umOctaves|ame)|c(y|o(ntentS(criptType|tyleType)|lor(-(interpolation(-filters)?|profile|rendering))?)|ursor|l(ip(-(path|rule)|PathUnits)?|ass)|a(p-height|lcMode)|x)|t(ype|o|ext(-(decoration|anchor|rendering)|Length)|a(rget(X|Y)?|b(index|leValues))|ransform)|i(n(tercept|2)?|d(eographic)?|mage-rendering)|z(oomAndPan)?|o(p(erator|acity)|ver(flow|line-(thickness|position))|ffset|r(i(ent(ation)?|gin)|der))|d(y|i(splay|visor|ffuseConstant|rection)|ominant-baseline|ur|e(scent|celerate)|x)?|u(1|n(i(code(-(range|bidi))?|ts-per-em)|derline-(thickness|position))|2)|p(ing|oint(s(At(X|Y|Z))?|er-events)|a(nose-1|t(h(Length)?|tern(ContentUnits|Transform|Units))|int-order)|r(imitiveUnits|eserveA(spectRatio|lpha)))|e(n(d|able-background)|dgeMode|levation|x(ternalResourcesRequired|ponent))|v(i(sibility|ew(Box|Target))|-(hanging|ideographic|alphabetic|mathematical)|e(ctor-effect|r(sion|t-(origin-(y|x)|adv-y)))|alues)|k(1|2|3|e(y(Splines|Times|Points)|rn(ing|el(Matrix|UnitLength)))|4)?|f(y|il(ter(Res|Units)?|l(-(opacity|rule))?)|o(nt-(s(t(yle|retch)|ize(-adjust)?)|variant|family|weight)|rmat)|lood-(color|opacity)|r(om)?|x)|w(idth(s)?|ord-spacing|riting-mode)|l(i(ghting-color|mitingConeAngle)|ocal|e(ngthAdjust|tter-spacing)|ang)|a(scent|cc(umulate|ent-height)|ttribute(Name|Type)|zimuth|dditive|utoReverse|l(ignment-baseline|phabetic|lowReorder)|rabic-form|mplitude)|r(y|otate|e(s(tart|ult)|ndering-intent|peat(Count|Dur)|quired(Extensions|Features)|f(X|Y|errerPolicy)|l)|adius|x)?|g(1|2|lyph(Ref|-(name|orientation-(horizontal|vertical)))|radient(Transform|Units))|x(1|2|ChannelSelector|-height|link:(show|href|t(ype|itle)|a(ctuate|rcrole)|role)|ml:(space|lang|base))?|m(in|ode|e(thod|dia)|a(sk(ContentUnits|Units)?|thematical|rker(Height|-(start|end|mid)|Units|Width)|x))|b(y|ias|egin|ase(Profile|line-shift|Frequency)|box))(?![\\w:-])", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.$1.html", - "patterns": [ - { - "include": "#attribute-interior" - } - ] - }, - { - "begin": "([^\\x{0020}\"'<>/=\\x{0000}-\\x{001F}\\x{007F}-\\x{009F}\\x{FDD0}-\\x{FDEF}\\x{FFFE}\\x{FFFF}\\x{1FFFE}\\x{1FFFF}\\x{2FFFE}\\x{2FFFF}\\x{3FFFE}\\x{3FFFF}\\x{4FFFE}\\x{4FFFF}\\x{5FFFE}\\x{5FFFF}\\x{6FFFE}\\x{6FFFF}\\x{7FFFE}\\x{7FFFF}\\x{8FFFE}\\x{8FFFF}\\x{9FFFE}\\x{9FFFF}\\x{AFFFE}\\x{AFFFF}\\x{BFFFE}\\x{BFFFF}\\x{CFFFE}\\x{CFFFF}\\x{DFFFE}\\x{DFFFF}\\x{EFFFE}\\x{EFFFF}\\x{FFFFE}\\x{FFFFF}\\x{10FFFE}\\x{10FFFF}]+)", - "beginCaptures": { - "0": { - "name": "entity.other.attribute-name.html" - } - }, - "comment": "Anything else that is valid", - "end": "(?=\\s*+[^=\\s])", - "name": "meta.attribute.unrecognized.$1.html", - "patterns": [ - { - "include": "#attribute-interior" - } - ] - }, - { - "match": "[^\\s>]+", - "name": "invalid.illegal.character-not-allowed-here.html" - } - ] - }, - "tags": { - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#cdata" - }, - { - "captures": { - "0": { - "name": "meta.tag.metadata.svg.$2.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(color-profile|desc|metadata|script|style|title)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.metadata.svg.$2.html" - }, - { - "begin": "(?i)(<)(color-profile|desc|metadata|script|style|title)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.metadata.svg.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "captures": { - "0": { - "name": "meta.tag.structure.svg.$2.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(animateMotion|clipPath|defs|feComponentTransfer|feDiffuseLighting|feMerge|feSpecularLighting|filter|g|hatch|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|pattern|radialGradient|switch|text|textPath)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.structure.svg.$2.html" - }, - { - "begin": "(?i)(<)(animateMotion|clipPath|defs|feComponentTransfer|feDiffuseLighting|feMerge|feSpecularLighting|filter|g|hatch|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|pattern|radialGradient|switch|text|textPath)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.structure.svg.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "captures": { - "0": { - "name": "meta.tag.inline.svg.$2.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(a|animate|discard|feBlend|feColorMatrix|feComposite|feConvolveMatrix|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feMergeNode|feMorphology|feOffset|fePointLight|feSpotLight|feTile|feTurbulence|hatchPath|mpath|set|solidcolor|stop|tspan)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.inline.svg.$2.html" - }, - { - "begin": "(?i)(<)(a|animate|discard|feBlend|feColorMatrix|feComposite|feConvolveMatrix|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feMergeNode|feMorphology|feOffset|fePointLight|feSpotLight|feTile|feTurbulence|hatchPath|mpath|set|solidcolor|stop|tspan)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.inline.svg.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.inline.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "captures": { - "0": { - "name": "meta.tag.object.svg.$2.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(circle|ellipse|feImage|foreignObject|image|line|path|polygon|polyline|rect|symbol|use|view)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.object.svg.$2.html" - }, - { - "begin": "(?i)(<)(a|circle|ellipse|feImage|foreignObject|image|line|path|polygon|polyline|rect|symbol|use|view)(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.object.svg.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "5": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.object.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "captures": { - "0": { - "name": "meta.tag.other.svg.$2.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - }, - "4": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "6": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)((altGlyph|altGlyphDef|altGlyphItem|animateColor|animateTransform|cursor|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|glyph|glyphRef|hkern|missing-glyph|tref|vkern))(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.other.svg.$2.html" - }, - { - "begin": "(?i)(<)((altGlyph|altGlyphDef|altGlyphItem|animateColor|animateTransform|cursor|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|glyph|glyphRef|hkern|missing-glyph|tref|vkern))(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.other.svg.$2.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - }, - "4": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "6": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.other.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "captures": { - "0": { - "name": "meta.tag.other.invalid.void.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.illegal.unrecognized-tag.html" - }, - "4": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "6": { - "name": "punctuation.definition.tag.end.html" - } - }, - "match": "(?i)(<)(([\\w:]+))(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(/>))", - "name": "meta.element.other.invalid.html" - }, - { - "begin": "(?i)(<)((\\w[^\\s>]*))(?=\\s|/?>)(?:(([^\"'>]|\"[^\"]*\"|'[^']*')*)(>))?", - "beginCaptures": { - "0": { - "name": "meta.tag.other.invalid.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.illegal.unrecognized-tag.html" - }, - "4": { - "patterns": [ - { - "include": "#attribute" - } - ] - }, - "6": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(?i)()|(/>)|(?=)\\G", - "end": "(?=/>)|>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.other.invalid.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#tags" - } - ] - }, - { - "include": "#tags-invalid" - } - ] - } - } - }, - "tags-invalid": { - "patterns": [ - { - "begin": "(]*))(?)", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.other.$2.html", - "patterns": [ - { - "include": "#attribute" - } - ] - } - ] - }, - "tags-valid": { - "patterns": [ - { - "begin": "(^[ \\t]+)?(?=<(?i:style)\\b(?!-))", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.embedded.leading.html" - } - }, - "end": "(?!\\G)([ \\t]*$\\n?)?", - "endCaptures": { - "1": { - "name": "punctuation.whitespace.embedded.trailing.html" - } - }, - "patterns": [ - { - "begin": "(?i)(<)(style)(?=\\s|/?>)", - "beginCaptures": { - "0": { - "name": "meta.tag.metadata.style.start.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": "(?i)((<)/)(style)\\s*(>)", - "endCaptures": { - "0": { - "name": "meta.tag.metadata.style.end.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "source.css-ignored-vscode" - }, - "3": { - "name": "entity.name.tag.html" - }, - "4": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.embedded.block.html", - "patterns": [ - { - "begin": "\\G", - "captures": { - "1": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "(>)", - "name": "meta.tag.metadata.style.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?!\\G)", - "end": "(?=)", - "endCaptures": { - "0": { - "name": "meta.tag.metadata.script.end.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.embedded.block.html", - "patterns": [ - { - "begin": "\\G", - "end": "(?=/)", - "patterns": [ - { - "begin": "(>)", - "beginCaptures": { - "0": { - "name": "meta.tag.metadata.script.start.html" - }, - "1": { - "name": "punctuation.definition.tag.end.html" - } - }, - "end": "((<))(?=/(?i:script))", - "endCaptures": { - "0": { - "name": "meta.tag.metadata.script.end.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "source.js-ignored-vscode" - } - }, - "patterns": [ - { - "begin": "\\G", - "end": "(?=\t\t\t\t\t\t\t\t\t\t\t# Tag without type attribute\n\t\t\t\t\t\t\t\t\t\t\t\t | type(?=[\\s=])\n\t\t\t\t\t\t\t\t\t\t\t\t \t(?!\\s*=\\s*\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t''\t\t\t\t\t\t\t\t# Empty\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t | \"\"\t\t\t\t\t\t\t\t\t# Values\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t | ('|\"|)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ttext/\t\t\t\t\t\t\t# Text mime-types\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tjavascript(1\\.[0-5])?\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t | x-javascript\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t | jscript\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t | livescript\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t | (x-)?ecmascript\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t | babel\t\t\t\t\t\t# Javascript variant currently\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t\t\t\t\t\t\t\t# recognized as such\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t | application/\t\t\t\t\t# Application mime-types\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t(x-)?javascript\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t | (x-)?ecmascript\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t | module\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t \t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t[\\s\"'>]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t)", - "name": "meta.tag.metadata.script.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?ix:\n\t\t\t\t\t\t\t\t\t\t\t\t(?=\n\t\t\t\t\t\t\t\t\t\t\t\t\ttype\\s*=\\s*\n\t\t\t\t\t\t\t\t\t\t\t\t\t('|\"|)\n\t\t\t\t\t\t\t\t\t\t\t\t\ttext/\n\t\t\t\t\t\t\t\t\t\t\t\t\t(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tx-handlebars\n\t\t\t\t\t\t\t\t\t\t\t\t\t | (x-(handlebars-)?|ng-)?template\n\t\t\t\t\t\t\t\t\t\t\t\t\t | html\n\t\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t\t\t[\\s\"'>]\n\t\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t\t)", - "end": "((<))(?=/(?i:script))", - "endCaptures": { - "0": { - "name": "meta.tag.metadata.script.end.html" - }, - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "text.html.basic" - } - }, - "patterns": [ - { - "begin": "\\G", - "end": "(>)", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.script.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?!\\G)", - "end": "(?=)", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.script.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?!\\G)", - "end": "(?=)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": "/?>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.$2.void.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)(noscript|title)(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.$2.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.$2.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)(col|hr|input)(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": "/?>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.$2.void.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)(address|article|aside|blockquote|body|button|caption|colgroup|datalist|dd|details|dialog|div|dl|dt|fieldset|figcaption|figure|footer|form|head|header|hgroup|html|h[1-6]|label|legend|li|main|map|menu|meter|nav|ol|optgroup|option|output|p|pre|progress|section|select|slot|summary|table|tbody|td|template|textarea|tfoot|th|thead|tr|ul)(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.$2.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.$2.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)(area|br|wbr)(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": "/?>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.inline.$2.void.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)(a|abbr|b|bdi|bdo|cite|code|data|del|dfn|em|i|ins|kbd|mark|q|rp|rt|ruby|s|samp|small|span|strong|sub|sup|time|u|var)(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.inline.$2.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.inline.$2.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)(embed|img|param|source|track)(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": "/?>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.object.$2.void.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)(audio|canvas|iframe|object|picture|video)(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.object.$2.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.object.$2.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)((basefont|isindex))(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - } - }, - "end": "/?>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.metadata.$2.void.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)((center|frameset|noembed|noframes))(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.$2.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.structure.$2.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)((acronym|big|blink|font|strike|tt|xmp))(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.inline.$2.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.inline.$2.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)((frame))(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - } - }, - "end": "/?>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.object.$2.void.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)((applet))(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.object.$2.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.deprecated.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.object.$2.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)(<)((dir|keygen|listing|menuitem|plaintext|spacer))(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.illegal.no-longer-supported.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.other.$2.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "(?i)()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - }, - "3": { - "name": "invalid.illegal.no-longer-supported.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.other.$2.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "include": "#math" - }, - { - "include": "#svg" - }, - { - "begin": "(<)([a-zA-Z][.0-9_a-zA-Z\\x{00B7}\\x{00C0}-\\x{00D6}\\x{00D8}-\\x{00F6}\\x{00F8}-\\x{037D}\\x{037F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{203F}-\\x{2040}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}]*-[\\-.0-9_a-zA-Z\\x{00B7}\\x{00C0}-\\x{00D6}\\x{00D8}-\\x{00F6}\\x{00F8}-\\x{037D}\\x{037F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{203F}-\\x{2040}\\x{2070}-\\x{218F}\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}]*)(?=\\s|/?>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": "/?>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.custom.start.html", - "patterns": [ - { - "include": "#attribute" - } - ] - }, - { - "begin": "()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": ">", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.end.html" - } - }, - "name": "meta.tag.custom.end.html", - "patterns": [ - { - "include": "#attribute" - } - ] - } - ] - }, - "xml-processing": { - "begin": "(<\\?)(xml)", - "captures": { - "1": { - "name": "punctuation.definition.tag.html" - }, - "2": { - "name": "entity.name.tag.html" - } - }, - "end": "(\\?>)", - "name": "meta.tag.metadata.processing.xml.html", - "patterns": [ - { - "include": "#attribute" - } - ] - } - } -} \ No newline at end of file diff --git a/extensions/image-preview/.eslintrc.json b/extensions/image-preview/.eslintrc.json deleted file mode 100644 index ce28ab7a81..0000000000 --- a/extensions/image-preview/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rules": { - "@typescript-eslint/explicit-function-return-type": ["off"], - "@typescript-eslint/await-thenable": ["off"], - "@typescript-eslint/no-unsafe-assignment": "off" - } -} diff --git a/extensions/image-preview/README.md b/extensions/image-preview/README.md deleted file mode 100644 index d3f0bd6cb6..0000000000 --- a/extensions/image-preview/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Image Preview - -**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. - -## Features - -This extension provides VS Code's built-in image preview functionality. - -Supported image formats: - -- `*.jpg`, `*.jpe`, `*.jpeg` -- `*.png` -- `*.bmp` -- `*.gif` -- `*.ico` -- `*.webp` diff --git a/extensions/image-preview/package.nls.json b/extensions/image-preview/package.nls.json deleted file mode 100644 index d1860bc2fb..0000000000 --- a/extensions/image-preview/package.nls.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "displayName": "Image Preview", - "description": "Provides VS Code's built-in image preview", - "customEditors.displayName": "Image Preview", - "command.zoomIn": "Zoom in", - "command.zoomOut": "Zoom out" -} diff --git a/extensions/image-preview/src/extension.ts b/extensions/image-preview/src/extension.ts deleted file mode 100644 index 424362a93c..0000000000 --- a/extensions/image-preview/src/extension.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; -import { PreviewManager } from './preview'; -import { SizeStatusBarEntry } from './sizeStatusBarEntry'; -import { ZoomStatusBarEntry } from './zoomStatusBarEntry'; - -export function activate(context: vscode.ExtensionContext) { - const sizeStatusBarEntry = new SizeStatusBarEntry(); - context.subscriptions.push(sizeStatusBarEntry); - - const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry(); - context.subscriptions.push(binarySizeStatusBarEntry); - - const zoomStatusBarEntry = new ZoomStatusBarEntry(); - context.subscriptions.push(zoomStatusBarEntry); - - const previewManager = new PreviewManager(context.extensionUri, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry); - - context.subscriptions.push(vscode.window.registerCustomEditorProvider(PreviewManager.viewType, previewManager, { - supportsMultipleEditorsPerDocument: true, - })); - - context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => { - previewManager.activePreview?.zoomIn(); - })); - - context.subscriptions.push(vscode.commands.registerCommand('imagePreview.zoomOut', () => { - previewManager.activePreview?.zoomOut(); - })); -} diff --git a/extensions/image-preview/src/preview.ts b/extensions/image-preview/src/preview.ts deleted file mode 100644 index e3425b9641..0000000000 --- a/extensions/image-preview/src/preview.ts +++ /dev/null @@ -1,275 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; -import { Disposable } from './dispose'; -import { SizeStatusBarEntry } from './sizeStatusBarEntry'; -import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry'; -import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; - -const localize = nls.loadMessageBundle(); - -export class PreviewManager implements vscode.CustomReadonlyEditorProvider { - - public static readonly viewType = 'imagePreview.previewEditor'; - - private readonly _previews = new Set(); - private _activePreview: Preview | undefined; - - constructor( - private readonly extensionRoot: vscode.Uri, - private readonly sizeStatusBarEntry: SizeStatusBarEntry, - private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, - private readonly zoomStatusBarEntry: ZoomStatusBarEntry, - ) { } - - public async openCustomDocument(uri: vscode.Uri) { - return { uri, dispose: () => { } }; - } - - public async resolveCustomEditor( - document: vscode.CustomDocument, - webviewEditor: vscode.WebviewPanel, - ): Promise { - const preview = new Preview(this.extensionRoot, document.uri, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); - this._previews.add(preview); - this.setActivePreview(preview); - - webviewEditor.onDidDispose(() => { this._previews.delete(preview); }); - - webviewEditor.onDidChangeViewState(() => { - if (webviewEditor.active) { - this.setActivePreview(preview); - } else if (this._activePreview === preview && !webviewEditor.active) { - this.setActivePreview(undefined); - } - }); - } - - public get activePreview() { return this._activePreview; } - - private setActivePreview(value: Preview | undefined): void { - this._activePreview = value; - this.setPreviewActiveContext(!!value); - } - - private setPreviewActiveContext(value: boolean) { - vscode.commands.executeCommand('setContext', 'imagePreviewFocus', value); - } -} - -const enum PreviewState { - Disposed, - Visible, - Active, -} - -class Preview extends Disposable { - - private readonly id: string = `${Date.now()}-${Math.random().toString()}`; - - private _previewState = PreviewState.Visible; - private _imageSize: string | undefined; - private _imageBinarySize: number | undefined; - private _imageZoom: Scale | undefined; - - private readonly emptyPngDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='; - - constructor( - private readonly extensionRoot: vscode.Uri, - private readonly resource: vscode.Uri, - private readonly webviewEditor: vscode.WebviewPanel, - private readonly sizeStatusBarEntry: SizeStatusBarEntry, - private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, - private readonly zoomStatusBarEntry: ZoomStatusBarEntry, - ) { - super(); - const resourceRoot = resource.with({ - path: resource.path.replace(/\/[^\/]+?\.\w+$/, '/'), - }); - - webviewEditor.webview.options = { - enableScripts: true, - enableForms: false, - localResourceRoots: [ - resourceRoot, - extensionRoot, - ] - }; - - this._register(webviewEditor.webview.onDidReceiveMessage(message => { - switch (message.type) { - case 'size': - { - this._imageSize = message.value; - this.update(); - break; - } - case 'zoom': - { - this._imageZoom = message.value; - this.update(); - break; - } - - case 'reopen-as-text': - { - vscode.commands.executeCommand('vscode.openWith', resource, 'default', webviewEditor.viewColumn); - break; - } - } - })); - - this._register(zoomStatusBarEntry.onDidChangeScale(e => { - if (this._previewState === PreviewState.Active) { - this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale }); - } - })); - - this._register(webviewEditor.onDidChangeViewState(() => { - this.update(); - this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); - })); - - this._register(webviewEditor.onDidDispose(() => { - if (this._previewState === PreviewState.Active) { - this.sizeStatusBarEntry.hide(this.id); - this.binarySizeStatusBarEntry.hide(this.id); - this.zoomStatusBarEntry.hide(this.id); - } - this._previewState = PreviewState.Disposed; - })); - - const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*'))); - this._register(watcher.onDidChange(e => { - if (e.toString() === this.resource.toString()) { - this.render(); - } - })); - this._register(watcher.onDidDelete(e => { - if (e.toString() === this.resource.toString()) { - this.webviewEditor.dispose(); - } - })); - - vscode.workspace.fs.stat(resource).then(({ size }) => { - this._imageBinarySize = size; - this.update(); - }); - - this.render(); - this.update(); - this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); - } - - public zoomIn() { - if (this._previewState === PreviewState.Active) { - this.webviewEditor.webview.postMessage({ type: 'zoomIn' }); - } - } - - public zoomOut() { - if (this._previewState === PreviewState.Active) { - this.webviewEditor.webview.postMessage({ type: 'zoomOut' }); - } - } - - private async render() { - if (this._previewState !== PreviewState.Disposed) { - this.webviewEditor.webview.html = await this.getWebviewContents(); - } - } - - private update() { - if (this._previewState === PreviewState.Disposed) { - return; - } - - if (this.webviewEditor.active) { - this._previewState = PreviewState.Active; - this.sizeStatusBarEntry.show(this.id, this._imageSize || ''); - this.binarySizeStatusBarEntry.show(this.id, this._imageBinarySize); - this.zoomStatusBarEntry.show(this.id, this._imageZoom || 'fit'); - } else { - if (this._previewState === PreviewState.Active) { - this.sizeStatusBarEntry.hide(this.id); - this.binarySizeStatusBarEntry.hide(this.id); - this.zoomStatusBarEntry.hide(this.id); - } - this._previewState = PreviewState.Visible; - } - } - - private async getWebviewContents(): Promise { - const version = Date.now().toString(); - const settings = { - src: await this.getResourcePath(this.webviewEditor, this.resource, version), - }; - - const nonce = getNonce(); - - const cspSource = this.webviewEditor.webview.cspSource; - return /* html */` - - - - - - - - Image Preview - - - - - - - -

- - - -`; - } - - private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise { - if (resource.scheme === 'git') { - const stat = await vscode.workspace.fs.stat(resource); - if (stat.size === 0) { - return this.emptyPngDataUri; - } - } - - // Avoid adding cache busting if there is already a query string - if (resource.query) { - return webviewEditor.webview.asWebviewUri(resource).toString(); - } - return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(); - } - - private extensionResource(path: string) { - return this.webviewEditor.webview.asWebviewUri(this.extensionRoot.with({ - path: this.extensionRoot.path + path - })); - } -} - -function escapeAttribute(value: string | vscode.Uri): string { - return value.toString().replace(/"/g, '"'); -} - -function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/image-preview/yarn.lock b/extensions/image-preview/yarn.lock deleted file mode 100644 index da44f29eaa..0000000000 --- a/extensions/image-preview/yarn.lock +++ /dev/null @@ -1,52 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" - integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== - dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/1ds-post-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" - integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== - dependencies: - "@microsoft/1ds-core-js" "3.2.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== - dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" - -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== - -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== - -"@vscode/extension-telemetry@0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" - integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== - dependencies: - "@microsoft/1ds-core-js" "^3.2.3" - "@microsoft/1ds-post-js" "^3.2.3" - -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== diff --git a/extensions/integration-tests/.vscode/launch.json b/extensions/integration-tests/.vscode/launch.json deleted file mode 100644 index f858d00591..0000000000 --- a/extensions/integration-tests/.vscode/launch.json +++ /dev/null @@ -1,17 +0,0 @@ -// A launch configuration that compiles the extension and then opens it inside a new window -{ - "version": "0.1.0", - "configurations": [ - { - "name": "Launch Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": ["${workspaceFolder}/../../", "${workspaceFolder}/test", "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out" ], - "stopOnEntry": false, - "sourceMaps": true, - "outDir": "${workspaceFolder}/out", - "preLaunchTask": "npm" - } - ] -} diff --git a/extensions/ipynb/.vscode/launch.json b/extensions/ipynb/.vscode/launch.json new file mode 100644 index 0000000000..30130a429d --- /dev/null +++ b/extensions/ipynb/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "name": "Launch Extension", + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "request": "launch", + "type": "extensionHost" + } + ] +} \ No newline at end of file diff --git a/extensions/ipynb/.vscodeignore b/extensions/ipynb/.vscodeignore index f45314d0c1..69a1b29e0c 100644 --- a/extensions/ipynb/.vscodeignore +++ b/extensions/ipynb/.vscodeignore @@ -1,5 +1,6 @@ .vscode/** src/** +notebook-src/** out/** tsconfig.json extension.webpack.config.js diff --git a/extensions/ipynb/esbuild.js b/extensions/ipynb/esbuild.js index 182f55ef6d..bb35628d01 100644 --- a/extensions/ipynb/esbuild.js +++ b/extensions/ipynb/esbuild.js @@ -18,7 +18,7 @@ if (outputRootIndex >= 0) { outputRoot = args[outputRootIndex + 1]; } -const srcDir = path.join(__dirname, 'src'); +const srcDir = path.join(__dirname, 'notebook-src'); const outDir = path.join(outputRoot, 'notebook-out'); async function build() { @@ -41,7 +41,11 @@ build().catch(() => process.exit(1)); if (isWatch) { const watcher = require('@parcel/watcher'); - watcher.subscribe(srcDir, () => { - return build(); + watcher.subscribe(srcDir, async () => { + try { + await build(); + } catch (e) { + console.error(e); + } }); } diff --git a/extensions/ipynb/src/cellAttachmentRenderer.ts b/extensions/ipynb/notebook-src/cellAttachmentRenderer.ts similarity index 82% rename from extensions/ipynb/src/cellAttachmentRenderer.ts rename to extensions/ipynb/notebook-src/cellAttachmentRenderer.ts index 844e2293a0..27cc1d3ea3 100644 --- a/extensions/ipynb/src/cellAttachmentRenderer.ts +++ b/extensions/ipynb/notebook-src/cellAttachmentRenderer.ts @@ -22,9 +22,9 @@ export async function activate(ctx: RendererContext) { md.renderer.rules.image = (tokens: MarkdownItToken[], idx: number, options, env, self) => { const token = tokens[idx]; const src = token.attrGet('src'); - const attachments: Record> = env.outputItem.metadata?.custom?.attachments; // this stores attachment entries for every image in the cell - if (attachments && src) { - const imageAttachment = attachments[src.replace('attachment:', '')]; + const attachments: Record> | undefined = env.outputItem.metadata?.attachments; + if (attachments && src && src.startsWith('attachment:')) { + const imageAttachment = attachments[tryDecodeURIComponent(src.replace('attachment:', ''))]; if (imageAttachment) { // objEntries will always be length 1, with objEntries[0] holding [0]=mime,[1]=b64 // if length = 0, something is wrong with the attachment, mime/b64 weren't copied over @@ -45,3 +45,11 @@ export async function activate(ctx: RendererContext) { }; }); } + +function tryDecodeURIComponent(uri: string) { + try { + return decodeURIComponent(uri); + } catch { + return uri; + } +} diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index 29c7742e41..153e8a9e34 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -9,11 +9,12 @@ "vscode": "^1.57.0" }, "enabledApiProposals": [ - "notebookEditor", - "notebookEditorEdit" + "documentPaste", + "diffContentOptions", + "dropMetadata" ], "activationEvents": [ - "*" + "onNotebook:jupyter-notebook" ], "extensionKind": [ "workspace", @@ -28,16 +29,32 @@ } }, "contributes": { + "configuration": [ + { + "properties": { + "ipynb.pasteImagesAsAttachments.enabled": { + "type": "boolean", + "scope": "resource", + "markdownDescription": "%ipynb.pasteImagesAsAttachments.enabled%", + "default": true + } + } + } + ], "commands": [ { "command": "ipynb.newUntitledIpynb", - "title": "New Jupyter Notebook", - "shortTitle": "Jupyter Notebook", + "title": "%newUntitledIpynb.title%", + "shortTitle": "%newUntitledIpynb.shortTitle%", "category": "Create" }, { "command": "ipynb.openIpynbInNotebookEditor", - "title": "Open ipynb file in notebook editor" + "title": "%openIpynbInNotebookEditor.title%" + }, + { + "command": "ipynb.cleanInvalidImageAttachment", + "title": "%cleanInvalidImageAttachment.title%" } ], "notebooks": [ @@ -52,16 +69,16 @@ "priority": "default" } ], - "notebookRenderer": [ - { - "id": "vscode.markdown-it-cell-attachment-renderer", - "displayName": "Markdown it ipynb Cell Attachment renderer", - "entrypoint": { - "extends": "vscode.markdown-it-renderer", - "path": "./notebook-out/cellAttachmentRenderer.js" - } - } - ], + "notebookRenderer": [ + { + "id": "vscode.markdown-it-cell-attachment-renderer", + "displayName": "%markdownAttachmentRenderer.displayName%", + "entrypoint": { + "extends": "vscode.markdown-it-renderer", + "path": "./notebook-out/cellAttachmentRenderer.js" + } + } + ], "menus": { "file/newFile": [ { @@ -76,6 +93,10 @@ { "command": "ipynb.openIpynbInNotebookEditor", "when": "false" + }, + { + "command": "ipynb.cleanInvalidImageAttachment", + "when": "false" } ] } diff --git a/extensions/ipynb/package.nls.json b/extensions/ipynb/package.nls.json index 6f2f7e47c0..bd8e0ab1da 100644 --- a/extensions/ipynb/package.nls.json +++ b/extensions/ipynb/package.nls.json @@ -1,4 +1,15 @@ { - "displayName": ".ipynb support", - "description": "Provides basic support for opening and reading Jupyter's .ipynb notebook files" + "displayName": ".ipynb Support", + "description": "Provides basic support for opening and reading Jupyter's .ipynb notebook files", + "ipynb.pasteImagesAsAttachments.enabled": "Enable/disable pasting of images into Markdown cells in ipynb notebook files. Pasted images are inserted as attachments to the cell.", + "newUntitledIpynb.title": "New Jupyter Notebook", + "newUntitledIpynb.shortTitle": "Jupyter Notebook", + "openIpynbInNotebookEditor.title": "Open IPYNB File In Notebook Editor", + "cleanInvalidImageAttachment.title": "Clean Invalid Image Attachment Reference", + "markdownAttachmentRenderer.displayName": { + "message": "Markdown-It ipynb Cell Attachment renderer", + "comment": [ + "Markdown-It is a product name and should not be translated" + ] + } } diff --git a/extensions/ipynb/src/cellIdService.ts b/extensions/ipynb/src/cellIdService.ts index 6a0e5a8acf..02698e48a0 100644 --- a/extensions/ipynb/src/cellIdService.ts +++ b/extensions/ipynb/src/cellIdService.ts @@ -3,12 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, NotebookDocument, NotebookDocumentChangeEvent, workspace, WorkspaceEdit } from 'vscode'; +import { ExtensionContext, NotebookDocument, NotebookDocumentChangeEvent, NotebookEdit, workspace, WorkspaceEdit } from 'vscode'; import { v4 as uuid } from 'uuid'; import { getCellMetadata } from './serializers'; import { CellMetadata } from './common'; import { getNotebookMetadata } from './notebookSerializer'; -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; /** * Ensure all new cells in notebooks with nbformat >= 4.5 have an id. @@ -34,7 +34,7 @@ function onDidChangeNotebookCells(e: NotebookDocumentChangeEvent) { // Don't edit the metadata directly, always get a clone (prevents accidental singletons and directly editing the objects). const updatedMetadata: CellMetadata = { ...JSON.parse(JSON.stringify(cellMetadata || {})) }; updatedMetadata.id = id; - edit.replaceNotebookCellMetadata(cell.notebook.uri, cell.index, { ...(cell.metadata), custom: updatedMetadata }); + edit.set(cell.notebook.uri, [NotebookEdit.updateCellMetadata(cell.index, { ...(cell.metadata), custom: updatedMetadata })]); workspace.applyEdit(edit); }); }); diff --git a/extensions/ipynb/src/common.ts b/extensions/ipynb/src/common.ts index b343037aa5..281b0cb092 100644 --- a/extensions/ipynb/src/common.ts +++ b/extensions/ipynb/src/common.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; /** * Metadata we store in VS Code cell output items. @@ -44,7 +44,7 @@ export interface CellOutputMetadata { /** * Metadata we store in VS Code cells. - * This contains the original metadata from the Jupyuter cells. + * This contains the original metadata from the Jupyter cells. */ export interface CellMetadata { /** diff --git a/extensions/ipynb/src/constants.ts b/extensions/ipynb/src/constants.ts index 53d52e0858..9aad9b244c 100644 --- a/extensions/ipynb/src/constants.ts +++ b/extensions/ipynb/src/constants.ts @@ -3,4 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + export const defaultNotebookFormat = { major: 4, minor: 2 }; +export const ATTACHMENT_CLEANUP_COMMANDID = 'ipynb.cleanInvalidImageAttachment'; + +export const JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR: vscode.DocumentSelector = { notebookType: 'jupyter-notebook', language: 'markdown' }; diff --git a/extensions/ipynb/src/deserializers.ts b/extensions/ipynb/src/deserializers.ts index 52b307eaa9..0e49024985 100644 --- a/extensions/ipynb/src/deserializers.ts +++ b/extensions/ipynb/src/deserializers.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; import { extensions, NotebookCellData, NotebookCellExecutionSummary, NotebookCellKind, NotebookCellOutput, NotebookCellOutputItem, NotebookData } from 'vscode'; import { CellMetadata, CellOutputMetadata } from './common'; @@ -149,21 +149,29 @@ function convertJupyterOutputToBuffer(mime: string, value: unknown): NotebookCel } } -function getNotebookCellMetadata(cell: nbformat.IBaseCell): CellMetadata { +function getNotebookCellMetadata(cell: nbformat.IBaseCell): { + [key: string]: any; +} { + const cellMetadata: { [key: string]: any } = {}; // We put this only for VSC to display in diff view. // Else we don't use this. - const propertiesToClone: (keyof CellMetadata)[] = ['metadata', 'attachments']; const custom: CellMetadata = {}; - propertiesToClone.forEach((propertyToClone) => { - if (cell[propertyToClone]) { - custom[propertyToClone] = JSON.parse(JSON.stringify(cell[propertyToClone])); - } - }); + if (cell['metadata']) { + custom['metadata'] = JSON.parse(JSON.stringify(cell['metadata'])); + } + if ('id' in cell && typeof cell.id === 'string') { custom.id = cell.id; } - return custom; + + cellMetadata.custom = custom; + + if (cell['attachments']) { + cellMetadata.attachments = JSON.parse(JSON.stringify(cell['attachments'])); + } + return cellMetadata; } + function getOutputMetadata(output: nbformat.IOutput): CellOutputMetadata { // Add on transient data if we have any. This should be removed by our save functions elsewhere. const metadata: CellOutputMetadata = { @@ -284,7 +292,7 @@ export function jupyterCellOutputToCellOutput(output: nbformat.IOutput): Noteboo function createNotebookCellDataFromRawCell(cell: nbformat.IRawCell): NotebookCellData { const cellData = new NotebookCellData(NotebookCellKind.Code, concatMultilineString(cell.source), 'raw'); cellData.outputs = []; - cellData.metadata = { custom: getNotebookCellMetadata(cell) }; + cellData.metadata = getNotebookCellMetadata(cell); return cellData; } function createNotebookCellDataFromMarkdownCell(cell: nbformat.IMarkdownCell): NotebookCellData { @@ -294,7 +302,7 @@ function createNotebookCellDataFromMarkdownCell(cell: nbformat.IMarkdownCell): N 'markdown' ); cellData.outputs = []; - cellData.metadata = { custom: getNotebookCellMetadata(cell) }; + cellData.metadata = getNotebookCellMetadata(cell); return cellData; } function createNotebookCellDataFromCodeCell(cell: nbformat.ICodeCell, cellLanguage: string): NotebookCellData { @@ -313,7 +321,7 @@ function createNotebookCellDataFromCodeCell(cell: nbformat.ICodeCell, cellLangua const cellData = new NotebookCellData(NotebookCellKind.Code, source, cellLanguageId); cellData.outputs = outputs; - cellData.metadata = { custom: getNotebookCellMetadata(cell) }; + cellData.metadata = getNotebookCellMetadata(cell); cellData.executionSummary = executionSummary; return cellData; } diff --git a/extensions/ipynb/src/helper.ts b/extensions/ipynb/src/helper.ts new file mode 100644 index 0000000000..1af1af2173 --- /dev/null +++ b/extensions/ipynb/src/helper.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function deepClone(obj: T): T { + if (!obj || typeof obj !== 'object') { + return obj; + } + if (obj instanceof RegExp) { + // See https://github.com/microsoft/TypeScript/issues/10990 + return obj as any; + } + const result: any = Array.isArray(obj) ? [] : {}; + Object.keys(obj).forEach((key: string) => { + if ((obj)[key] && typeof (obj)[key] === 'object') { + result[key] = deepClone((obj)[key]); + } else { + result[key] = (obj)[key]; + } + }); + return result; +} + +// from https://github.com/microsoft/vscode/blob/43ae27a30e7b5e8711bf6b218ee39872ed2b8ef6/src/vs/base/common/objects.ts#L117 +export function objectEquals(one: any, other: any) { + if (one === other) { + return true; + } + if (one === null || one === undefined || other === null || other === undefined) { + return false; + } + if (typeof one !== typeof other) { + return false; + } + if (typeof one !== 'object') { + return false; + } + if ((Array.isArray(one)) !== (Array.isArray(other))) { + return false; + } + + let i: number; + let key: string; + + if (Array.isArray(one)) { + if (one.length !== other.length) { + return false; + } + for (i = 0; i < one.length; i++) { + if (!objectEquals(one[i], other[i])) { + return false; + } + } + } else { + const oneKeys: string[] = []; + + for (key in one) { + oneKeys.push(key); + } + oneKeys.sort(); + const otherKeys: string[] = []; + for (key in other) { + otherKeys.push(key); + } + otherKeys.sort(); + if (!objectEquals(oneKeys, otherKeys)) { + return false; + } + for (i = 0; i < oneKeys.length; i++) { + if (!objectEquals(one[oneKeys[i]], other[oneKeys[i]])) { + return false; + } + } + } + + return true; +} + +/** + * A helper to delay/debounce execution of a task, includes cancellation/disposal support. + * Pulled from https://github.com/microsoft/vscode/blob/3059063b805ed0ac10a6d9539e213386bfcfb852/extensions/markdown-language-features/src/util/async.ts + */ +export class Delayer { + + public defaultDelay: number; + private _timeout: any; // Timer + private _cancelTimeout: Promise | null; + private _onSuccess: ((value: T | PromiseLike | undefined) => void) | null; + private _task: ITask | null; + + constructor(defaultDelay: number) { + this.defaultDelay = defaultDelay; + this._timeout = null; + this._cancelTimeout = null; + this._onSuccess = null; + this._task = null; + } + + dispose() { + this._doCancelTimeout(); + } + + public trigger(task: ITask, delay: number = this.defaultDelay): Promise { + this._task = task; + if (delay >= 0) { + this._doCancelTimeout(); + } + + if (!this._cancelTimeout) { + this._cancelTimeout = new Promise((resolve) => { + this._onSuccess = resolve; + }).then(() => { + this._cancelTimeout = null; + this._onSuccess = null; + const result = this._task && this._task?.(); + this._task = null; + return result; + }); + } + + if (delay >= 0 || this._timeout === null) { + this._timeout = setTimeout(() => { + this._timeout = null; + this._onSuccess?.(undefined); + }, delay >= 0 ? delay : this.defaultDelay); + } + + return this._cancelTimeout; + } + + private _doCancelTimeout(): void { + if (this._timeout !== null) { + clearTimeout(this._timeout); + this._timeout = null; + } + } +} + +export interface ITask { + (): T; +} diff --git a/extensions/ipynb/src/ipynbMain.ts b/extensions/ipynb/src/ipynbMain.ts index d111446b9f..e90376f9e0 100644 --- a/extensions/ipynb/src/ipynbMain.ts +++ b/extensions/ipynb/src/ipynbMain.ts @@ -4,8 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { ensureAllNewCellsHaveCellIds } from './cellIdService'; import { NotebookSerializer } from './notebookSerializer'; +import { ensureAllNewCellsHaveCellIds } from './cellIdService'; +import { notebookImagePasteSetup } from './notebookImagePaste'; +import { AttachmentCleaner } from './notebookAttachmentCleaner'; // From {nbformat.INotebookMetadata} in @jupyterlab/coreutils type NotebookMetadata = { @@ -33,9 +35,13 @@ export function activate(context: vscode.ExtensionContext) { transientOutputs: false, transientCellMetadata: { breakpointMargin: true, - custom: false + custom: false, + attachments: false + }, + cellContentMetadata: { + attachments: true } - })); + } as vscode.NotebookDocumentContentOptions)); vscode.languages.registerCodeLensProvider({ pattern: '**/*.ipynb' }, { provideCodeLenses: (document) => { @@ -77,12 +83,21 @@ export function activate(context: vscode.ExtensionContext) { await vscode.window.showNotebookDocument(document); })); + context.subscriptions.push(notebookImagePasteSetup()); + + const enabled = vscode.workspace.getConfiguration('ipynb').get('pasteImagesAsAttachments.enabled', false); + if (enabled) { + const cleaner = new AttachmentCleaner(); + context.subscriptions.push(cleaner); + } + // Update new file contribution vscode.extensions.onDidChange(() => { vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter')); }); vscode.commands.executeCommand('setContext', 'jupyterEnabled', vscode.extensions.getExtension('ms-toolsai.jupyter')); + return { exportNotebook: (notebook: vscode.NotebookData): string => { return exportNotebook(notebook, serializer); @@ -94,7 +109,7 @@ export function activate(context: vscode.ExtensionContext) { } const edit = new vscode.WorkspaceEdit(); - edit.replaceNotebookMetadata(resource, { + edit.set(resource, [vscode.NotebookEdit.updateNotebookMetadata({ ...document.metadata, custom: { ...(document.metadata.custom ?? {}), @@ -103,7 +118,7 @@ export function activate(context: vscode.ExtensionContext) { ...metadata }, } - }); + })]); return vscode.workspace.applyEdit(edit); }, }; diff --git a/extensions/ipynb/src/notebookAttachmentCleaner.ts b/extensions/ipynb/src/notebookAttachmentCleaner.ts new file mode 100644 index 0000000000..6018c708a4 --- /dev/null +++ b/extensions/ipynb/src/notebookAttachmentCleaner.ts @@ -0,0 +1,391 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { ATTACHMENT_CLEANUP_COMMANDID, JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR } from './constants'; +import { deepClone, objectEquals, Delayer } from './helper'; + +interface AttachmentCleanRequest { + notebook: vscode.NotebookDocument; + document: vscode.TextDocument; + cell: vscode.NotebookCell; +} + +interface IAttachmentData { + [key: string /** mimetype */]: string;/** b64-encoded */ +} + +interface IAttachmentDiagnostic { + name: string; + ranges: vscode.Range[]; +} + +export enum DiagnosticCode { + missing_attachment = 'notebook.missing-attachment' +} + +export class AttachmentCleaner implements vscode.CodeActionProvider { + private _attachmentCache: + Map>> = new Map(); + + private _disposables: vscode.Disposable[]; + private _imageDiagnosticCollection: vscode.DiagnosticCollection; + private readonly _delayer = new Delayer(750); + + constructor() { + this._disposables = []; + this._imageDiagnosticCollection = vscode.languages.createDiagnosticCollection('Notebook Image Attachment'); + this._disposables.push(this._imageDiagnosticCollection); + + this._disposables.push(vscode.commands.registerCommand(ATTACHMENT_CLEANUP_COMMANDID, async (document: vscode.Uri, range: vscode.Range) => { + const workspaceEdit = new vscode.WorkspaceEdit(); + workspaceEdit.delete(document, range); + await vscode.workspace.applyEdit(workspaceEdit); + })); + + this._disposables.push(vscode.languages.registerCodeActionsProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, this, { + providedCodeActionKinds: [ + vscode.CodeActionKind.QuickFix + ], + })); + + this._disposables.push(vscode.workspace.onDidChangeNotebookDocument(e => { + this._delayer.trigger(() => { + + e.cellChanges.forEach(change => { + if (!change.document) { + return; + } + + if (change.cell.kind !== vscode.NotebookCellKind.Markup) { + return; + } + + const metadataEdit = this.cleanNotebookAttachments({ + notebook: e.notebook, + cell: change.cell, + document: change.document + }); + if (metadataEdit) { + const workspaceEdit = new vscode.WorkspaceEdit(); + workspaceEdit.set(e.notebook.uri, [metadataEdit]); + vscode.workspace.applyEdit(workspaceEdit); + } + }); + }); + })); + + + this._disposables.push(vscode.workspace.onWillSaveNotebookDocument(e => { + if (e.reason === vscode.TextDocumentSaveReason.Manual) { + this._delayer.dispose(); + + e.waitUntil(new Promise((resolve) => { + if (e.notebook.getCells().length === 0) { + return; + } + + const notebookEdits: vscode.NotebookEdit[] = []; + for (const cell of e.notebook.getCells()) { + if (cell.kind !== vscode.NotebookCellKind.Markup) { + continue; + } + + const metadataEdit = this.cleanNotebookAttachments({ + notebook: e.notebook, + cell: cell, + document: cell.document + }); + + if (metadataEdit) { + notebookEdits.push(metadataEdit); + } + } + + const workspaceEdit = new vscode.WorkspaceEdit(); + workspaceEdit.set(e.notebook.uri, notebookEdits); + + resolve(workspaceEdit); + })); + } + })); + + this._disposables.push(vscode.workspace.onDidCloseNotebookDocument(e => { + this._attachmentCache.delete(e.uri.toString()); + })); + + this._disposables.push(vscode.workspace.onWillRenameFiles(e => { + const re = /\.ipynb$/; + for (const file of e.files) { + if (!re.exec(file.oldUri.toString())) { + continue; + } + + // transfer cache to new uri + if (this._attachmentCache.has(file.oldUri.toString())) { + this._attachmentCache.set(file.newUri.toString(), this._attachmentCache.get(file.oldUri.toString())!); + this._attachmentCache.delete(file.oldUri.toString()); + } + } + })); + + this._disposables.push(vscode.workspace.onDidOpenTextDocument(e => { + this.analyzeMissingAttachments(e); + })); + + this._disposables.push(vscode.workspace.onDidCloseTextDocument(e => { + this.analyzeMissingAttachments(e); + })); + + vscode.workspace.textDocuments.forEach(document => { + this.analyzeMissingAttachments(document); + }); + } + + provideCodeActions(document: vscode.TextDocument, _range: vscode.Range | vscode.Selection, context: vscode.CodeActionContext, _token: vscode.CancellationToken): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> { + const fixes: vscode.CodeAction[] = []; + + for (const diagnostic of context.diagnostics) { + switch (diagnostic.code) { + case DiagnosticCode.missing_attachment: + { + const fix = new vscode.CodeAction( + 'Remove invalid image attachment reference', + vscode.CodeActionKind.QuickFix); + + fix.command = { + command: ATTACHMENT_CLEANUP_COMMANDID, + title: 'Remove invalid image attachment reference', + arguments: [document.uri, diagnostic.range], + }; + fixes.push(fix); + } + break; + } + } + + return fixes; + } + + /** + * take in a NotebookDocumentChangeEvent, and clean the attachment data for the cell(s) that have had their markdown source code changed + * @param e NotebookDocumentChangeEvent from the onDidChangeNotebookDocument listener + * @returns vscode.NotebookEdit, the metadata alteration performed on the json behind the ipynb + */ + private cleanNotebookAttachments(e: AttachmentCleanRequest): vscode.NotebookEdit | undefined { + + if (e.notebook.isClosed) { + return; + } + const document = e.document; + const cell = e.cell; + + const markdownAttachmentsInUse: { [key: string /** filename */]: IAttachmentData } = {}; + const cellFragment = cell.document.uri.fragment; + const notebookUri = e.notebook.uri.toString(); + const diagnostics: IAttachmentDiagnostic[] = []; + const markdownAttachmentsRefedInCell = this.getAttachmentNames(document); + + if (markdownAttachmentsRefedInCell.size === 0) { + // no attachments used in this cell, cache all images from cell metadata + this.saveAllAttachmentsToCache(cell.metadata, notebookUri, cellFragment); + } + + if (this.checkMetadataHasAttachmentsField(cell.metadata)) { + // the cell metadata contains attachments, check if any are used in the markdown source + + for (const [currFilename, attachment] of Object.entries(cell.metadata.attachments)) { + // means markdown reference is present in the metadata, rendering will work properly + // therefore, we don't need to check it in the next loop either + if (markdownAttachmentsRefedInCell.has(currFilename)) { + // attachment reference is present in the markdown source, no need to cache it + markdownAttachmentsRefedInCell.get(currFilename)!.valid = true; + markdownAttachmentsInUse[currFilename] = attachment as IAttachmentData; + } else { + // attachment reference is not present in the markdown source, cache it + this.saveAttachmentToCache(notebookUri, cellFragment, currFilename, cell.metadata); + } + } + } + + for (const [currFilename, attachment] of markdownAttachmentsRefedInCell) { + if (attachment.valid) { + // attachment reference is present in both the markdown source and the metadata, no op + continue; + } + + // if image is referenced in markdown source but not in metadata -> check if we have image in the cache + const cachedImageAttachment = this._attachmentCache.get(notebookUri)?.get(cellFragment)?.get(currFilename); + if (cachedImageAttachment) { + markdownAttachmentsInUse[currFilename] = cachedImageAttachment; + this._attachmentCache.get(notebookUri)?.get(cellFragment)?.delete(currFilename); + } else { + // if image is not in the cache, show warning + diagnostics.push({ name: currFilename, ranges: attachment.ranges }); + } + } + + this.updateDiagnostics(cell.document.uri, diagnostics); + + if (cell.index > -1 && !objectEquals(markdownAttachmentsInUse, cell.metadata.attachments)) { + const updateMetadata: { [key: string]: any } = deepClone(cell.metadata); + updateMetadata.attachments = markdownAttachmentsInUse; + const metadataEdit = vscode.NotebookEdit.updateCellMetadata(cell.index, updateMetadata); + + return metadataEdit; + } + return; + } + + private analyzeMissingAttachments(document: vscode.TextDocument): void { + if (document.uri.scheme !== 'vscode-notebook-cell') { + // not notebook + return; + } + + if (document.isClosed) { + this.updateDiagnostics(document.uri, []); + return; + } + + let notebook: vscode.NotebookDocument | undefined; + let activeCell: vscode.NotebookCell | undefined; + for (const notebookDocument of vscode.workspace.notebookDocuments) { + const cell = notebookDocument.getCells().find(cell => cell.document === document); + if (cell) { + notebook = notebookDocument; + activeCell = cell; + break; + } + } + + if (!notebook || !activeCell) { + return; + } + + const diagnostics: IAttachmentDiagnostic[] = []; + const markdownAttachments = this.getAttachmentNames(document); + if (this.checkMetadataHasAttachmentsField(activeCell.metadata)) { + for (const [currFilename, attachment] of markdownAttachments) { + if (!activeCell.metadata.attachments[currFilename]) { + // no attachment reference in the metadata + diagnostics.push({ name: currFilename, ranges: attachment.ranges }); + } + } + } + + this.updateDiagnostics(activeCell.document.uri, diagnostics); + } + + private updateDiagnostics(cellUri: vscode.Uri, diagnostics: IAttachmentDiagnostic[]) { + const vscodeDiagnostics: vscode.Diagnostic[] = []; + for (const currDiagnostic of diagnostics) { + currDiagnostic.ranges.forEach(range => { + const diagnostic = new vscode.Diagnostic(range, `The image named: '${currDiagnostic.name}' is not present in cell metadata.`, vscode.DiagnosticSeverity.Warning); + diagnostic.code = DiagnosticCode.missing_attachment; + vscodeDiagnostics.push(diagnostic); + }); + } + + this._imageDiagnosticCollection.set(cellUri, vscodeDiagnostics); + } + + /** + * remove attachment from metadata and add it to the cache + * @param notebookUri uri of the notebook currently being edited + * @param cellFragment fragment of the cell currently being edited + * @param currFilename filename of the image being pulled into the cell + * @param metadata metadata of the cell currently being edited + */ + private saveAttachmentToCache(notebookUri: string, cellFragment: string, currFilename: string, metadata: { [key: string]: any }): void { + const documentCache = this._attachmentCache.get(notebookUri); + if (!documentCache) { + // no cache for this notebook yet + const cellCache = new Map(); + cellCache.set(currFilename, this.getMetadataAttachment(metadata, currFilename)); + const documentCache = new Map(); + documentCache.set(cellFragment, cellCache); + this._attachmentCache.set(notebookUri, documentCache); + } else if (!documentCache.has(cellFragment)) { + // no cache for this cell yet + const cellCache = new Map(); + cellCache.set(currFilename, this.getMetadataAttachment(metadata, currFilename)); + documentCache.set(cellFragment, cellCache); + } else { + // cache for this cell already exists + // add to cell cache + documentCache.get(cellFragment)?.set(currFilename, this.getMetadataAttachment(metadata, currFilename)); + } + } + + /** + * get an attachment entry from the given metadata + * @param metadata metadata to extract image data from + * @param currFilename filename of image being extracted + * @returns + */ + private getMetadataAttachment(metadata: { [key: string]: any }, currFilename: string): { [key: string]: any } { + return metadata.attachments[currFilename]; + } + + /** + * returns a boolean that represents if there are any images in the attachment field of a cell's metadata + * @param metadata metadata of cell + * @returns boolean representing the presence of any attachments + */ + private checkMetadataHasAttachmentsField(metadata: { [key: string]: unknown }): metadata is { readonly attachments: Record } { + return !!metadata.attachments && typeof metadata.attachments === 'object'; + } + + /** + * given metadata from a cell, cache every image (used in cases with no image links in markdown source) + * @param metadata metadata for a cell with no images in markdown source + * @param notebookUri uri for the notebook being edited + * @param cellFragment fragment of cell being edited + */ + private saveAllAttachmentsToCache(metadata: { [key: string]: unknown }, notebookUri: string, cellFragment: string): void { + const documentCache = this._attachmentCache.get(notebookUri) ?? new Map(); + this._attachmentCache.set(notebookUri, documentCache); + const cellCache = documentCache.get(cellFragment) ?? new Map(); + documentCache.set(cellFragment, cellCache); + + if (metadata.attachments && typeof metadata.attachments === 'object') { + for (const [currFilename, attachment] of Object.entries(metadata.attachments)) { + cellCache.set(currFilename, attachment); + } + } + } + + /** + * pass in all of the markdown source code, and get a dictionary of all images referenced in the markdown. keys are image filenames, values are render state + * @param document the text document for the cell, formatted as a string + */ + private getAttachmentNames(document: vscode.TextDocument) { + const source = document.getText(); + const filenames: Map = new Map(); + const re = /!\[.*?\]\(.*?)>?\)/gm; + + let match; + while ((match = re.exec(source))) { + if (match.groups?.filename) { + const index = match.index; + const length = match[0].length; + const startPosition = document.positionAt(index); + const endPosition = document.positionAt(index + length); + const range = new vscode.Range(startPosition, endPosition); + const filename = filenames.get(match.groups.filename) ?? { valid: false, ranges: [] }; + filenames.set(match.groups.filename, filename); + filename.ranges.push(range); + } + } + return filenames; + } + + dispose() { + this._disposables.forEach(d => d.dispose()); + this._delayer.dispose(); + } +} + diff --git a/extensions/ipynb/src/notebookImagePaste.ts b/extensions/ipynb/src/notebookImagePaste.ts new file mode 100644 index 0000000000..7865561528 --- /dev/null +++ b/extensions/ipynb/src/notebookImagePaste.ts @@ -0,0 +1,326 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR } from './constants'; +import { basename, extname } from 'path'; + +enum MimeType { + bmp = 'image/bmp', + gif = 'image/gif', + ico = 'image/ico', + jpeg = 'image/jpeg', + png = 'image/png', + tiff = 'image/tiff', + webp = 'image/webp', + uriList = 'text/uri-list', +} + +const imageMimeTypes: ReadonlySet = new Set([ + MimeType.bmp, + MimeType.gif, + MimeType.ico, + MimeType.jpeg, + MimeType.png, + MimeType.tiff, + MimeType.webp, +]); + +const imageExtToMime: ReadonlyMap = new Map([ + ['.bmp', MimeType.bmp], + ['.gif', MimeType.gif], + ['.ico', MimeType.ico], + ['.jpe', MimeType.jpeg], + ['.jpeg', MimeType.jpeg], + ['.jpg', MimeType.jpeg], + ['.png', MimeType.png], + ['.tif', MimeType.tiff], + ['.tiff', MimeType.tiff], + ['.webp', MimeType.webp], +]); + +function getImageMimeType(uri: vscode.Uri): string | undefined { + return imageExtToMime.get(extname(uri.fsPath).toLowerCase()); +} + +class DropOrPasteEditProvider implements vscode.DocumentPasteEditProvider, vscode.DocumentDropEditProvider { + + private readonly id = 'insertAttachment'; + + private readonly defaultPriority = 5; + + async provideDocumentPasteEdits( + document: vscode.TextDocument, + _ranges: readonly vscode.Range[], + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise { + const enabled = vscode.workspace.getConfiguration('ipynb', document).get('pasteImagesAsAttachments.enabled', true); + if (!enabled) { + return; + } + + const insert = await this.createInsertImageAttachmentEdit(document, dataTransfer, token); + if (!insert) { + return; + } + + const pasteEdit = new vscode.DocumentPasteEdit(insert.insertText, this.id, vscode.l10n.t('Insert Image as Attachment')); + pasteEdit.priority = this.getPastePriority(dataTransfer); + pasteEdit.additionalEdit = insert.additionalEdit; + return pasteEdit; + } + + async provideDocumentDropEdits( + document: vscode.TextDocument, + _position: vscode.Position, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise { + const insert = await this.createInsertImageAttachmentEdit(document, dataTransfer, token); + if (!insert) { + return; + } + + const dropEdit = new vscode.DocumentDropEdit(insert.insertText); + dropEdit.id = this.id; + dropEdit.priority = this.defaultPriority; + dropEdit.additionalEdit = insert.additionalEdit; + dropEdit.label = vscode.l10n.t('Insert Image as Attachment'); + return dropEdit; + } + + private getPastePriority(dataTransfer: vscode.DataTransfer): number { + if (dataTransfer.get('text/plain')) { + // Deprioritize in favor of normal text content + return -5; + } + + // Otherwise boost priority so attachments are preferred + return this.defaultPriority; + } + + private async createInsertImageAttachmentEdit( + document: vscode.TextDocument, + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, + ): Promise<{ insertText: vscode.SnippetString; additionalEdit: vscode.WorkspaceEdit } | undefined> { + const imageData = await getDroppedImageData(dataTransfer, token); + if (!imageData.length || token.isCancellationRequested) { + return; + } + + const currentCell = getCellFromCellDocument(document); + if (!currentCell) { + return undefined; + } + + // create updated metadata for cell (prep for WorkspaceEdit) + const newAttachment = buildAttachment(currentCell, imageData); + if (!newAttachment) { + return; + } + + // build edits + const additionalEdit = new vscode.WorkspaceEdit(); + const nbEdit = vscode.NotebookEdit.updateCellMetadata(currentCell.index, newAttachment.metadata); + const notebookUri = currentCell.notebook.uri; + additionalEdit.set(notebookUri, [nbEdit]); + + // create a snippet for paste + const insertText = new vscode.SnippetString(); + newAttachment.filenames.forEach((filename, i) => { + insertText.appendText('!['); + insertText.appendPlaceholder(`${filename}`); + insertText.appendText(`](${/\s/.test(filename) ? `` : `attachment:${filename}`})`); + if (i !== newAttachment.filenames.length - 1) { + insertText.appendText(' '); + } + }); + + return { insertText, additionalEdit }; + } +} + +async function getDroppedImageData( + dataTransfer: vscode.DataTransfer, + token: vscode.CancellationToken, +): Promise { + + // Prefer using image data in the clipboard + const files = coalesce(await Promise.all(Array.from(dataTransfer, async ([mimeType, item]): Promise => { + if (!imageMimeTypes.has(mimeType)) { + return; + } + + const file = item.asFile(); + if (!file) { + return; + } + + const data = await file.data(); + return { fileName: file.name, mimeType, data }; + }))); + if (files.length) { + return files; + } + + // Then fallback to image files in the uri-list + const urlList = await dataTransfer.get('text/uri-list')?.asString(); + if (token.isCancellationRequested) { + return []; + } + + if (urlList) { + const uris: vscode.Uri[] = []; + for (const resource of urlList.split(/\r?\n/g)) { + try { + uris.push(vscode.Uri.parse(resource)); + } catch { + // noop + } + } + + const entries = await Promise.all(uris.map(async (uri) => { + const mimeType = getImageMimeType(uri); + if (!mimeType) { + return; + } + + const data = await vscode.workspace.fs.readFile(uri); + return { fileName: basename(uri.fsPath), mimeType, data }; + })); + + return coalesce(entries); + } + + return []; +} + +function coalesce(array: ReadonlyArray): T[] { + return array.filter(e => !!e); +} + +function getCellFromCellDocument(cellDocument: vscode.TextDocument): vscode.NotebookCell | undefined { + for (const notebook of vscode.workspace.notebookDocuments) { + if (notebook.uri.path === cellDocument.uri.path) { + for (const cell of notebook.getCells()) { + if (cell.document === cellDocument) { + return cell; + } + } + } + } + return undefined; +} + +/** + * Taken from https://github.com/microsoft/vscode/blob/743b016722db90df977feecde0a4b3b4f58c2a4c/src/vs/base/common/buffer.ts#L350-L387 + */ +function encodeBase64(buffer: Uint8Array, padded = true, urlSafe = false) { + const base64Alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + const base64UrlSafeAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; + + const dictionary = urlSafe ? base64UrlSafeAlphabet : base64Alphabet; + let output = ''; + + const remainder = buffer.byteLength % 3; + + let i = 0; + for (; i < buffer.byteLength - remainder; i += 3) { + const a = buffer[i + 0]; + const b = buffer[i + 1]; + const c = buffer[i + 2]; + + output += dictionary[a >>> 2]; + output += dictionary[(a << 4 | b >>> 4) & 0b111111]; + output += dictionary[(b << 2 | c >>> 6) & 0b111111]; + output += dictionary[c & 0b111111]; + } + + if (remainder === 1) { + const a = buffer[i + 0]; + output += dictionary[a >>> 2]; + output += dictionary[(a << 4) & 0b111111]; + if (padded) { output += '=='; } + } else if (remainder === 2) { + const a = buffer[i + 0]; + const b = buffer[i + 1]; + output += dictionary[a >>> 2]; + output += dictionary[(a << 4 | b >>> 4) & 0b111111]; + output += dictionary[(b << 2) & 0b111111]; + if (padded) { output += '='; } + } + + return output; +} + + +interface ImageAttachmentData { + readonly fileName: string; + readonly data: Uint8Array; + readonly mimeType: string; +} + +function buildAttachment( + cell: vscode.NotebookCell, + attachments: readonly ImageAttachmentData[], +): { metadata: { [key: string]: any }; filenames: string[] } | undefined { + const cellMetadata = { ...cell.metadata }; + const tempFilenames: string[] = []; + if (!attachments.length) { + return undefined; + } + + if (!cellMetadata.attachments) { + cellMetadata.attachments = {}; + } + + for (const attachment of attachments) { + const b64 = encodeBase64(attachment.data); + + const fileExt = extname(attachment.fileName); + const filenameWithoutExt = basename(attachment.fileName, fileExt); + + let tempFilename = filenameWithoutExt + fileExt; + for (let appendValue = 2; tempFilename in cellMetadata.attachments; appendValue++) { + const objEntries = Object.entries(cellMetadata.attachments[tempFilename]); + if (objEntries.length) { // check that mime:b64 are present + const [mime, attachmentb64] = objEntries[0]; + if (mime === attachment.mimeType && attachmentb64 === b64) { // checking if filename can be reused, based on comparison of image data + break; + } else { + tempFilename = filenameWithoutExt.concat(`-${appendValue}`) + fileExt; + } + } + } + + tempFilenames.push(tempFilename); + cellMetadata.attachments[tempFilename] = { [attachment.mimeType]: b64 }; + } + + return { + metadata: cellMetadata, + filenames: tempFilenames, + }; +} + +export function notebookImagePasteSetup(): vscode.Disposable { + const provider = new DropOrPasteEditProvider(); + return vscode.Disposable.from( + vscode.languages.registerDocumentPasteEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, { + pasteMimeTypes: [ + MimeType.png, + MimeType.uriList, + ], + }), + vscode.languages.registerDocumentDropEditProvider(JUPYTER_NOTEBOOK_MARKDOWN_SELECTOR, provider, { + dropMimeTypes: [ + ...Object.values(imageExtToMime), + MimeType.uriList, + ], + }) + ); +} diff --git a/extensions/ipynb/src/notebookSerializer.ts b/extensions/ipynb/src/notebookSerializer.ts index cb70f57ff2..495b286cbe 100644 --- a/extensions/ipynb/src/notebookSerializer.ts +++ b/extensions/ipynb/src/notebookSerializer.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; import * as detectIndent from 'detect-indent'; import * as vscode from 'vscode'; import { defaultNotebookFormat } from './constants'; @@ -84,7 +84,7 @@ export class NotebookSerializer implements vscode.NotebookSerializer { public serializeNotebookToString(data: vscode.NotebookData): string { const notebookContent = getNotebookMetadata(data); // use the preferred language from document metadata or the first cell language as the notebook preferred cell language - const preferredCellLanguage = notebookContent.metadata?.language_info?.name ?? data.cells[0].languageId; + const preferredCellLanguage = notebookContent.metadata?.language_info?.name ?? data.cells.find(cell => cell.kind === vscode.NotebookCellKind.Code)?.languageId; notebookContent.cells = data.cells .map(cell => createJupyterCellFromNotebookCell(cell, preferredCellLanguage)) @@ -93,7 +93,7 @@ export class NotebookSerializer implements vscode.NotebookSerializer { const indentAmount = data.metadata && 'indentAmount' in data.metadata && typeof data.metadata.indentAmount === 'string' ? data.metadata.indentAmount : ' '; - // ipynb always ends with a trailing new line (we add this so that SCMs do not show unnecesary changes, resulting from a missing trailing new line). + // ipynb always ends with a trailing new line (we add this so that SCMs do not show unnecessary changes, resulting from a missing trailing new line). return JSON.stringify(sortObjectPropertiesRecursively(notebookContent), undefined, indentAmount) + '\n'; } } diff --git a/extensions/ipynb/src/serializers.ts b/extensions/ipynb/src/serializers.ts index 8606a52a68..378cc3c7da 100644 --- a/extensions/ipynb/src/serializers.ts +++ b/extensions/ipynb/src/serializers.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; import { NotebookCell, NotebookCellData, NotebookCellKind, NotebookCellOutput } from 'vscode'; -import { CellMetadata, CellOutputMetadata } from './common'; +import { CellOutputMetadata } from './common'; import { textMimeTypes } from './deserializers'; const textDecoder = new TextDecoder(); @@ -55,8 +55,14 @@ export function sortObjectPropertiesRecursively(obj: any): any { } export function getCellMetadata(cell: NotebookCell | NotebookCellData) { - return cell.metadata?.custom as CellMetadata | undefined; + return { + // it contains the cell id, and the cell metadata, along with other nb cell metadata + ...(cell.metadata?.custom ?? {}), + // promote the cell attachments to the top level + attachments: cell.metadata?.custom?.attachments ?? cell.metadata?.attachments + }; } + function createCodeCellFromNotebookCell(cell: NotebookCellData, preferredLanguage: string | undefined): nbformat.ICodeCell { const cellMetadata = getCellMetadata(cell); let metadata = cellMetadata?.metadata || {}; // This cannot be empty. @@ -274,7 +280,7 @@ function convertStreamOutput(output: NotebookCellOutput): JupyterOutput { .filter((opit) => opit.mime === CellOutputMimeTypes.stderr || opit.mime === CellOutputMimeTypes.stdout) .map((opit) => textDecoder.decode(opit.data)) .forEach(value => { - // Ensure each line is a seprate entry in an array (ending with \n). + // Ensure each line is a separate entry in an array (ending with \n). const lines = value.split('\n'); // If the last item in `outputs` is not empty and the first item in `lines` is not empty, then concate them. // As they are part of the same line. @@ -326,16 +332,17 @@ function convertOutputMimeToJupyterOutput(mime: string, value: Uint8Array) { } else if (mime.toLowerCase().includes('json')) { const stringValue = textDecoder.decode(value); return stringValue.length > 0 ? JSON.parse(stringValue) : stringValue; + } else if (mime === 'image/svg+xml') { + return splitMultilineString(textDecoder.decode(value)); } else { - const stringValue = textDecoder.decode(value); - return stringValue; + return textDecoder.decode(value); } } catch (ex) { return ''; } } -function createMarkdownCellFromNotebookCell(cell: NotebookCellData): nbformat.IMarkdownCell { +export function createMarkdownCellFromNotebookCell(cell: NotebookCellData): nbformat.IMarkdownCell { const cellMetadata = getCellMetadata(cell); const markdownCell: any = { cell_type: 'markdown', diff --git a/extensions/ipynb/src/test/index.ts b/extensions/ipynb/src/test/index.ts index 38ad28d2d0..360c58cc53 100644 --- a/extensions/ipynb/src/test/index.ts +++ b/extensions/ipynb/src/test/index.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -const path = require('path'); -const testRunner = require('../../../../test/integration/electron/testrunner'); +import * as path from 'path'; +import * as testRunner from '../../../../test/integration/electron/testrunner'; -const options: any = { +const options: import('mocha').MochaOptions = { ui: 'tdd', color: true, timeout: 60000 diff --git a/extensions/ipynb/src/test/serializers.test.ts b/extensions/ipynb/src/test/serializers.test.ts index 84df4eec0b..ba37ea5c7e 100644 --- a/extensions/ipynb/src/test/serializers.test.ts +++ b/extensions/ipynb/src/test/serializers.test.ts @@ -3,10 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nbformat from '@jupyterlab/nbformat'; +import type * as nbformat from '@jupyterlab/nbformat'; import * as assert from 'assert'; import * as vscode from 'vscode'; import { jupyterCellOutputToCellOutput, jupyterNotebookModelToNotebookData } from '../deserializers'; +import { createMarkdownCellFromNotebookCell, getCellMetadata } from '../serializers'; function deepStripProperties(obj: any, props: string[]) { for (let prop in obj) { @@ -52,6 +53,71 @@ suite('ipynb serializer', () => { assert.deepStrictEqual(notebook.cells, [expectedCodeCell, expectedMarkdownCell]); }); + + + test('Serialize', async () => { + const markdownCell = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# header1', 'markdown'); + markdownCell.metadata = { + attachments: { + 'image.png': { + 'image/png': 'abc' + } + }, + custom: { + id: '123', + metadata: { + foo: 'bar' + } + } + }; + + const cellMetadata = getCellMetadata(markdownCell); + assert.deepStrictEqual(cellMetadata, { + id: '123', + metadata: { + foo: 'bar', + }, + attachments: { + 'image.png': { + 'image/png': 'abc' + } + } + }); + + const markdownCell2 = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# header1', 'markdown'); + markdownCell2.metadata = { + custom: { + id: '123', + metadata: { + foo: 'bar' + }, + attachments: { + 'image.png': { + 'image/png': 'abc' + } + } + } + }; + + const nbMarkdownCell = createMarkdownCellFromNotebookCell(markdownCell); + const nbMarkdownCell2 = createMarkdownCellFromNotebookCell(markdownCell2); + assert.deepStrictEqual(nbMarkdownCell, nbMarkdownCell2); + + assert.deepStrictEqual(nbMarkdownCell, { + cell_type: 'markdown', + source: ['# header1'], + metadata: { + foo: 'bar', + }, + attachments: { + 'image.png': { + 'image/png': 'abc' + } + }, + id: '123' + }); + }); + suite('Outputs', () => { function validateCellOutputTranslation( outputs: nbformat.IOutput[], diff --git a/extensions/ipynb/tsconfig.json b/extensions/ipynb/tsconfig.json index 178e86493b..189a4848f5 100644 --- a/extensions/ipynb/tsconfig.json +++ b/extensions/ipynb/tsconfig.json @@ -2,14 +2,13 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "lib": [ - "dom" - ] + "lib": ["dom"] }, "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookEditor.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts" + "../../src/vscode-dts/vscode.proposed.documentPaste.d.ts", + "../../src/vscode-dts/vscode.proposed.dropMetadata.d.ts" + ] } diff --git a/extensions/javascript/.vscodeignore b/extensions/javascript/.vscodeignore deleted file mode 100644 index b66f1626fb..0000000000 --- a/extensions/javascript/.vscodeignore +++ /dev/null @@ -1,5 +0,0 @@ -test/** -src/**/*.ts -syntaxes/Readme.md -tsconfig.json -cgmanifest.json diff --git a/extensions/javascript/cgmanifest.json b/extensions/javascript/cgmanifest.json deleted file mode 100644 index 2d5d904f01..0000000000 --- a/extensions/javascript/cgmanifest.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "registrations": [ - { - "component": { - "type": "git", - "git": { - "name": "microsoft/TypeScript-TmLanguage", - "repositoryUrl": "https://github.com/microsoft/TypeScript-TmLanguage", - "commitHash": "3133e3d914db9a2bb8812119f9273727a305f16b" - } - }, - "license": "MIT", - "version": "0.0.1", - "description": "The file syntaxes/JavaScript.tmLanguage.json was derived from TypeScriptReact.tmLanguage in https://github.com/microsoft/TypeScript-TmLanguage." - }, - { - "component": { - "type": "git", - "git": { - "name": "textmate/javascript.tmbundle", - "repositoryUrl": "https://github.com/textmate/javascript.tmbundle", - "commitHash": "fccf0af0c95430a42e1bf98f0c7a4723a53283e7" - } - }, - "licenseDetail": [ - "Copyright (c) textmate-javascript.tmbundle project authors", - "", - "If not otherwise specified (see below), files in this repository fall under the following license:", - "", - "Permission to copy, use, modify, sell and distribute this", - "software is granted. This software is provided \"as is\" without", - "express or implied warranty, and with no claim as to its", - "suitability for any purpose.", - "", - "An exception is made for files in readable text which contain their own license information,", - "or files where an accompanying file exists (in the same directory) with a \"-license\" suffix added", - "to the base-name name of the original file, and an extension of txt, html, or similar. For example", - "\"tidy\" is accompanied by \"tidy-license.txt\"." - ], - "license": "TextMate Bundle License", - "version": "0.0.0" - } - ], - "version": 1 -} diff --git a/extensions/javascript/javascript-language-configuration.json b/extensions/javascript/javascript-language-configuration.json deleted file mode 100644 index 72997cc3ec..0000000000 --- a/extensions/javascript/javascript-language-configuration.json +++ /dev/null @@ -1,193 +0,0 @@ -{ - // Note that this file should stay in sync with 'typescript-language-basics/language-configuration.json' - "comments": { - "lineComment": "//", - "blockComment": [ - "/*", - "*/" - ] - }, - "brackets": [ - [ - "${", - "}" - ], - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - }, - { - "open": "`", - "close": "`", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "/**", - "close": " */", - "notIn": [ - "string" - ] - } - ], - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ], - [ - "`", - "`" - ], - [ - "<", - ">" - ] - ], - "autoCloseBefore": ";:.,=}])>` \n\t", - "folding": { - "markers": { - "start": "^\\s*//\\s*#?region\\b", - "end": "^\\s*//\\s*#?endregion\\b" - } - }, - "wordPattern": { - "pattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>/\\?\\s]+)", - }, - "indentationRules": { - "decreaseIndentPattern": { - "pattern": "^((?!.*?/\\*).*\\*\/)?\\s*[\\}\\]].*$" - }, - "increaseIndentPattern": { - "pattern": "^((?!//).)*(\\{([^}\"'`/]*|(\\t|[ ])*//.*)|\\([^)\"'`/]*|\\[[^\\]\"'`/]*)$" - }, - // e.g. * ...| or */| or *-----*/| - "unIndentedLinePattern": { - "pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$|^(\\t|[ ])*[ ]\\*/\\s*$|^(\\t|[ ])*[ ]\\*([ ]([^\\*]|\\*(?!/))*)?$" - } - }, - "onEnterRules": [ - { - // e.g. /** | */ - "beforeText": { - "pattern": "^\\s*/\\*\\*(?!/)([^\\*]|\\*(?!/))*$" - }, - "afterText": { - "pattern": "^\\s*\\*/$" - }, - "action": { - "indent": "indentOutdent", - "appendText": " * " - } - }, - { - // e.g. /** ...| - "beforeText": { - "pattern": "^\\s*/\\*\\*(?!/)([^\\*]|\\*(?!/))*$" - }, - "action": { - "indent": "none", - "appendText": " * " - } - }, - { - // e.g. * ...| - "beforeText": { - "pattern": "^(\\t|[ ])*[ ]\\*([ ]([^\\*]|\\*(?!/))*)?$" - }, - "previousLineText": { - "pattern": "(?=^(\\s*(/\\*\\*|\\*)).*)(?=(?!(\\s*\\*/)))" - }, - "action": { - "indent": "none", - "appendText": "* " - } - }, - { - // e.g. */| - "beforeText": { - "pattern": "^(\\t|[ ])*[ ]\\*/\\s*$" - }, - "action": { - "indent": "none", - "removeText": 1 - }, - }, - { - // e.g. *-----*/| - "beforeText": { - "pattern": "^(\\t|[ ])*[ ]\\*[^/]*\\*/\\s*$" - }, - "action": { - "indent": "none", - "removeText": 1 - }, - }, - { - "beforeText": { - "pattern": "^\\s*(\\bcase\\s.+:|\\bdefault:)$" - }, - "afterText": { - "pattern": "^(?!\\s*(\\bcase\\b|\\bdefault\\b))" - }, - "action": { - "indent": "indent" - } - } - ] -} diff --git a/extensions/javascript/package.json b/extensions/javascript/package.json deleted file mode 100644 index e3a087bfc9..0000000000 --- a/extensions/javascript/package.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "name": "javascript", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { - "vscode": "0.10.x" - }, - "contributes": { - "configurationDefaults": { - "[javascript]": { - "editor.maxTokenizationLineLength": 2500 - } - }, - "languages": [ - { - "id": "javascriptreact", - "aliases": [ - "JavaScript React", - "jsx" - ], - "extensions": [ - ".jsx" - ], - "configuration": "./javascript-language-configuration.json" - }, - { - "id": "javascript", - "aliases": [ - "JavaScript", - "javascript", - "js" - ], - "extensions": [ - ".js", - ".es6", - ".mjs", - ".cjs", - ".pac" - ], - "filenames": [ - "jakefile" - ], - "firstLine": "^#!.*\\bnode", - "mimetypes": [ - "text/javascript" - ], - "configuration": "./javascript-language-configuration.json" - }, - { - "id": "jsx-tags", - "aliases": [], - "configuration": "./tags-language-configuration.json" - } - ], - "grammars": [ - { - "language": "javascriptreact", - "scopeName": "source.js.jsx", - "path": "./syntaxes/JavaScriptReact.tmLanguage.json", - "embeddedLanguages": { - "meta.tag.js": "jsx-tags", - "meta.tag.without-attributes.js": "jsx-tags", - "meta.tag.attributes.js.jsx": "javascriptreact", - "meta.embedded.expression.js": "javascriptreact" - }, - "tokenTypes": { - "meta.template.expression": "other", - "meta.template.expression string": "string", - "meta.template.expression comment": "comment", - "entity.name.type.instance.jsdoc": "other", - "entity.name.function.tagged-template": "other", - "meta.import string.quoted": "other", - "variable.other.jsdoc": "other" - } - }, - { - "language": "javascript", - "scopeName": "source.js", - "path": "./syntaxes/JavaScript.tmLanguage.json", - "embeddedLanguages": { - "meta.tag.js": "jsx-tags", - "meta.tag.without-attributes.js": "jsx-tags", - "meta.tag.attributes.js": "javascript", - "meta.embedded.expression.js": "javascript" - }, - "tokenTypes": { - "meta.template.expression": "other", - "meta.template.expression string": "string", - "meta.template.expression comment": "comment", - "entity.name.type.instance.jsdoc": "other", - "entity.name.function.tagged-template": "other", - "meta.import string.quoted": "other", - "variable.other.jsdoc": "other" - } - }, - { - "scopeName": "source.js.regexp", - "path": "./syntaxes/Regular Expressions (JavaScript).tmLanguage" - } - ], - "semanticTokenScopes": [ - { - "language": "javascript", - "scopes": { - "property": [ - "variable.other.property.js" - ], - "property.readonly": [ - "variable.other.constant.property.js" - ], - "variable": [ - "variable.other.readwrite.js" - ], - "variable.readonly": [ - "variable.other.constant.object.js" - ], - "function": [ - "entity.name.function.js" - ], - "namespace": [ - "entity.name.type.module.js" - ], - "variable.defaultLibrary": [ - "support.variable.js" - ], - "function.defaultLibrary": [ - "support.function.js" - ] - } - }, - { - "language": "javascriptreact", - "scopes": { - "property": [ - "variable.other.property.jsx" - ], - "property.readonly": [ - "variable.other.constant.property.jsx" - ], - "variable": [ - "variable.other.readwrite.jsx" - ], - "variable.readonly": [ - "variable.other.constant.object.jsx" - ], - "function": [ - "entity.name.function.jsx" - ], - "namespace": [ - "entity.name.type.module.jsx" - ], - "variable.defaultLibrary": [ - "support.variable.js" - ], - "function.defaultLibrary": [ - "support.function.js" - ] - } - } - ], - "snippets": [ - { - "language": "javascript", - "path": "./snippets/javascript.code-snippets" - }, - { - "language": "javascriptreact", - "path": "./snippets/javascript.code-snippets" - } - ] - }, - "repository": { - "type": "git", - "url": "https://github.com/microsoft/vscode.git" - } -} diff --git a/extensions/javascript/package.nls.json b/extensions/javascript/package.nls.json deleted file mode 100644 index b7df6e1411..0000000000 --- a/extensions/javascript/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "JavaScript Language Basics", - "description": "Provides snippets, syntax highlighting, bracket matching and folding in JavaScript files." -} diff --git a/extensions/javascript/snippets/javascript.code-snippets b/extensions/javascript/snippets/javascript.code-snippets deleted file mode 100644 index 9448fd140d..0000000000 --- a/extensions/javascript/snippets/javascript.code-snippets +++ /dev/null @@ -1,203 +0,0 @@ -{ - "define module": { - "prefix": "define", - "body": [ - "define([", - "\t'require',", - "\t'${1:dependency}'", - "], function(require, ${2:factory}) {", - "\t'use strict';", - "\t$0", - "});" - ], - "description": "define module" - }, - "For Loop": { - "prefix": "for", - "body": [ - "for (let ${1:index} = 0; ${1:index} < ${2:array}.length; ${1:index}++) {", - "\tconst ${3:element} = ${2:array}[${1:index}];", - "\t$TM_SELECTED_TEXT$0", - "}" - ], - "description": "For Loop" - }, - "For-Each Loop": { - "prefix": "foreach", - "body": [ - "${1:array}.forEach(${2:element} => {", - "\t$TM_SELECTED_TEXT$0", - "});" - ], - "description": "For-Each Loop" - }, - "For-In Loop": { - "prefix": "forin", - "body": [ - "for (const ${1:key} in ${2:object}) {", - "\tif (Object.hasOwnProperty.call(${2:object}, ${1:key})) {", - "\t\tconst ${3:element} = ${2:object}[${1:key}];", - "\t\t$TM_SELECTED_TEXT$0", - "\t}", - "}" - ], - "description": "For-In Loop" - }, - "For-Of Loop": { - "prefix": "forof", - "body": [ - "for (const ${1:iterator} of ${2:object}) {", - "\t$TM_SELECTED_TEXT$0", - "}" - ], - "description": "For-Of Loop" - }, - "Function Statement": { - "prefix": "function", - "body": [ - "function ${1:name}(${2:params}) {", - "\t$TM_SELECTED_TEXT$0", - "}" - ], - "description": "Function Statement" - }, - "If Statement": { - "prefix": "if", - "body": [ - "if (${1:condition}) {", - "\t$TM_SELECTED_TEXT$0", - "}" - ], - "description": "If Statement" - }, - "If-Else Statement": { - "prefix": "ifelse", - "body": [ - "if (${1:condition}) {", - "\t$TM_SELECTED_TEXT$0", - "} else {", - "\t", - "}" - ], - "description": "If-Else Statement" - }, - "New Statement": { - "prefix": "new", - "body": [ - "const ${1:name} = new ${2:type}(${3:arguments});$0" - ], - "description": "New Statement" - }, - "Switch Statement": { - "prefix": "switch", - "body": [ - "switch (${1:key}) {", - "\tcase ${2:value}:", - "\t\t$0", - "\t\tbreak;", - "", - "\tdefault:", - "\t\tbreak;", - "}" - ], - "description": "Switch Statement" - }, - "While Statement": { - "prefix": "while", - "body": [ - "while (${1:condition}) {", - "\t$TM_SELECTED_TEXT$0", - "}" - ], - "description": "While Statement" - }, - "Do-While Statement": { - "prefix": "dowhile", - "body": [ - "do {", - "\t$TM_SELECTED_TEXT$0", - "} while (${1:condition});" - ], - "description": "Do-While Statement" - }, - "Try-Catch Statement": { - "prefix": "trycatch", - "body": [ - "try {", - "\t$TM_SELECTED_TEXT$0", - "} catch (${1:error}) {", - "\t", - "}" - ], - "description": "Try-Catch Statement" - }, - "Set Timeout Function": { - "prefix": "settimeout", - "body": [ - "setTimeout(() => {", - "\t$TM_SELECTED_TEXT$0", - "}, ${1:timeout});" - ], - "description": "Set Timeout Function" - }, - "Set Interval Function": { - "prefix": "setinterval", - "body": [ - "setInterval(() => {", - "\t$TM_SELECTED_TEXT$0", - "}, ${1:interval});" - ], - "description": "Set Interval Function" - }, - "Import Statement": { - "prefix": "import", - "body": [ - "import { $0 } from \"${1:module}\";" - ], - "description": "Import external module" - }, - "Region Start": { - "prefix": "#region", - "body": [ - "//#region $0" - ], - "description": "Folding Region Start" - }, - "Region End": { - "prefix": "#endregion", - "body": [ - "//#endregion" - ], - "description": "Folding Region End" - }, - "Log to the console": { - "prefix": "log", - "body": [ - "console.log($1);" - ], - "description": "Log to the console" - }, - "Log warning to console": { - "prefix": "warn", - "body": [ - "console.warn($1);" - ], - "description": "Log warning to the console" - }, - "Log error to console": { - "prefix": "error", - "body": [ - "console.error($1);" - ], - "description": "Log error to the console" - }, - "new Promise": { - "prefix": "newpromise", - "body": [ - "new Promise((resolve, reject) => {", - "\t$TM_SELECTED_TEXT$0", - "})" - ], - "description": "Create a new Promise" - } -} diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json deleted file mode 100644 index e957c06c1b..0000000000 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ /dev/null @@ -1,5912 +0,0 @@ -{ - "information_for_contributors": [ - "This file has been converted from https://github.com/microsoft/TypeScript-TmLanguage/blob/master/TypeScriptReact.tmLanguage", - "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/0dfae8cc4807fecfbf8f1add095d9817df824c95", - "name": "JavaScript (with React support)", - "scopeName": "source.js", - "patterns": [ - { - "include": "#directives" - }, - { - "include": "#statements" - }, - { - "include": "#shebang" - } - ], - "repository": { - "shebang": { - "name": "comment.line.shebang.js", - "match": "\\A(#!).*(?=$)", - "captures": { - "1": { - "name": "punctuation.definition.comment.js" - } - } - }, - "statements": { - "patterns": [ - { - "include": "#declaration" - }, - { - "include": "#control-statement" - }, - { - "include": "#after-operator-block-as-object-literal" - }, - { - "include": "#decl-block" - }, - { - "include": "#label" - }, - { - "include": "#expression" - }, - { - "include": "#punctuation-semicolon" - }, - { - "include": "#string" - }, - { - "include": "#comment" - } - ] - }, - "declaration": { - "patterns": [ - { - "include": "#decorator" - }, - { - "include": "#var-expr" - }, - { - "include": "#function-declaration" - }, - { - "include": "#class-declaration" - }, - { - "include": "#interface-declaration" - }, - { - "include": "#enum-declaration" - }, - { - "include": "#namespace-declaration" - }, - { - "include": "#type-alias-declaration" - }, - { - "include": "#import-equals-declaration" - }, - { - "include": "#import-declaration" - }, - { - "include": "#export-declaration" - }, - { - "name": "storage.modifier.js", - "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)))", - "beginCaptures": { - "1": { - "name": "meta.definition.variable.js entity.name.function.js" - }, - "2": { - "name": "keyword.operator.definiteassignment.js" - } - }, - "end": "(?=$|^|[;,=}]|((?)\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" - } - }, - "end": "(?=$|^|[;,=}]|((?)\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" - }, - "2": { - "name": "keyword.operator.rest.js" - }, - "3": { - "name": "entity.name.function.js variable.language.this.js" - }, - "4": { - "name": "entity.name.function.js" - }, - "5": { - "name": "keyword.operator.optional.js" - } - } - }, - { - "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": "meta.definition.property.js entity.name.function.js" - }, - "2": { - "name": "keyword.operator.optional.js" - }, - "3": { - "name": "keyword.operator.definiteassignment.js" - } - } - }, - { - "name": "meta.definition.property.js variable.object.property.js", - "match": "\\#?[_$[:alpha:]][_$[:alnum:]]*" - }, - { - "name": "keyword.operator.optional.js", - "match": "\\?" - }, - { - "name": "keyword.operator.definiteassignment.js", - "match": "\\!" - } - ] - }, - "variable-initializer": { - "patterns": [ - { - "begin": "(?\\s*$)", - "beginCaptures": { - "1": { - "name": "keyword.operator.assignment.js" - } - }, - "end": "(?=$|^|[,);}\\]]|((?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", - "beginCaptures": { - "1": { - "name": "storage.modifier.js" - }, - "2": { - "name": "storage.modifier.js" - }, - "3": { - "name": "storage.modifier.js" - }, - "4": { - "name": "storage.modifier.async.js" - }, - "5": { - "name": "keyword.operator.new.js" - }, - "6": { - "name": "keyword.generator.asterisk.js" - } - }, - "end": "(?=\\}|;|,|$)|(?<=\\})", - "patterns": [ - { - "include": "#method-declaration-name" - }, - { - "include": "#function-body" - } - ] - }, - { - "name": "meta.method.declaration.js", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", - "beginCaptures": { - "1": { - "name": "storage.modifier.js" - }, - "2": { - "name": "storage.modifier.js" - }, - "3": { - "name": "storage.modifier.js" - }, - "4": { - "name": "storage.modifier.async.js" - }, - "5": { - "name": "storage.type.property.js" - }, - "6": { - "name": "keyword.generator.asterisk.js" - } - }, - "end": "(?=\\}|;|,|$)|(?<=\\})", - "patterns": [ - { - "include": "#method-declaration-name" - }, - { - "include": "#function-body" - } - ] - } - ] - }, - "object-literal-method-declaration": { - "name": "meta.method.declaration.js", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js" - }, - "2": { - "name": "storage.type.property.js" - }, - "3": { - "name": "keyword.generator.asterisk.js" - } - }, - "end": "(?=\\}|;|,)|(?<=\\})", - "patterns": [ - { - "include": "#method-declaration-name" - }, - { - "include": "#function-body" - }, - { - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js" - }, - "2": { - "name": "storage.type.property.js" - }, - "3": { - "name": "keyword.generator.asterisk.js" - } - }, - "end": "(?=\\(|\\<)", - "patterns": [ - { - "include": "#method-declaration-name" - } - ] - } - ] - }, - "method-declaration-name": { - "begin": "(?x)(?=((\\b(?)", - "captures": { - "1": { - "name": "storage.modifier.async.js" - }, - "2": { - "name": "variable.parameter.js" - } - } - }, - { - "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)", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js" - } - }, - "end": "(?==>|\\{|(^\\s*(export|function|class|interface|let|var|const|import|enum|namespace|module|type|abstract|declare)\\s+))", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-parameters" - }, - { - "include": "#function-parameters" - }, - { - "include": "#arrow-return-type" - }, - { - "include": "#possibly-arrow-return-type" - } - ] - }, - { - "name": "meta.arrow.js", - "begin": "=>", - "beginCaptures": { - "0": { - "name": "storage.type.function.arrow.js" - } - }, - "end": "((?<=\\}|\\S)(?)|((?!\\{)(?=\\S)))(?!\\/[\\/\\*])", - "patterns": [ - { - "include": "#single-line-comment-consuming-line-ending" - }, - { - "include": "#decl-block" - }, - { - "include": "#expression" - } - ] - } - ] - }, - "indexer-declaration": { - "name": "meta.indexer.declaration.js", - "begin": "(?:(?]|^await|[^\\._$[:alnum:]]await|^return|[^\\._$[:alnum:]]return|^yield|[^\\._$[:alnum:]]yield|^throw|[^\\._$[:alnum:]]throw|^in|[^\\._$[:alnum:]]in|^of|[^\\._$[:alnum:]]of|^typeof|[^\\._$[:alnum:]]typeof|&&|\\|\\||\\*)\\s*(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.block.js" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.block.js" - } - }, - "patterns": [ - { - "include": "#object-member" - } - ] - }, - "object-literal": { - "name": "meta.objectliteral.js", - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.block.js" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.block.js" - } - }, - "patterns": [ - { - "include": "#object-member" - } - ] - }, - "object-member": { - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#object-literal-method-declaration" - }, - { - "name": "meta.object.member.js meta.object-literal.key.js", - "begin": "(?=\\[)", - "end": "(?=:)|((?<=[\\]])(?=\\s*[\\(\\<]))", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#array-literal" - } - ] - }, - { - "name": "meta.object.member.js meta.object-literal.key.js", - "begin": "(?=[\\'\\\"\\`])", - "end": "(?=:)|((?<=[\\'\\\"\\`])(?=((\\s*[\\(\\<,}])|(\\s+(as)\\s+))))", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#string" - } - ] - }, - { - "name": "meta.object.member.js meta.object-literal.key.js", - "begin": "(?x)(?=(\\b(?)\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" - }, - "1": { - "name": "entity.name.function.js" - } - } - }, - { - "name": "meta.object.member.js", - "match": "(?:[_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:)", - "captures": { - "0": { - "name": "meta.object-literal.key.js" - } - } - }, - { - "name": "meta.object.member.js", - "begin": "\\.\\.\\.", - "beginCaptures": { - "0": { - "name": "keyword.operator.spread.js" - } - }, - "end": "(?=,|\\})", - "patterns": [ - { - "include": "#expression" - } - ] - }, - { - "name": "meta.object.member.js", - "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=,|\\}|$|\\/\\/|\\/\\*)", - "captures": { - "1": { - "name": "variable.other.readwrite.js" - } - } - }, - { - "name": "meta.object.member.js", - "match": "(?]|\\|\\||\\&\\&|\\!\\=\\=|$|^|((?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js" - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#type-parameters" - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "meta.brace.round.js" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "meta.brace.round.js" - } - }, - "patterns": [ - { - "include": "#expression-inside-possibly-arrow-parens" - } - ] - } - ] - }, - { - "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js" - }, - "2": { - "name": "meta.brace.round.js" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "meta.brace.round.js" - } - }, - "patterns": [ - { - "include": "#expression-inside-possibly-arrow-parens" - } - ] - }, - { - "begin": "(?<=:)\\s*(async)?\\s*(?=\\<\\s*$)", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js" - } - }, - "end": "(?<=\\>)", - "patterns": [ - { - "include": "#type-parameters" - } - ] - }, - { - "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", - "beginCaptures": { - "1": { - "name": "meta.brace.round.js" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "meta.brace.round.js" - } - }, - "patterns": [ - { - "include": "#expression-inside-possibly-arrow-parens" - } - ] - }, - { - "include": "#possibly-arrow-return-type" - }, - { - "include": "#expression" - } - ] - }, - { - "include": "#punctuation-comma" - } - ] - }, - "ternary-expression": { - "begin": "(?!\\?\\.\\s*[^[:digit:]])(\\?)(?!\\?)", - "beginCaptures": { - "1": { - "name": "keyword.operator.ternary.js" - } - }, - "end": "\\s*(:)", - "endCaptures": { - "1": { - "name": "keyword.operator.ternary.js" - } - }, - "patterns": [ - { - "include": "#expression" - } - ] - }, - "function-call": { - "patterns": [ - { - "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", - "patterns": [ - { - "name": "meta.function-call.js", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", - "patterns": [ - { - "include": "#function-call-target" - } - ] - }, - { - "include": "#comment" - }, - { - "include": "#function-call-optionals" - }, - { - "include": "#type-arguments" - }, - { - "include": "#paren-expression" - } - ] - }, - { - "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", - "end": "(?<=\\>)(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", - "patterns": [ - { - "name": "meta.function-call.js", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=(<\\s*[\\{\\[\\(]\\s*$))", - "patterns": [ - { - "include": "#function-call-target" - } - ] - }, - { - "include": "#comment" - }, - { - "include": "#function-call-optionals" - }, - { - "include": "#type-arguments" - } - ] - } - ] - }, - "function-call-target": { - "patterns": [ - { - "include": "#support-function-call-identifiers" - }, - { - "name": "entity.name.function.js", - "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" - } - ] - }, - "function-call-optionals": { - "patterns": [ - { - "name": "meta.function-call.js punctuation.accessor.optional.js", - "match": "\\?\\." - }, - { - "name": "meta.function-call.js keyword.operator.definiteassignment.js", - "match": "\\!" - } - ] - }, - "support-function-call-identifiers": { - "patterns": [ - { - "include": "#literal" - }, - { - "include": "#support-objects" - }, - { - "include": "#object-identifiers" - }, - { - "include": "#punctuation-accessor" - }, - { - "name": "keyword.operator.expression.import.js", - "match": "(?:(?]|\\|\\||\\&\\&|\\!\\=\\=|$|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|(([\\&\\~\\^\\|]\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s+instanceof(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\(\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js" - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#paren-expression-possibly-arrow-with-typeparameters" - } - ] - }, - { - "begin": "(?<=[(=,]|=>|^return|[^\\._$[:alnum:]]return)\\s*(async)?(?=\\s*((((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\()|(<))\\s*$)", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js" - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#paren-expression-possibly-arrow-with-typeparameters" - } - ] - }, - { - "include": "#possibly-arrow-return-type" - } - ] - }, - "paren-expression-possibly-arrow-with-typeparameters": { - "patterns": [ - { - "include": "#type-parameters" - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "meta.brace.round.js" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "meta.brace.round.js" - } - }, - "patterns": [ - { - "include": "#expression-inside-possibly-arrow-parens" - } - ] - } - ] - }, - "expression-inside-possibly-arrow-parens": { - "patterns": [ - { - "include": "#expressionWithoutIdentifiers" - }, - { - "include": "#comment" - }, - { - "include": "#string" - }, - { - "include": "#decorator" - }, - { - "include": "#destructuring-parameter" - }, - { - "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)))", - "captures": { - "1": { - "name": "storage.modifier.js" - }, - "2": { - "name": "keyword.operator.rest.js" - }, - "3": { - "name": "entity.name.function.js variable.language.this.js" - }, - "4": { - "name": "entity.name.function.js" - }, - "5": { - "name": "keyword.operator.optional.js" - } - } - }, - { - "match": "(?x)(?:(?]|\\|\\||\\&\\&|\\!\\=\\=|$|((?>=|>>>=|\\|=" - }, - { - "name": "keyword.operator.bitwise.shift.js", - "match": "<<|>>>|>>" - }, - { - "name": "keyword.operator.comparison.js", - "match": "===|!==|==|!=" - }, - { - "name": "keyword.operator.relational.js", - "match": "<=|>=|<>|<|>" - }, - { - "match": "(?<=[_$[:alnum:]])(\\!)\\s*(?:(/=)|(?:(/)(?![/*])))", - "captures": { - "1": { - "name": "keyword.operator.logical.js" - }, - "2": { - "name": "keyword.operator.assignment.compound.js" - }, - "3": { - "name": "keyword.operator.arithmetic.js" - } - } - }, - { - "name": "keyword.operator.logical.js", - "match": "\\!|&&|\\|\\||\\?\\?" - }, - { - "name": "keyword.operator.bitwise.js", - "match": "\\&|~|\\^|\\|" - }, - { - "name": "keyword.operator.assignment.js", - "match": "\\=" - }, - { - "name": "keyword.operator.decrement.js", - "match": "--" - }, - { - "name": "keyword.operator.increment.js", - "match": "\\+\\+" - }, - { - "name": "keyword.operator.arithmetic.js", - "match": "%|\\*|/|-|\\+" - }, - { - "begin": "(?<=[_$[:alnum:])\\]])\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)+(?:(/=)|(?:(/)(?![/*]))))", - "end": "(?:(/=)|(?:(/)(?!\\*([^\\*]|(\\*[^\\/]))*\\*\\/)))", - "endCaptures": { - "1": { - "name": "keyword.operator.assignment.compound.js" - }, - "2": { - "name": "keyword.operator.arithmetic.js" - } - }, - "patterns": [ - { - "include": "#comment" - } - ] - }, - { - "match": "(?<=[_$[:alnum:])\\]])\\s*(?:(/=)|(?:(/)(?![/*])))", - "captures": { - "1": { - "name": "keyword.operator.assignment.compound.js" - }, - "2": { - "name": "keyword.operator.arithmetic.js" - } - } - } - ] - }, - "typeof-operator": { - "begin": "(?:&|{\\?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", - "patterns": [ - { - "include": "#expression" - } - ] - }, - "literal": { - "patterns": [ - { - "include": "#numeric-literal" - }, - { - "include": "#boolean-literal" - }, - { - "include": "#null-literal" - }, - { - "include": "#undefined-literal" - }, - { - "include": "#numericConstant-literal" - }, - { - "include": "#array-literal" - }, - { - "include": "#this-literal" - }, - { - "include": "#super-literal" - } - ] - }, - "array-literal": { - "name": "meta.array.literal.js", - "begin": "\\s*(\\[)", - "beginCaptures": { - "1": { - "name": "meta.brace.square.js" - } - }, - "end": "\\]", - "endCaptures": { - "0": { - "name": "meta.brace.square.js" - } - }, - "patterns": [ - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "numeric-literal": { - "patterns": [ - { - "name": "constant.numeric.hex.js", - "match": "\\b(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\\())\n |\n (?:(EPSILON|MAX_SAFE_INTEGER|MAX_VALUE|MIN_SAFE_INTEGER|MIN_VALUE|NEGATIVE_INFINITY|POSITIVE_INFINITY)\\b(?!\\$)))", - "captures": { - "1": { - "name": "punctuation.accessor.js" - }, - "2": { - "name": "punctuation.accessor.optional.js" - }, - "3": { - "name": "support.variable.property.js" - }, - "4": { - "name": "support.constant.js" - } - } - }, - { - "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))", - "captures": { - "1": { - "name": "punctuation.accessor.js" - }, - "2": { - "name": "punctuation.accessor.optional.js" - }, - "3": { - "name": "entity.name.function.js" - } - } - }, - { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", - "captures": { - "1": { - "name": "punctuation.accessor.js" - }, - "2": { - "name": "punctuation.accessor.optional.js" - }, - "3": { - "name": "variable.other.constant.property.js" - } - } - }, - { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*)", - "captures": { - "1": { - "name": "punctuation.accessor.js" - }, - "2": { - "name": "punctuation.accessor.optional.js" - }, - "3": { - "name": "variable.other.property.js" - } - } - }, - { - "name": "variable.other.constant.js", - "match": "([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])" - }, - { - "name": "variable.other.readwrite.js", - "match": "[_$[:alpha:]][_$[:alnum:]]*" - } - ] - }, - "object-identifiers": { - "patterns": [ - { - "name": "support.class.js", - "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" - }, - { - "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n (\\#?[[:upper:]][_$[:digit:][:upper:]]*) |\n (\\#?[_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", - "captures": { - "1": { - "name": "punctuation.accessor.js" - }, - "2": { - "name": "punctuation.accessor.optional.js" - }, - "3": { - "name": "variable.other.constant.object.property.js" - }, - "4": { - "name": "variable.other.object.property.js" - } - } - }, - { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", - "captures": { - "1": { - "name": "variable.other.constant.object.js" - }, - "2": { - "name": "variable.other.object.js" - } - } - } - ] - }, - "type-annotation": { - "patterns": [ - { - "name": "meta.type.annotation.js", - "begin": "(:)(?=\\s*\\S)", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.annotation.js" - } - }, - "end": "(?])|((?<=[\\}>\\]\\)]|[_$[:alpha:]])\\s*(?=\\{)))", - "patterns": [ - { - "include": "#type" - } - ] - }, - { - "name": "meta.type.annotation.js", - "begin": "(:)", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.annotation.js" - } - }, - "end": "(?])|(?=^\\s*$)|((?<=\\S)(?=\\s*$))|((?<=[\\}>\\]\\)]|[_$[:alpha:]])\\s*(?=\\{)))", - "patterns": [ - { - "include": "#type" - } - ] - } - ] - }, - "parameter-type-annotation": { - "patterns": [ - { - "name": "meta.type.annotation.js", - "begin": "(:)", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.annotation.js" - } - }, - "end": "(?=[,)])|(?==[^>])", - "patterns": [ - { - "include": "#type" - } - ] - } - ] - }, - "return-type": { - "patterns": [ - { - "name": "meta.return.type.js", - "begin": "(?<=\\))\\s*(:)(?=\\s*\\S)", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.annotation.js" - } - }, - "end": "(?|\\{|(^\\s*(export|function|class|interface|let|var|const|import|enum|namespace|module|type|abstract|declare)\\s+))", - "patterns": [ - { - "include": "#arrow-return-type-body" - } - ] - }, - "possibly-arrow-return-type": { - "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", - "beginCaptures": { - "1": { - "name": "meta.arrow.js meta.return.type.arrow.js keyword.operator.type.annotation.js" - } - }, - "end": "(?==>|\\{|(^\\s*(export|function|class|interface|let|var|const|import|enum|namespace|module|type|abstract|declare)\\s+))", - "contentName": "meta.arrow.js meta.return.type.arrow.js", - "patterns": [ - { - "include": "#arrow-return-type-body" - } - ] - }, - "arrow-return-type-body": { - "patterns": [ - { - "begin": "(?<=[:])(?=\\s*\\{)", - "end": "(?<=\\})", - "patterns": [ - { - "include": "#type-object" - } - ] - }, - { - "include": "#type-predicate-operator" - }, - { - "include": "#type" - } - ] - }, - "type-parameters": { - "name": "meta.type.parameters.js", - "begin": "(<)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.typeparameters.begin.js" - } - }, - "end": "(>)", - "endCaptures": { - "1": { - "name": "punctuation.definition.typeparameters.end.js" - } - }, - "patterns": [ - { - "include": "#comment" - }, - { - "name": "storage.modifier.js", - "match": "(?)" - } - ] - }, - "type-arguments": { - "name": "meta.type.parameters.js", - "begin": "\\<", - "beginCaptures": { - "0": { - "name": "punctuation.definition.typeparameters.begin.js" - } - }, - "end": "\\>", - "endCaptures": { - "0": { - "name": "punctuation.definition.typeparameters.end.js" - } - }, - "patterns": [ - { - "include": "#type-arguments-body" - } - ] - }, - "type-arguments-body": { - "patterns": [ - { - "match": "(?)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", - "captures": { - "1": { - "name": "storage.modifier.js" - }, - "2": { - "name": "keyword.operator.rest.js" - }, - "3": { - "name": "entity.name.function.js variable.language.this.js" - }, - "4": { - "name": "entity.name.function.js" - }, - "5": { - "name": "keyword.operator.optional.js" - } - } - }, - { - "match": "(?x)(?:(?)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-parameters" - } - ] - }, - { - "name": "meta.type.constructor.js", - "begin": "(?)\n ))\n )\n )\n)", - "end": "(?<=\\))", - "patterns": [ - { - "include": "#function-parameters" - } - ] - } - ] - }, - "type-function-return-type": { - "patterns": [ - { - "name": "meta.type.function.return.js", - "begin": "(=>)(?=\\s*\\S)", - "beginCaptures": { - "1": { - "name": "storage.type.function.arrow.js" - } - }, - "end": "(?)(?:\\?]|//|$)", - "patterns": [ - { - "include": "#type-function-return-type-core" - } - ] - }, - { - "name": "meta.type.function.return.js", - "begin": "=>", - "beginCaptures": { - "0": { - "name": "storage.type.function.arrow.js" - } - }, - "end": "(?)(?]|//|^\\s*$)|((?<=\\S)(?=\\s*$)))", - "patterns": [ - { - "include": "#type-function-return-type-core" - } - ] - } - ] - }, - "type-function-return-type-core": { - "patterns": [ - { - "include": "#comment" - }, - { - "begin": "(?<==>)(?=\\s*\\{)", - "end": "(?<=\\})", - "patterns": [ - { - "include": "#type-object" - } - ] - }, - { - "include": "#type-predicate-operator" - }, - { - "include": "#type" - } - ] - }, - "type-operators": { - "patterns": [ - { - "include": "#typeof-operator" - }, - { - "include": "#type-infer" - }, - { - "begin": "([&|])(?=\\s*\\{)", - "beginCaptures": { - "0": { - "name": "keyword.operator.type.js" - } - }, - "end": "(?<=\\})", - "patterns": [ - { - "include": "#type-object" - } - ] - }, - { - "begin": "[&|]", - "beginCaptures": { - "0": { - "name": "keyword.operator.type.js" - } - }, - "end": "(?=\\S)" - }, - { - "name": "keyword.operator.expression.keyof.js", - "match": "(?)", - "endCaptures": { - "1": { - "name": "meta.type.parameters.js punctuation.definition.typeparameters.end.js" - } - }, - "contentName": "meta.type.parameters.js", - "patterns": [ - { - "include": "#type-arguments-body" - } - ] - }, - { - "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(<)", - "beginCaptures": { - "1": { - "name": "entity.name.type.js" - }, - "2": { - "name": "meta.type.parameters.js punctuation.definition.typeparameters.begin.js" - } - }, - "end": "(>)", - "endCaptures": { - "1": { - "name": "meta.type.parameters.js punctuation.definition.typeparameters.end.js" - } - }, - "contentName": "meta.type.parameters.js", - "patterns": [ - { - "include": "#type-arguments-body" - } - ] - }, - { - "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))", - "captures": { - "1": { - "name": "entity.name.type.module.js" - }, - "2": { - "name": "punctuation.accessor.js" - }, - "3": { - "name": "punctuation.accessor.optional.js" - } - } - }, - { - "name": "entity.name.type.js", - "match": "[_$[:alpha:]][_$[:alnum:]]*" - } - ] - }, - "punctuation-comma": { - "name": "punctuation.separator.comma.js", - "match": "," - }, - "punctuation-semicolon": { - "name": "punctuation.terminator.statement.js", - "match": ";" - }, - "punctuation-accessor": { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))", - "captures": { - "1": { - "name": "punctuation.accessor.js" - }, - "2": { - "name": "punctuation.accessor.optional.js" - } - } - }, - "string": { - "patterns": [ - { - "include": "#qstring-single" - }, - { - "include": "#qstring-double" - }, - { - "include": "#template" - } - ] - }, - "qstring-double": { - "name": "string.quoted.double.js", - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.js" - } - }, - "end": "(\")|((?:[^\\\\\\n])$)", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.js" - }, - "2": { - "name": "invalid.illegal.newline.js" - } - }, - "patterns": [ - { - "include": "#string-character-escape" - } - ] - }, - "qstring-single": { - "name": "string.quoted.single.js", - "begin": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.js" - } - }, - "end": "(\\')|((?:[^\\\\\\n])$)", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.js" - }, - "2": { - "name": "invalid.illegal.newline.js" - } - }, - "patterns": [ - { - "include": "#string-character-escape" - } - ] - }, - "string-character-escape": { - "name": "constant.character.escape.js", - "match": "\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|u\\{[0-9A-Fa-f]+\\}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)" - }, - "template": { - "patterns": [ - { - "include": "#template-call" - }, - { - "name": "string.template.js", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)?(`)", - "beginCaptures": { - "1": { - "name": "entity.name.function.tagged-template.js" - }, - "2": { - "name": "punctuation.definition.string.template.begin.js" - } - }, - "end": "`", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.template.end.js" - } - }, - "patterns": [ - { - "include": "#template-substitution-element" - }, - { - "include": "#string-character-escape" - } - ] - } - ] - }, - "template-call": { - "patterns": [ - { - "name": "string.template.js", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", - "end": "(?=`)", - "patterns": [ - { - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", - "patterns": [ - { - "include": "#support-function-call-identifiers" - }, - { - "name": "entity.name.function.tagged-template.js", - "match": "([_$[:alpha:]][_$[:alnum:]]*)" - } - ] - }, - { - "include": "#type-arguments" - } - ] - }, - { - "name": "string.template.js", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)?\\s*(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)`)", - "beginCaptures": { - "1": { - "name": "entity.name.function.tagged-template.js" - } - }, - "end": "(?=`)", - "patterns": [ - { - "include": "#type-arguments" - } - ] - } - ] - }, - "template-substitution-element": { - "name": "meta.template.expression.js", - "begin": "\\$\\{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.template-expression.begin.js" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.template-expression.end.js" - } - }, - "patterns": [ - { - "include": "#expression" - } - ], - "contentName": "meta.embedded.line.js" - }, - "type-string": { - "patterns": [ - { - "include": "#qstring-single" - }, - { - "include": "#qstring-double" - }, - { - "include": "#template-type" - } - ] - }, - "template-type": { - "patterns": [ - { - "include": "#template-call" - }, - { - "name": "string.template.js", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)?(`)", - "beginCaptures": { - "1": { - "name": "entity.name.function.tagged-template.js" - }, - "2": { - "name": "punctuation.definition.string.template.begin.js" - } - }, - "end": "`", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.template.end.js" - } - }, - "patterns": [ - { - "include": "#template-type-substitution-element" - }, - { - "include": "#string-character-escape" - } - ] - } - ] - }, - "template-type-substitution-element": { - "name": "meta.template.expression.js", - "begin": "\\$\\{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.template-expression.begin.js" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.template-expression.end.js" - } - }, - "patterns": [ - { - "include": "#type" - } - ], - "contentName": "meta.embedded.line.js" - }, - "regex": { - "patterns": [ - { - "name": "string.regexp.js", - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([dgimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.js" - } - }, - "end": "(/)([dgimsuy]*)", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.js" - }, - "2": { - "name": "keyword.other.js" - } - }, - "patterns": [ - { - "include": "#regexp" - } - ] - }, - { - "name": "string.regexp.js", - "begin": "((?", - "captures": { - "0": { - "name": "keyword.other.back-reference.regexp" - }, - "1": { - "name": "variable.other.regexp" - } - } - }, - { - "name": "keyword.operator.quantifier.regexp", - "match": "[?+*]|\\{(\\d+,\\d+|\\d+,|,\\d+|\\d+)\\}\\??" - }, - { - "name": "keyword.operator.or.regexp", - "match": "\\|" - }, - { - "name": "meta.group.assertion.regexp", - "begin": "(\\()((\\?=)|(\\?!)|(\\?<=)|(\\?))?", - "beginCaptures": { - "0": { - "name": "punctuation.definition.group.regexp" - }, - "1": { - "name": "punctuation.definition.group.no-capture.regexp" - }, - "2": { - "name": "variable.other.regexp" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.group.regexp" - } - }, - "patterns": [ - { - "include": "#regexp" - } - ] - }, - { - "name": "constant.other.character-class.set.regexp", - "begin": "(\\[)(\\^)?", - "beginCaptures": { - "1": { - "name": "punctuation.definition.character-class.regexp" - }, - "2": { - "name": "keyword.operator.negation.regexp" - } - }, - "end": "(\\])", - "endCaptures": { - "1": { - "name": "punctuation.definition.character-class.regexp" - } - }, - "patterns": [ - { - "name": "constant.other.character-class.range.regexp", - "match": "(?:.|(\\\\(?:[0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}))|(\\\\c[A-Z])|(\\\\.))\\-(?:[^\\]\\\\]|(\\\\(?:[0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}))|(\\\\c[A-Z])|(\\\\.))", - "captures": { - "1": { - "name": "constant.character.numeric.regexp" - }, - "2": { - "name": "constant.character.control.regexp" - }, - "3": { - "name": "constant.character.escape.backslash.regexp" - }, - "4": { - "name": "constant.character.numeric.regexp" - }, - "5": { - "name": "constant.character.control.regexp" - }, - "6": { - "name": "constant.character.escape.backslash.regexp" - } - } - }, - { - "include": "#regex-character-class" - } - ] - }, - { - "include": "#regex-character-class" - } - ] - }, - "regex-character-class": { - "patterns": [ - { - "name": "constant.other.character-class.regexp", - "match": "\\\\[wWsSdDtrnvf]|\\." - }, - { - "name": "constant.character.numeric.regexp", - "match": "\\\\([0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4})" - }, - { - "name": "constant.character.control.regexp", - "match": "\\\\c[A-Z]" - }, - { - "name": "constant.character.escape.backslash.regexp", - "match": "\\\\." - } - ] - }, - "comment": { - "patterns": [ - { - "name": "comment.block.documentation.js", - "begin": "/\\*\\*(?!/)", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.js" - } - }, - "end": "\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.js" - } - }, - "patterns": [ - { - "include": "#docblock" - } - ] - }, - { - "name": "comment.block.js", - "begin": "(/\\*)(?:\\s*((@)internal)(?=\\s|(\\*/)))?", - "beginCaptures": { - "1": { - "name": "punctuation.definition.comment.js" - }, - "2": { - "name": "storage.type.internaldeclaration.js" - }, - "3": { - "name": "punctuation.decorator.internaldeclaration.js" - } - }, - "end": "\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.js" - } - } - }, - { - "begin": "(^[ \\t]+)?((//)(?:\\s*((@)internal)(?=\\s|$))?)", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.js" - }, - "2": { - "name": "comment.line.double-slash.js" - }, - "3": { - "name": "punctuation.definition.comment.js" - }, - "4": { - "name": "storage.type.internaldeclaration.js" - }, - "5": { - "name": "punctuation.decorator.internaldeclaration.js" - } - }, - "end": "(?=$)", - "contentName": "comment.line.double-slash.js" - } - ] - }, - "single-line-comment-consuming-line-ending": { - "begin": "(^[ \\t]+)?((//)(?:\\s*((@)internal)(?=\\s|$))?)", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.js" - }, - "2": { - "name": "comment.line.double-slash.js" - }, - "3": { - "name": "punctuation.definition.comment.js" - }, - "4": { - "name": "storage.type.internaldeclaration.js" - }, - "5": { - "name": "punctuation.decorator.internaldeclaration.js" - } - }, - "end": "(?=^)", - "contentName": "comment.line.double-slash.js" - }, - "directives": { - "name": "comment.line.triple-slash.directive.js", - "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name|resolution-mode)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.comment.js" - } - }, - "end": "(?=$)", - "patterns": [ - { - "name": "meta.tag.js", - "begin": "(<)(reference|amd-dependency|amd-module)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.directive.js" - }, - "2": { - "name": "entity.name.tag.directive.js" - } - }, - "end": "/>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.directive.js" - } - }, - "patterns": [ - { - "name": "entity.other.attribute-name.directive.js", - "match": "path|types|no-default-lib|lib|name|resolution-mode" - }, - { - "name": "keyword.operator.assignment.js", - "match": "=" - }, - { - "include": "#string" - } - ] - } - ] - }, - "docblock": { - "patterns": [ - { - "match": "(?x)\n((@)(?:access|api))\n\\s+\n(private|protected|public)\n\\b", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "constant.language.access-type.jsdoc" - } - } - }, - { - "match": "(?x)\n((@)author)\n\\s+\n(\n [^@\\s<>*/]\n (?:[^@<>*/]|\\*[^/])*\n)\n(?:\n \\s*\n (<)\n ([^>\\s]+)\n (>)\n)?", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "entity.name.type.instance.jsdoc" - }, - "4": { - "name": "punctuation.definition.bracket.angle.begin.jsdoc" - }, - "5": { - "name": "constant.other.email.link.underline.jsdoc" - }, - "6": { - "name": "punctuation.definition.bracket.angle.end.jsdoc" - } - } - }, - { - "match": "(?x)\n((@)borrows) \\s+\n((?:[^@\\s*/]|\\*[^/])+) # \n\\s+ (as) \\s+ # as\n((?:[^@\\s*/]|\\*[^/])+) # ", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "entity.name.type.instance.jsdoc" - }, - "4": { - "name": "keyword.operator.control.jsdoc" - }, - "5": { - "name": "entity.name.type.instance.jsdoc" - } - } - }, - { - "name": "meta.example.jsdoc", - "begin": "((@)example)\\s+", - "end": "(?=@|\\*/)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "patterns": [ - { - "match": "^\\s\\*\\s+" - }, - { - "contentName": "constant.other.description.jsdoc", - "begin": "\\G(<)caption(>)", - "beginCaptures": { - "0": { - "name": "entity.name.tag.inline.jsdoc" - }, - "1": { - "name": "punctuation.definition.bracket.angle.begin.jsdoc" - }, - "2": { - "name": "punctuation.definition.bracket.angle.end.jsdoc" - } - }, - "end": "()|(?=\\*/)", - "endCaptures": { - "0": { - "name": "entity.name.tag.inline.jsdoc" - }, - "1": { - "name": "punctuation.definition.bracket.angle.begin.jsdoc" - }, - "2": { - "name": "punctuation.definition.bracket.angle.end.jsdoc" - } - } - }, - { - "match": "[^\\s@*](?:[^*]|\\*[^/])*", - "captures": { - "0": { - "name": "source.embedded.js" - } - } - } - ] - }, - { - "match": "(?x) ((@)kind) \\s+ (class|constant|event|external|file|function|member|mixin|module|namespace|typedef) \\b", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "constant.language.symbol-type.jsdoc" - } - } - }, - { - "match": "(?x)\n((@)see)\n\\s+\n(?:\n # URL\n (\n (?=https?://)\n (?:[^\\s*]|\\*[^/])+\n )\n |\n # JSDoc namepath\n (\n (?!\n # Avoid matching bare URIs (also acceptable as links)\n https?://\n |\n # Avoid matching {@inline tags}; we match those below\n (?:\\[[^\\[\\]]*\\])? # Possible description [preceding]{@tag}\n {@(?:link|linkcode|linkplain|tutorial)\\b\n )\n # Matched namepath\n (?:[^@\\s*/]|\\*[^/])+\n )\n)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.link.underline.jsdoc" - }, - "4": { - "name": "entity.name.type.instance.jsdoc" - } - } - }, - { - "match": "(?x)\n((@)template)\n\\s+\n# One or more valid identifiers\n(\n [A-Za-z_$] # First character: non-numeric word character\n [\\w$.\\[\\]]* # Rest of identifier\n (?: # Possible list of additional identifiers\n \\s* , \\s*\n [A-Za-z_$]\n [\\w$.\\[\\]]*\n )*\n)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.jsdoc" - } - } - }, - { - "begin": "(?x)((@)template)\\s+(?={)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", - "patterns": [ - { - "include": "#jsdoctype" - }, - { - "name": "variable.other.jsdoc", - "match": "([A-Za-z_$][\\w$.\\[\\]]*)" - } - ] - }, - { - "match": "(?x)\n(\n (@)\n (?:arg|argument|const|constant|member|namespace|param|var)\n)\n\\s+\n(\n [A-Za-z_$]\n [\\w$.\\[\\]]*\n)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.jsdoc" - } - } - }, - { - "begin": "((@)typedef)\\s+(?={)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", - "patterns": [ - { - "include": "#jsdoctype" - }, - { - "name": "entity.name.type.instance.jsdoc", - "match": "(?:[^@\\s*/]|\\*[^/])+" - } - ] - }, - { - "begin": "((@)(?:arg|argument|const|constant|member|namespace|param|prop|property|var))\\s+(?={)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", - "patterns": [ - { - "include": "#jsdoctype" - }, - { - "name": "variable.other.jsdoc", - "match": "([A-Za-z_$][\\w$.\\[\\]]*)" - }, - { - "name": "variable.other.jsdoc", - "match": "(?x)\n(\\[)\\s*\n[\\w$]+\n(?:\n (?:\\[\\])? # Foo[ ].bar properties within an array\n \\. # Foo.Bar namespaced parameter\n [\\w$]+\n)*\n(?:\n \\s*\n (=) # [foo=bar] Default parameter value\n \\s*\n (\n # The inner regexes are to stop the match early at */ and to not stop at escaped quotes\n (?>\n \"(?:(?:\\*(?!/))|(?:\\\\(?!\"))|[^*\\\\])*?\" | # [foo=\"bar\"] Double-quoted\n '(?:(?:\\*(?!/))|(?:\\\\(?!'))|[^*\\\\])*?' | # [foo='bar'] Single-quoted\n \\[ (?:(?:\\*(?!/))|[^*])*? \\] | # [foo=[1,2]] Array literal\n (?:(?:\\*(?!/))|\\s(?!\\s*\\])|\\[.*?(?:\\]|(?=\\*/))|[^*\\s\\[\\]])* # Everything else\n )*\n )\n)?\n\\s*(?:(\\])((?:[^*\\s]|\\*[^\\s/])+)?|(?=\\*/))", - "captures": { - "1": { - "name": "punctuation.definition.optional-value.begin.bracket.square.jsdoc" - }, - "2": { - "name": "keyword.operator.assignment.jsdoc" - }, - "3": { - "name": "source.embedded.js" - }, - "4": { - "name": "punctuation.definition.optional-value.end.bracket.square.jsdoc" - }, - "5": { - "name": "invalid.illegal.syntax.jsdoc" - } - } - } - ] - }, - { - "begin": "(?x)\n(\n (@)\n (?:define|enum|exception|export|extends|lends|implements|modifies\n |namespace|private|protected|returns?|suppress|this|throws|type\n |yields?)\n)\n\\s+(?={)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", - "patterns": [ - { - "include": "#jsdoctype" - } - ] - }, - { - "match": "(?x)\n(\n (@)\n (?:alias|augments|callback|constructs|emits|event|fires|exports?\n |extends|external|function|func|host|lends|listens|interface|memberof!?\n |method|module|mixes|mixin|name|requires|see|this|typedef|uses)\n)\n\\s+\n(\n (?:\n [^{}@\\s*] | \\*[^/]\n )+\n)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "entity.name.type.instance.jsdoc" - } - } - }, - { - "contentName": "variable.other.jsdoc", - "begin": "((@)(?:default(?:value)?|license|version))\\s+(([''\"]))", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.jsdoc" - }, - "4": { - "name": "punctuation.definition.string.begin.jsdoc" - } - }, - "end": "(\\3)|(?=$|\\*/)", - "endCaptures": { - "0": { - "name": "variable.other.jsdoc" - }, - "1": { - "name": "punctuation.definition.string.end.jsdoc" - } - } - }, - { - "match": "((@)(?:default(?:value)?|license|tutorial|variation|version))\\s+([^\\s*]+)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.jsdoc" - } - } - }, - { - "name": "storage.type.class.jsdoc", - "match": "(?x) (@) (?:abstract|access|alias|api|arg|argument|async|attribute|augments|author|beta|borrows|bubbles |callback|chainable|class|classdesc|code|config|const|constant|constructor|constructs|copyright |default|defaultvalue|define|deprecated|desc|description|dict|emits|enum|event|example|exception |exports?|extends|extension(?:_?for)?|external|externs|file|fileoverview|final|fires|for|func |function|generator|global|hideconstructor|host|ignore|implements|implicitCast|inherit[Dd]oc |inner|instance|interface|internal|kind|lends|license|listens|main|member|memberof!?|method |mixes|mixins?|modifies|module|name|namespace|noalias|nocollapse|nocompile|nosideeffects |override|overview|package|param|polymer(?:Behavior)?|preserve|private|prop|property|protected |public|read[Oo]nly|record|require[ds]|returns?|see|since|static|struct|submodule|summary |suppress|template|this|throws|todo|tutorial|type|typedef|unrestricted|uses|var|variation |version|virtual|writeOnce|yields?) \\b", - "captures": { - "1": { - "name": "punctuation.definition.block.tag.jsdoc" - } - } - }, - { - "include": "#inline-tags" - }, - { - "match": "((@)(?:[_$[:alpha:]][_$[:alnum:]]*))(?=\\s+)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - } - } - ] - }, - "brackets": { - "patterns": [ - { - "begin": "{", - "end": "}|(?=\\*/)", - "patterns": [ - { - "include": "#brackets" - } - ] - }, - { - "begin": "\\[", - "end": "\\]|(?=\\*/)", - "patterns": [ - { - "include": "#brackets" - } - ] - } - ] - }, - "inline-tags": { - "patterns": [ - { - "name": "constant.other.description.jsdoc", - "match": "(\\[)[^\\]]+(\\])(?={@(?:link|linkcode|linkplain|tutorial))", - "captures": { - "1": { - "name": "punctuation.definition.bracket.square.begin.jsdoc" - }, - "2": { - "name": "punctuation.definition.bracket.square.end.jsdoc" - } - } - }, - { - "name": "entity.name.type.instance.jsdoc", - "begin": "({)((@)(?:link(?:code|plain)?|tutorial))\\s*", - "beginCaptures": { - "1": { - "name": "punctuation.definition.bracket.curly.begin.jsdoc" - }, - "2": { - "name": "storage.type.class.jsdoc" - }, - "3": { - "name": "punctuation.definition.inline.tag.jsdoc" - } - }, - "end": "}|(?=\\*/)", - "endCaptures": { - "0": { - "name": "punctuation.definition.bracket.curly.end.jsdoc" - } - }, - "patterns": [ - { - "match": "\\G((?=https?://)(?:[^|}\\s*]|\\*[/])+)(\\|)?", - "captures": { - "1": { - "name": "variable.other.link.underline.jsdoc" - }, - "2": { - "name": "punctuation.separator.pipe.jsdoc" - } - } - }, - { - "match": "\\G((?:[^{}@\\s|*]|\\*[^/])+)(\\|)?", - "captures": { - "1": { - "name": "variable.other.description.jsdoc" - }, - "2": { - "name": "punctuation.separator.pipe.jsdoc" - } - } - } - ] - } - ] - }, - "jsdoctype": { - "patterns": [ - { - "contentName": "entity.name.type.instance.jsdoc", - "begin": "\\G({)", - "beginCaptures": { - "0": { - "name": "entity.name.type.instance.jsdoc" - }, - "1": { - "name": "punctuation.definition.bracket.curly.begin.jsdoc" - } - }, - "end": "((}))\\s*|(?=\\*/)", - "endCaptures": { - "1": { - "name": "entity.name.type.instance.jsdoc" - }, - "2": { - "name": "punctuation.definition.bracket.curly.end.jsdoc" - } - }, - "patterns": [ - { - "include": "#brackets" - } - ] - } - ] - }, - "jsx": { - "patterns": [ - { - "include": "#jsx-tag-without-attributes-in-expression" - }, - { - "include": "#jsx-tag-in-expression" - } - ] - }, - "jsx-tag-without-attributes-in-expression": { - "begin": "(?:*]|&&|\\|\\||\\?|\\*\\/|^await|[^\\._$[:alnum:]]await|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^yield|[^\\._$[:alnum:]]yield|^)\\s*(?=(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "end": "(?!(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "patterns": [ - { - "include": "#jsx-tag-without-attributes" - } - ] - }, - "jsx-tag-without-attributes": { - "name": "meta.tag.without-attributes.js", - "begin": "(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?)", - "end": "()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.js" - }, - "2": { - "name": "entity.name.tag.namespace.js" - }, - "3": { - "name": "punctuation.separator.namespace.js" - }, - "4": { - "name": "entity.name.tag.js" - }, - "5": { - "name": "support.class.component.js" - }, - "6": { - "name": "punctuation.definition.tag.end.js" - } - }, - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.js" - }, - "2": { - "name": "entity.name.tag.namespace.js" - }, - "3": { - "name": "punctuation.separator.namespace.js" - }, - "4": { - "name": "entity.name.tag.js" - }, - "5": { - "name": "support.class.component.js" - }, - "6": { - "name": "punctuation.definition.tag.end.js" - } - }, - "contentName": "meta.jsx.children.js", - "patterns": [ - { - "include": "#jsx-children" - } - ] - }, - "jsx-tag-in-expression": { - "begin": "(?x)\n (?:*]|&&|\\|\\||\\?|\\*\\/|^await|[^\\._$[:alnum:]]await|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^yield|[^\\._$[:alnum:]]yield|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "end": "(?!(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "patterns": [ - { - "include": "#jsx-tag" - } - ] - }, - "jsx-tag": { - "name": "meta.tag.js", - "begin": "(?=(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "end": "(/>)|(?:())", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.end.js" - }, - "2": { - "name": "punctuation.definition.tag.begin.js" - }, - "3": { - "name": "entity.name.tag.namespace.js" - }, - "4": { - "name": "punctuation.separator.namespace.js" - }, - "5": { - "name": "entity.name.tag.js" - }, - "6": { - "name": "support.class.component.js" - }, - "7": { - "name": "punctuation.definition.tag.end.js" - } - }, - "patterns": [ - { - "begin": "(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.js" - }, - "2": { - "name": "entity.name.tag.namespace.js" - }, - "3": { - "name": "punctuation.separator.namespace.js" - }, - "4": { - "name": "entity.name.tag.js" - }, - "5": { - "name": "support.class.component.js" - } - }, - "end": "(?=[/]?>)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-arguments" - }, - { - "include": "#jsx-tag-attributes" - } - ] - }, - { - "begin": "(>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.end.js" - } - }, - "end": "(?=)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#jsx-tag-attribute-name" - }, - { - "include": "#jsx-tag-attribute-assignment" - }, - { - "include": "#jsx-string-double-quoted" - }, - { - "include": "#jsx-string-single-quoted" - }, - { - "include": "#jsx-evaluated-code" - }, - { - "include": "#jsx-tag-attributes-illegal" - } - ] - }, - "jsx-tag-attribute-name": { - "match": "(?x)\n \\s*\n (?:([_$[:alpha:]][-_$[:alnum:].]*)(:))?\n ([_$[:alpha:]][-_$[:alnum:]]*)\n (?=\\s|=|/?>|/\\*|//)", - "captures": { - "1": { - "name": "entity.other.attribute-name.namespace.js" - }, - "2": { - "name": "punctuation.separator.namespace.js" - }, - "3": { - "name": "entity.other.attribute-name.js" - } - } - }, - "jsx-tag-attribute-assignment": { - "name": "keyword.operator.assignment.js", - "match": "=(?=\\s*(?:'|\"|{|/\\*|//|\\n))" - }, - "jsx-string-double-quoted": { - "name": "string.quoted.double.js", - "begin": "\"", - "end": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.js" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.js" - } - }, - "patterns": [ - { - "include": "#jsx-entities" - } - ] - }, - "jsx-string-single-quoted": { - "name": "string.quoted.single.js", - "begin": "'", - "end": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.js" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.js" - } - }, - "patterns": [ - { - "include": "#jsx-entities" - } - ] - }, - "jsx-tag-attributes-illegal": { - "name": "invalid.illegal.attribute.js", - "match": "\\S+" - } - } -} \ No newline at end of file diff --git a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json b/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json deleted file mode 100644 index 90476c2a91..0000000000 --- a/extensions/javascript/syntaxes/JavaScriptReact.tmLanguage.json +++ /dev/null @@ -1,5912 +0,0 @@ -{ - "information_for_contributors": [ - "This file has been converted from https://github.com/microsoft/TypeScript-TmLanguage/blob/master/TypeScriptReact.tmLanguage", - "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/0dfae8cc4807fecfbf8f1add095d9817df824c95", - "name": "JavaScript (with React support)", - "scopeName": "source.js.jsx", - "patterns": [ - { - "include": "#directives" - }, - { - "include": "#statements" - }, - { - "include": "#shebang" - } - ], - "repository": { - "shebang": { - "name": "comment.line.shebang.js.jsx", - "match": "\\A(#!).*(?=$)", - "captures": { - "1": { - "name": "punctuation.definition.comment.js.jsx" - } - } - }, - "statements": { - "patterns": [ - { - "include": "#declaration" - }, - { - "include": "#control-statement" - }, - { - "include": "#after-operator-block-as-object-literal" - }, - { - "include": "#decl-block" - }, - { - "include": "#label" - }, - { - "include": "#expression" - }, - { - "include": "#punctuation-semicolon" - }, - { - "include": "#string" - }, - { - "include": "#comment" - } - ] - }, - "declaration": { - "patterns": [ - { - "include": "#decorator" - }, - { - "include": "#var-expr" - }, - { - "include": "#function-declaration" - }, - { - "include": "#class-declaration" - }, - { - "include": "#interface-declaration" - }, - { - "include": "#enum-declaration" - }, - { - "include": "#namespace-declaration" - }, - { - "include": "#type-alias-declaration" - }, - { - "include": "#import-equals-declaration" - }, - { - "include": "#import-declaration" - }, - { - "include": "#export-declaration" - }, - { - "name": "storage.modifier.js.jsx", - "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)))", - "beginCaptures": { - "1": { - "name": "meta.definition.variable.js.jsx entity.name.function.js.jsx" - }, - "2": { - "name": "keyword.operator.definiteassignment.js.jsx" - } - }, - "end": "(?=$|^|[;,=}]|((?)\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" - } - }, - "end": "(?=$|^|[;,=}]|((?)\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" - }, - "2": { - "name": "keyword.operator.rest.js.jsx" - }, - "3": { - "name": "entity.name.function.js.jsx variable.language.this.js.jsx" - }, - "4": { - "name": "entity.name.function.js.jsx" - }, - "5": { - "name": "keyword.operator.optional.js.jsx" - } - } - }, - { - "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": "meta.definition.property.js.jsx entity.name.function.js.jsx" - }, - "2": { - "name": "keyword.operator.optional.js.jsx" - }, - "3": { - "name": "keyword.operator.definiteassignment.js.jsx" - } - } - }, - { - "name": "meta.definition.property.js.jsx variable.object.property.js.jsx", - "match": "\\#?[_$[:alpha:]][_$[:alnum:]]*" - }, - { - "name": "keyword.operator.optional.js.jsx", - "match": "\\?" - }, - { - "name": "keyword.operator.definiteassignment.js.jsx", - "match": "\\!" - } - ] - }, - "variable-initializer": { - "patterns": [ - { - "begin": "(?\\s*$)", - "beginCaptures": { - "1": { - "name": "keyword.operator.assignment.js.jsx" - } - }, - "end": "(?=$|^|[,);}\\]]|((?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", - "beginCaptures": { - "1": { - "name": "storage.modifier.js.jsx" - }, - "2": { - "name": "storage.modifier.js.jsx" - }, - "3": { - "name": "storage.modifier.js.jsx" - }, - "4": { - "name": "storage.modifier.async.js.jsx" - }, - "5": { - "name": "keyword.operator.new.js.jsx" - }, - "6": { - "name": "keyword.generator.asterisk.js.jsx" - } - }, - "end": "(?=\\}|;|,|$)|(?<=\\})", - "patterns": [ - { - "include": "#method-declaration-name" - }, - { - "include": "#function-body" - } - ] - }, - { - "name": "meta.method.declaration.js.jsx", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", - "beginCaptures": { - "1": { - "name": "storage.modifier.js.jsx" - }, - "2": { - "name": "storage.modifier.js.jsx" - }, - "3": { - "name": "storage.modifier.js.jsx" - }, - "4": { - "name": "storage.modifier.async.js.jsx" - }, - "5": { - "name": "storage.type.property.js.jsx" - }, - "6": { - "name": "keyword.generator.asterisk.js.jsx" - } - }, - "end": "(?=\\}|;|,|$)|(?<=\\})", - "patterns": [ - { - "include": "#method-declaration-name" - }, - { - "include": "#function-body" - } - ] - } - ] - }, - "object-literal-method-declaration": { - "name": "meta.method.declaration.js.jsx", - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js.jsx" - }, - "2": { - "name": "storage.type.property.js.jsx" - }, - "3": { - "name": "keyword.generator.asterisk.js.jsx" - } - }, - "end": "(?=\\}|;|,)|(?<=\\})", - "patterns": [ - { - "include": "#method-declaration-name" - }, - { - "include": "#function-body" - }, - { - "begin": "(?x)(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?[\\(])", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js.jsx" - }, - "2": { - "name": "storage.type.property.js.jsx" - }, - "3": { - "name": "keyword.generator.asterisk.js.jsx" - } - }, - "end": "(?=\\(|\\<)", - "patterns": [ - { - "include": "#method-declaration-name" - } - ] - } - ] - }, - "method-declaration-name": { - "begin": "(?x)(?=((\\b(?)", - "captures": { - "1": { - "name": "storage.modifier.async.js.jsx" - }, - "2": { - "name": "variable.parameter.js.jsx" - } - } - }, - { - "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)", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js.jsx" - } - }, - "end": "(?==>|\\{|(^\\s*(export|function|class|interface|let|var|const|import|enum|namespace|module|type|abstract|declare)\\s+))", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-parameters" - }, - { - "include": "#function-parameters" - }, - { - "include": "#arrow-return-type" - }, - { - "include": "#possibly-arrow-return-type" - } - ] - }, - { - "name": "meta.arrow.js.jsx", - "begin": "=>", - "beginCaptures": { - "0": { - "name": "storage.type.function.arrow.js.jsx" - } - }, - "end": "((?<=\\}|\\S)(?)|((?!\\{)(?=\\S)))(?!\\/[\\/\\*])", - "patterns": [ - { - "include": "#single-line-comment-consuming-line-ending" - }, - { - "include": "#decl-block" - }, - { - "include": "#expression" - } - ] - } - ] - }, - "indexer-declaration": { - "name": "meta.indexer.declaration.js.jsx", - "begin": "(?:(?]|^await|[^\\._$[:alnum:]]await|^return|[^\\._$[:alnum:]]return|^yield|[^\\._$[:alnum:]]yield|^throw|[^\\._$[:alnum:]]throw|^in|[^\\._$[:alnum:]]in|^of|[^\\._$[:alnum:]]of|^typeof|[^\\._$[:alnum:]]typeof|&&|\\|\\||\\*)\\s*(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.block.js.jsx" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.block.js.jsx" - } - }, - "patterns": [ - { - "include": "#object-member" - } - ] - }, - "object-literal": { - "name": "meta.objectliteral.js.jsx", - "begin": "\\{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.block.js.jsx" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.block.js.jsx" - } - }, - "patterns": [ - { - "include": "#object-member" - } - ] - }, - "object-member": { - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#object-literal-method-declaration" - }, - { - "name": "meta.object.member.js.jsx meta.object-literal.key.js.jsx", - "begin": "(?=\\[)", - "end": "(?=:)|((?<=[\\]])(?=\\s*[\\(\\<]))", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#array-literal" - } - ] - }, - { - "name": "meta.object.member.js.jsx meta.object-literal.key.js.jsx", - "begin": "(?=[\\'\\\"\\`])", - "end": "(?=:)|((?<=[\\'\\\"\\`])(?=((\\s*[\\(\\<,}])|(\\s+(as)\\s+))))", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#string" - } - ] - }, - { - "name": "meta.object.member.js.jsx meta.object-literal.key.js.jsx", - "begin": "(?x)(?=(\\b(?)\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" - }, - "1": { - "name": "entity.name.function.js.jsx" - } - } - }, - { - "name": "meta.object.member.js.jsx", - "match": "(?:[_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:)", - "captures": { - "0": { - "name": "meta.object-literal.key.js.jsx" - } - } - }, - { - "name": "meta.object.member.js.jsx", - "begin": "\\.\\.\\.", - "beginCaptures": { - "0": { - "name": "keyword.operator.spread.js.jsx" - } - }, - "end": "(?=,|\\})", - "patterns": [ - { - "include": "#expression" - } - ] - }, - { - "name": "meta.object.member.js.jsx", - "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?=,|\\}|$|\\/\\/|\\/\\*)", - "captures": { - "1": { - "name": "variable.other.readwrite.js.jsx" - } - } - }, - { - "name": "meta.object.member.js.jsx", - "match": "(?]|\\|\\||\\&\\&|\\!\\=\\=|$|^|((?]|=[^<]|\\<\\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" - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#type-parameters" - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "meta.brace.round.js.jsx" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "meta.brace.round.js.jsx" - } - }, - "patterns": [ - { - "include": "#expression-inside-possibly-arrow-parens" - } - ] - } - ] - }, - { - "begin": "(?<=:)\\s*(async)?\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js.jsx" - }, - "2": { - "name": "meta.brace.round.js.jsx" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "meta.brace.round.js.jsx" - } - }, - "patterns": [ - { - "include": "#expression-inside-possibly-arrow-parens" - } - ] - }, - { - "begin": "(?<=:)\\s*(async)?\\s*(?=\\<\\s*$)", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js.jsx" - } - }, - "end": "(?<=\\>)", - "patterns": [ - { - "include": "#type-parameters" - } - ] - }, - { - "begin": "(?<=\\>)\\s*(\\()(?=\\s*((([\\{\\[]\\s*)?$)|((\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})\\s*((:\\s*\\{?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))))", - "beginCaptures": { - "1": { - "name": "meta.brace.round.js.jsx" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "meta.brace.round.js.jsx" - } - }, - "patterns": [ - { - "include": "#expression-inside-possibly-arrow-parens" - } - ] - }, - { - "include": "#possibly-arrow-return-type" - }, - { - "include": "#expression" - } - ] - }, - { - "include": "#punctuation-comma" - } - ] - }, - "ternary-expression": { - "begin": "(?!\\?\\.\\s*[^[:digit:]])(\\?)(?!\\?)", - "beginCaptures": { - "1": { - "name": "keyword.operator.ternary.js.jsx" - } - }, - "end": "\\s*(:)", - "endCaptures": { - "1": { - "name": "keyword.operator.ternary.js.jsx" - } - }, - "patterns": [ - { - "include": "#expression" - } - ] - }, - "function-call": { - "patterns": [ - { - "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", - "end": "(?<=\\))(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", - "patterns": [ - { - "name": "meta.function-call.js.jsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(?:(\\?\\.\\s*)|(\\!))?((<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?\\())", - "patterns": [ - { - "include": "#function-call-target" - } - ] - }, - { - "include": "#comment" - }, - { - "include": "#function-call-optionals" - }, - { - "include": "#type-arguments" - }, - { - "include": "#paren-expression" - } - ] - }, - { - "begin": "(?=(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", - "end": "(?<=\\>)(?!(((([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))|(?<=[\\)]))(<\\s*[\\{\\[\\(]\\s*$))", - "patterns": [ - { - "name": "meta.function-call.js.jsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*)(\\s*\\??\\.\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*))*)|(\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=(<\\s*[\\{\\[\\(]\\s*$))", - "patterns": [ - { - "include": "#function-call-target" - } - ] - }, - { - "include": "#comment" - }, - { - "include": "#function-call-optionals" - }, - { - "include": "#type-arguments" - } - ] - } - ] - }, - "function-call-target": { - "patterns": [ - { - "include": "#support-function-call-identifiers" - }, - { - "name": "entity.name.function.js.jsx", - "match": "(\\#?[_$[:alpha:]][_$[:alnum:]]*)" - } - ] - }, - "function-call-optionals": { - "patterns": [ - { - "name": "meta.function-call.js.jsx punctuation.accessor.optional.js.jsx", - "match": "\\?\\." - }, - { - "name": "meta.function-call.js.jsx keyword.operator.definiteassignment.js.jsx", - "match": "\\!" - } - ] - }, - "support-function-call-identifiers": { - "patterns": [ - { - "include": "#literal" - }, - { - "include": "#support-objects" - }, - { - "include": "#object-identifiers" - }, - { - "include": "#punctuation-accessor" - }, - { - "name": "keyword.operator.expression.import.js.jsx", - "match": "(?:(?]|\\|\\||\\&\\&|\\!\\=\\=|$|((?]|\\|\\||\\&\\&|\\!\\=\\=|$|(([\\&\\~\\^\\|]\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s+instanceof(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.)))|((?]|=[^<]|\\<\\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" - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#paren-expression-possibly-arrow-with-typeparameters" - } - ] - }, - { - "begin": "(?<=[(=,]|=>|^return|[^\\._$[:alnum:]]return)\\s*(async)?(?=\\s*((((<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*))?\\()|(<))\\s*$)", - "beginCaptures": { - "1": { - "name": "storage.modifier.async.js.jsx" - } - }, - "end": "(?<=\\))", - "patterns": [ - { - "include": "#paren-expression-possibly-arrow-with-typeparameters" - } - ] - }, - { - "include": "#possibly-arrow-return-type" - } - ] - }, - "paren-expression-possibly-arrow-with-typeparameters": { - "patterns": [ - { - "include": "#type-parameters" - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "meta.brace.round.js.jsx" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "meta.brace.round.js.jsx" - } - }, - "patterns": [ - { - "include": "#expression-inside-possibly-arrow-parens" - } - ] - } - ] - }, - "expression-inside-possibly-arrow-parens": { - "patterns": [ - { - "include": "#expressionWithoutIdentifiers" - }, - { - "include": "#comment" - }, - { - "include": "#string" - }, - { - "include": "#decorator" - }, - { - "include": "#destructuring-parameter" - }, - { - "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)))", - "captures": { - "1": { - "name": "storage.modifier.js.jsx" - }, - "2": { - "name": "keyword.operator.rest.js.jsx" - }, - "3": { - "name": "entity.name.function.js.jsx variable.language.this.js.jsx" - }, - "4": { - "name": "entity.name.function.js.jsx" - }, - "5": { - "name": "keyword.operator.optional.js.jsx" - } - } - }, - { - "match": "(?x)(?:(?]|\\|\\||\\&\\&|\\!\\=\\=|$|((?>=|>>>=|\\|=" - }, - { - "name": "keyword.operator.bitwise.shift.js.jsx", - "match": "<<|>>>|>>" - }, - { - "name": "keyword.operator.comparison.js.jsx", - "match": "===|!==|==|!=" - }, - { - "name": "keyword.operator.relational.js.jsx", - "match": "<=|>=|<>|<|>" - }, - { - "match": "(?<=[_$[:alnum:]])(\\!)\\s*(?:(/=)|(?:(/)(?![/*])))", - "captures": { - "1": { - "name": "keyword.operator.logical.js.jsx" - }, - "2": { - "name": "keyword.operator.assignment.compound.js.jsx" - }, - "3": { - "name": "keyword.operator.arithmetic.js.jsx" - } - } - }, - { - "name": "keyword.operator.logical.js.jsx", - "match": "\\!|&&|\\|\\||\\?\\?" - }, - { - "name": "keyword.operator.bitwise.js.jsx", - "match": "\\&|~|\\^|\\|" - }, - { - "name": "keyword.operator.assignment.js.jsx", - "match": "\\=" - }, - { - "name": "keyword.operator.decrement.js.jsx", - "match": "--" - }, - { - "name": "keyword.operator.increment.js.jsx", - "match": "\\+\\+" - }, - { - "name": "keyword.operator.arithmetic.js.jsx", - "match": "%|\\*|/|-|\\+" - }, - { - "begin": "(?<=[_$[:alnum:])\\]])\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)+(?:(/=)|(?:(/)(?![/*]))))", - "end": "(?:(/=)|(?:(/)(?!\\*([^\\*]|(\\*[^\\/]))*\\*\\/)))", - "endCaptures": { - "1": { - "name": "keyword.operator.assignment.compound.js.jsx" - }, - "2": { - "name": "keyword.operator.arithmetic.js.jsx" - } - }, - "patterns": [ - { - "include": "#comment" - } - ] - }, - { - "match": "(?<=[_$[:alnum:])\\]])\\s*(?:(/=)|(?:(/)(?![/*])))", - "captures": { - "1": { - "name": "keyword.operator.assignment.compound.js.jsx" - }, - "2": { - "name": "keyword.operator.arithmetic.js.jsx" - } - } - } - ] - }, - "typeof-operator": { - "begin": "(?:&|{\\?]|$|;|^\\s*$|(?:^\\s*(?:abstract|async|class|const|declare|enum|export|function|import|interface|let|module|namespace|return|type|var)\\b))", - "patterns": [ - { - "include": "#expression" - } - ] - }, - "literal": { - "patterns": [ - { - "include": "#numeric-literal" - }, - { - "include": "#boolean-literal" - }, - { - "include": "#null-literal" - }, - { - "include": "#undefined-literal" - }, - { - "include": "#numericConstant-literal" - }, - { - "include": "#array-literal" - }, - { - "include": "#this-literal" - }, - { - "include": "#super-literal" - } - ] - }, - "array-literal": { - "name": "meta.array.literal.js.jsx", - "begin": "\\s*(\\[)", - "beginCaptures": { - "1": { - "name": "meta.brace.square.js.jsx" - } - }, - "end": "\\]", - "endCaptures": { - "0": { - "name": "meta.brace.square.js.jsx" - } - }, - "patterns": [ - { - "include": "#expression" - }, - { - "include": "#punctuation-comma" - } - ] - }, - "numeric-literal": { - "patterns": [ - { - "name": "constant.numeric.hex.js.jsx", - "match": "\\b(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\]))([^=<>]|=[^<])*\\>)*\\>)*>\\s*)?\\())\n |\n (?:(EPSILON|MAX_SAFE_INTEGER|MAX_VALUE|MIN_SAFE_INTEGER|MIN_VALUE|NEGATIVE_INFINITY|POSITIVE_INFINITY)\\b(?!\\$)))", - "captures": { - "1": { - "name": "punctuation.accessor.js.jsx" - }, - "2": { - "name": "punctuation.accessor.optional.js.jsx" - }, - "3": { - "name": "support.variable.property.js.jsx" - }, - "4": { - "name": "support.constant.js.jsx" - } - } - }, - { - "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))", - "captures": { - "1": { - "name": "punctuation.accessor.js.jsx" - }, - "2": { - "name": "punctuation.accessor.optional.js.jsx" - }, - "3": { - "name": "entity.name.function.js.jsx" - } - } - }, - { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", - "captures": { - "1": { - "name": "punctuation.accessor.js.jsx" - }, - "2": { - "name": "punctuation.accessor.optional.js.jsx" - }, - "3": { - "name": "variable.other.constant.property.js.jsx" - } - } - }, - { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(\\#?[_$[:alpha:]][_$[:alnum:]]*)", - "captures": { - "1": { - "name": "punctuation.accessor.js.jsx" - }, - "2": { - "name": "punctuation.accessor.optional.js.jsx" - }, - "3": { - "name": "variable.other.property.js.jsx" - } - } - }, - { - "name": "variable.other.constant.js.jsx", - "match": "([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])" - }, - { - "name": "variable.other.readwrite.js.jsx", - "match": "[_$[:alpha:]][_$[:alnum:]]*" - } - ] - }, - "object-identifiers": { - "patterns": [ - { - "name": "support.class.js.jsx", - "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" - }, - { - "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n (\\#?[[:upper:]][_$[:digit:][:upper:]]*) |\n (\\#?[_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", - "captures": { - "1": { - "name": "punctuation.accessor.js.jsx" - }, - "2": { - "name": "punctuation.accessor.optional.js.jsx" - }, - "3": { - "name": "variable.other.constant.object.property.js.jsx" - }, - "4": { - "name": "variable.other.object.property.js.jsx" - } - } - }, - { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*\\#?[_$[:alpha:]][_$[:alnum:]]*)", - "captures": { - "1": { - "name": "variable.other.constant.object.js.jsx" - }, - "2": { - "name": "variable.other.object.js.jsx" - } - } - } - ] - }, - "type-annotation": { - "patterns": [ - { - "name": "meta.type.annotation.js.jsx", - "begin": "(:)(?=\\s*\\S)", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.annotation.js.jsx" - } - }, - "end": "(?])|((?<=[\\}>\\]\\)]|[_$[:alpha:]])\\s*(?=\\{)))", - "patterns": [ - { - "include": "#type" - } - ] - }, - { - "name": "meta.type.annotation.js.jsx", - "begin": "(:)", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.annotation.js.jsx" - } - }, - "end": "(?])|(?=^\\s*$)|((?<=\\S)(?=\\s*$))|((?<=[\\}>\\]\\)]|[_$[:alpha:]])\\s*(?=\\{)))", - "patterns": [ - { - "include": "#type" - } - ] - } - ] - }, - "parameter-type-annotation": { - "patterns": [ - { - "name": "meta.type.annotation.js.jsx", - "begin": "(:)", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.annotation.js.jsx" - } - }, - "end": "(?=[,)])|(?==[^>])", - "patterns": [ - { - "include": "#type" - } - ] - } - ] - }, - "return-type": { - "patterns": [ - { - "name": "meta.return.type.js.jsx", - "begin": "(?<=\\))\\s*(:)(?=\\s*\\S)", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.annotation.js.jsx" - } - }, - "end": "(?|\\{|(^\\s*(export|function|class|interface|let|var|const|import|enum|namespace|module|type|abstract|declare)\\s+))", - "patterns": [ - { - "include": "#arrow-return-type-body" - } - ] - }, - "possibly-arrow-return-type": { - "begin": "(?<=\\)|^)\\s*(:)(?=\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*=>)", - "beginCaptures": { - "1": { - "name": "meta.arrow.js.jsx meta.return.type.arrow.js.jsx keyword.operator.type.annotation.js.jsx" - } - }, - "end": "(?==>|\\{|(^\\s*(export|function|class|interface|let|var|const|import|enum|namespace|module|type|abstract|declare)\\s+))", - "contentName": "meta.arrow.js.jsx meta.return.type.arrow.js.jsx", - "patterns": [ - { - "include": "#arrow-return-type-body" - } - ] - }, - "arrow-return-type-body": { - "patterns": [ - { - "begin": "(?<=[:])(?=\\s*\\{)", - "end": "(?<=\\})", - "patterns": [ - { - "include": "#type-object" - } - ] - }, - { - "include": "#type-predicate-operator" - }, - { - "include": "#type" - } - ] - }, - "type-parameters": { - "name": "meta.type.parameters.js.jsx", - "begin": "(<)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.typeparameters.begin.js.jsx" - } - }, - "end": "(>)", - "endCaptures": { - "1": { - "name": "punctuation.definition.typeparameters.end.js.jsx" - } - }, - "patterns": [ - { - "include": "#comment" - }, - { - "name": "storage.modifier.js.jsx", - "match": "(?)" - } - ] - }, - "type-arguments": { - "name": "meta.type.parameters.js.jsx", - "begin": "\\<", - "beginCaptures": { - "0": { - "name": "punctuation.definition.typeparameters.begin.js.jsx" - } - }, - "end": "\\>", - "endCaptures": { - "0": { - "name": "punctuation.definition.typeparameters.end.js.jsx" - } - }, - "patterns": [ - { - "include": "#type-arguments-body" - } - ] - }, - "type-arguments-body": { - "patterns": [ - { - "match": "(?)\n ))\n ))\n)) |\n(:\\s*(?\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*)))|((\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])\\s*((:\\s*\\[?$)|((\\s*([^<>\\(\\)\\{\\}]|\\<([^<>]|\\<([^<>]|\\<[^<>]+\\>)+\\>)+\\>|\\([^\\(\\)]+\\)|\\{[^\\{\\}]+\\})+\\s*)?=\\s*))))))))", - "captures": { - "1": { - "name": "storage.modifier.js.jsx" - }, - "2": { - "name": "keyword.operator.rest.js.jsx" - }, - "3": { - "name": "entity.name.function.js.jsx variable.language.this.js.jsx" - }, - "4": { - "name": "entity.name.function.js.jsx" - }, - "5": { - "name": "keyword.operator.optional.js.jsx" - } - } - }, - { - "match": "(?x)(?:(?)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-parameters" - } - ] - }, - { - "name": "meta.type.constructor.js.jsx", - "begin": "(?)\n ))\n )\n )\n)", - "end": "(?<=\\))", - "patterns": [ - { - "include": "#function-parameters" - } - ] - } - ] - }, - "type-function-return-type": { - "patterns": [ - { - "name": "meta.type.function.return.js.jsx", - "begin": "(=>)(?=\\s*\\S)", - "beginCaptures": { - "1": { - "name": "storage.type.function.arrow.js.jsx" - } - }, - "end": "(?)(?:\\?]|//|$)", - "patterns": [ - { - "include": "#type-function-return-type-core" - } - ] - }, - { - "name": "meta.type.function.return.js.jsx", - "begin": "=>", - "beginCaptures": { - "0": { - "name": "storage.type.function.arrow.js.jsx" - } - }, - "end": "(?)(?]|//|^\\s*$)|((?<=\\S)(?=\\s*$)))", - "patterns": [ - { - "include": "#type-function-return-type-core" - } - ] - } - ] - }, - "type-function-return-type-core": { - "patterns": [ - { - "include": "#comment" - }, - { - "begin": "(?<==>)(?=\\s*\\{)", - "end": "(?<=\\})", - "patterns": [ - { - "include": "#type-object" - } - ] - }, - { - "include": "#type-predicate-operator" - }, - { - "include": "#type" - } - ] - }, - "type-operators": { - "patterns": [ - { - "include": "#typeof-operator" - }, - { - "include": "#type-infer" - }, - { - "begin": "([&|])(?=\\s*\\{)", - "beginCaptures": { - "0": { - "name": "keyword.operator.type.js.jsx" - } - }, - "end": "(?<=\\})", - "patterns": [ - { - "include": "#type-object" - } - ] - }, - { - "begin": "[&|]", - "beginCaptures": { - "0": { - "name": "keyword.operator.type.js.jsx" - } - }, - "end": "(?=\\S)" - }, - { - "name": "keyword.operator.expression.keyof.js.jsx", - "match": "(?)", - "endCaptures": { - "1": { - "name": "meta.type.parameters.js.jsx punctuation.definition.typeparameters.end.js.jsx" - } - }, - "contentName": "meta.type.parameters.js.jsx", - "patterns": [ - { - "include": "#type-arguments-body" - } - ] - }, - { - "begin": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(<)", - "beginCaptures": { - "1": { - "name": "entity.name.type.js.jsx" - }, - "2": { - "name": "meta.type.parameters.js.jsx punctuation.definition.typeparameters.begin.js.jsx" - } - }, - "end": "(>)", - "endCaptures": { - "1": { - "name": "meta.type.parameters.js.jsx punctuation.definition.typeparameters.end.js.jsx" - } - }, - "contentName": "meta.type.parameters.js.jsx", - "patterns": [ - { - "include": "#type-arguments-body" - } - ] - }, - { - "match": "([_$[:alpha:]][_$[:alnum:]]*)\\s*(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))", - "captures": { - "1": { - "name": "entity.name.type.module.js.jsx" - }, - "2": { - "name": "punctuation.accessor.js.jsx" - }, - "3": { - "name": "punctuation.accessor.optional.js.jsx" - } - } - }, - { - "name": "entity.name.type.js.jsx", - "match": "[_$[:alpha:]][_$[:alnum:]]*" - } - ] - }, - "punctuation-comma": { - "name": "punctuation.separator.comma.js.jsx", - "match": "," - }, - "punctuation-semicolon": { - "name": "punctuation.terminator.statement.js.jsx", - "match": ";" - }, - "punctuation-accessor": { - "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))", - "captures": { - "1": { - "name": "punctuation.accessor.js.jsx" - }, - "2": { - "name": "punctuation.accessor.optional.js.jsx" - } - } - }, - "string": { - "patterns": [ - { - "include": "#qstring-single" - }, - { - "include": "#qstring-double" - }, - { - "include": "#template" - } - ] - }, - "qstring-double": { - "name": "string.quoted.double.js.jsx", - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.js.jsx" - } - }, - "end": "(\")|((?:[^\\\\\\n])$)", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.js.jsx" - }, - "2": { - "name": "invalid.illegal.newline.js.jsx" - } - }, - "patterns": [ - { - "include": "#string-character-escape" - } - ] - }, - "qstring-single": { - "name": "string.quoted.single.js.jsx", - "begin": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.js.jsx" - } - }, - "end": "(\\')|((?:[^\\\\\\n])$)", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.js.jsx" - }, - "2": { - "name": "invalid.illegal.newline.js.jsx" - } - }, - "patterns": [ - { - "include": "#string-character-escape" - } - ] - }, - "string-character-escape": { - "name": "constant.character.escape.js.jsx", - "match": "\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|u\\{[0-9A-Fa-f]+\\}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)" - }, - "template": { - "patterns": [ - { - "include": "#template-call" - }, - { - "name": "string.template.js.jsx", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)?(`)", - "beginCaptures": { - "1": { - "name": "entity.name.function.tagged-template.js.jsx" - }, - "2": { - "name": "punctuation.definition.string.template.begin.js.jsx" - } - }, - "end": "`", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.template.end.js.jsx" - } - }, - "patterns": [ - { - "include": "#template-substitution-element" - }, - { - "include": "#string-character-escape" - } - ] - } - ] - }, - "template-call": { - "patterns": [ - { - "name": "string.template.js.jsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", - "end": "(?=`)", - "patterns": [ - { - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)?`)", - "patterns": [ - { - "include": "#support-function-call-identifiers" - }, - { - "name": "entity.name.function.tagged-template.js.jsx", - "match": "([_$[:alpha:]][_$[:alnum:]]*)" - } - ] - }, - { - "include": "#type-arguments" - } - ] - }, - { - "name": "string.template.js.jsx", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)?\\s*(?=(<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))(([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>|\\<\\s*(((keyof|infer|typeof|readonly)\\s+)|(([_$[:alpha:]][_$[:alnum:]]*|(\\{([^\\{\\}]|(\\{([^\\{\\}]|\\{[^\\{\\}]*\\})*\\}))*\\})|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(\\[([^\\[\\]]|(\\[([^\\[\\]]|\\[[^\\[\\]]*\\])*\\]))*\\])|(\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`))(?=\\s*([\\<\\>\\,\\.\\[]|=>|&(?!&)|\\|(?!\\|)))))([^<>\\(]|(\\(([^\\(\\)]|(\\(([^\\(\\)]|\\([^\\(\\)]*\\))*\\)))*\\))|(?<==)\\>)*(?))*(?)*(?\\s*)`)", - "beginCaptures": { - "1": { - "name": "entity.name.function.tagged-template.js.jsx" - } - }, - "end": "(?=`)", - "patterns": [ - { - "include": "#type-arguments" - } - ] - } - ] - }, - "template-substitution-element": { - "name": "meta.template.expression.js.jsx", - "begin": "\\$\\{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.template-expression.begin.js.jsx" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.template-expression.end.js.jsx" - } - }, - "patterns": [ - { - "include": "#expression" - } - ], - "contentName": "meta.embedded.line.js.jsx" - }, - "type-string": { - "patterns": [ - { - "include": "#qstring-single" - }, - { - "include": "#qstring-double" - }, - { - "include": "#template-type" - } - ] - }, - "template-type": { - "patterns": [ - { - "include": "#template-call" - }, - { - "name": "string.template.js.jsx", - "begin": "([_$[:alpha:]][_$[:alnum:]]*)?(`)", - "beginCaptures": { - "1": { - "name": "entity.name.function.tagged-template.js.jsx" - }, - "2": { - "name": "punctuation.definition.string.template.begin.js.jsx" - } - }, - "end": "`", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.template.end.js.jsx" - } - }, - "patterns": [ - { - "include": "#template-type-substitution-element" - }, - { - "include": "#string-character-escape" - } - ] - } - ] - }, - "template-type-substitution-element": { - "name": "meta.template.expression.js.jsx", - "begin": "\\$\\{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.template-expression.begin.js.jsx" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.template-expression.end.js.jsx" - } - }, - "patterns": [ - { - "include": "#type" - } - ], - "contentName": "meta.embedded.line.js.jsx" - }, - "regex": { - "patterns": [ - { - "name": "string.regexp.js.jsx", - "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([dgimsuy]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", - "beginCaptures": { - "1": { - "name": "punctuation.definition.string.begin.js.jsx" - } - }, - "end": "(/)([dgimsuy]*)", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.js.jsx" - }, - "2": { - "name": "keyword.other.js.jsx" - } - }, - "patterns": [ - { - "include": "#regexp" - } - ] - }, - { - "name": "string.regexp.js.jsx", - "begin": "((?", - "captures": { - "0": { - "name": "keyword.other.back-reference.regexp" - }, - "1": { - "name": "variable.other.regexp" - } - } - }, - { - "name": "keyword.operator.quantifier.regexp", - "match": "[?+*]|\\{(\\d+,\\d+|\\d+,|,\\d+|\\d+)\\}\\??" - }, - { - "name": "keyword.operator.or.regexp", - "match": "\\|" - }, - { - "name": "meta.group.assertion.regexp", - "begin": "(\\()((\\?=)|(\\?!)|(\\?<=)|(\\?))?", - "beginCaptures": { - "0": { - "name": "punctuation.definition.group.regexp" - }, - "1": { - "name": "punctuation.definition.group.no-capture.regexp" - }, - "2": { - "name": "variable.other.regexp" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.group.regexp" - } - }, - "patterns": [ - { - "include": "#regexp" - } - ] - }, - { - "name": "constant.other.character-class.set.regexp", - "begin": "(\\[)(\\^)?", - "beginCaptures": { - "1": { - "name": "punctuation.definition.character-class.regexp" - }, - "2": { - "name": "keyword.operator.negation.regexp" - } - }, - "end": "(\\])", - "endCaptures": { - "1": { - "name": "punctuation.definition.character-class.regexp" - } - }, - "patterns": [ - { - "name": "constant.other.character-class.range.regexp", - "match": "(?:.|(\\\\(?:[0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}))|(\\\\c[A-Z])|(\\\\.))\\-(?:[^\\]\\\\]|(\\\\(?:[0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}))|(\\\\c[A-Z])|(\\\\.))", - "captures": { - "1": { - "name": "constant.character.numeric.regexp" - }, - "2": { - "name": "constant.character.control.regexp" - }, - "3": { - "name": "constant.character.escape.backslash.regexp" - }, - "4": { - "name": "constant.character.numeric.regexp" - }, - "5": { - "name": "constant.character.control.regexp" - }, - "6": { - "name": "constant.character.escape.backslash.regexp" - } - } - }, - { - "include": "#regex-character-class" - } - ] - }, - { - "include": "#regex-character-class" - } - ] - }, - "regex-character-class": { - "patterns": [ - { - "name": "constant.other.character-class.regexp", - "match": "\\\\[wWsSdDtrnvf]|\\." - }, - { - "name": "constant.character.numeric.regexp", - "match": "\\\\([0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4})" - }, - { - "name": "constant.character.control.regexp", - "match": "\\\\c[A-Z]" - }, - { - "name": "constant.character.escape.backslash.regexp", - "match": "\\\\." - } - ] - }, - "comment": { - "patterns": [ - { - "name": "comment.block.documentation.js.jsx", - "begin": "/\\*\\*(?!/)", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.js.jsx" - } - }, - "end": "\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.js.jsx" - } - }, - "patterns": [ - { - "include": "#docblock" - } - ] - }, - { - "name": "comment.block.js.jsx", - "begin": "(/\\*)(?:\\s*((@)internal)(?=\\s|(\\*/)))?", - "beginCaptures": { - "1": { - "name": "punctuation.definition.comment.js.jsx" - }, - "2": { - "name": "storage.type.internaldeclaration.js.jsx" - }, - "3": { - "name": "punctuation.decorator.internaldeclaration.js.jsx" - } - }, - "end": "\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.js.jsx" - } - } - }, - { - "begin": "(^[ \\t]+)?((//)(?:\\s*((@)internal)(?=\\s|$))?)", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.js.jsx" - }, - "2": { - "name": "comment.line.double-slash.js.jsx" - }, - "3": { - "name": "punctuation.definition.comment.js.jsx" - }, - "4": { - "name": "storage.type.internaldeclaration.js.jsx" - }, - "5": { - "name": "punctuation.decorator.internaldeclaration.js.jsx" - } - }, - "end": "(?=$)", - "contentName": "comment.line.double-slash.js.jsx" - } - ] - }, - "single-line-comment-consuming-line-ending": { - "begin": "(^[ \\t]+)?((//)(?:\\s*((@)internal)(?=\\s|$))?)", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.js.jsx" - }, - "2": { - "name": "comment.line.double-slash.js.jsx" - }, - "3": { - "name": "punctuation.definition.comment.js.jsx" - }, - "4": { - "name": "storage.type.internaldeclaration.js.jsx" - }, - "5": { - "name": "punctuation.decorator.internaldeclaration.js.jsx" - } - }, - "end": "(?=^)", - "contentName": "comment.line.double-slash.js.jsx" - }, - "directives": { - "name": "comment.line.triple-slash.directive.js.jsx", - "begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name|resolution-mode)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.comment.js.jsx" - } - }, - "end": "(?=$)", - "patterns": [ - { - "name": "meta.tag.js.jsx", - "begin": "(<)(reference|amd-dependency|amd-module)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.directive.js.jsx" - }, - "2": { - "name": "entity.name.tag.directive.js.jsx" - } - }, - "end": "/>", - "endCaptures": { - "0": { - "name": "punctuation.definition.tag.directive.js.jsx" - } - }, - "patterns": [ - { - "name": "entity.other.attribute-name.directive.js.jsx", - "match": "path|types|no-default-lib|lib|name|resolution-mode" - }, - { - "name": "keyword.operator.assignment.js.jsx", - "match": "=" - }, - { - "include": "#string" - } - ] - } - ] - }, - "docblock": { - "patterns": [ - { - "match": "(?x)\n((@)(?:access|api))\n\\s+\n(private|protected|public)\n\\b", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "constant.language.access-type.jsdoc" - } - } - }, - { - "match": "(?x)\n((@)author)\n\\s+\n(\n [^@\\s<>*/]\n (?:[^@<>*/]|\\*[^/])*\n)\n(?:\n \\s*\n (<)\n ([^>\\s]+)\n (>)\n)?", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "entity.name.type.instance.jsdoc" - }, - "4": { - "name": "punctuation.definition.bracket.angle.begin.jsdoc" - }, - "5": { - "name": "constant.other.email.link.underline.jsdoc" - }, - "6": { - "name": "punctuation.definition.bracket.angle.end.jsdoc" - } - } - }, - { - "match": "(?x)\n((@)borrows) \\s+\n((?:[^@\\s*/]|\\*[^/])+) # \n\\s+ (as) \\s+ # as\n((?:[^@\\s*/]|\\*[^/])+) # ", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "entity.name.type.instance.jsdoc" - }, - "4": { - "name": "keyword.operator.control.jsdoc" - }, - "5": { - "name": "entity.name.type.instance.jsdoc" - } - } - }, - { - "name": "meta.example.jsdoc", - "begin": "((@)example)\\s+", - "end": "(?=@|\\*/)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "patterns": [ - { - "match": "^\\s\\*\\s+" - }, - { - "contentName": "constant.other.description.jsdoc", - "begin": "\\G(<)caption(>)", - "beginCaptures": { - "0": { - "name": "entity.name.tag.inline.jsdoc" - }, - "1": { - "name": "punctuation.definition.bracket.angle.begin.jsdoc" - }, - "2": { - "name": "punctuation.definition.bracket.angle.end.jsdoc" - } - }, - "end": "()|(?=\\*/)", - "endCaptures": { - "0": { - "name": "entity.name.tag.inline.jsdoc" - }, - "1": { - "name": "punctuation.definition.bracket.angle.begin.jsdoc" - }, - "2": { - "name": "punctuation.definition.bracket.angle.end.jsdoc" - } - } - }, - { - "match": "[^\\s@*](?:[^*]|\\*[^/])*", - "captures": { - "0": { - "name": "source.embedded.js.jsx" - } - } - } - ] - }, - { - "match": "(?x) ((@)kind) \\s+ (class|constant|event|external|file|function|member|mixin|module|namespace|typedef) \\b", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "constant.language.symbol-type.jsdoc" - } - } - }, - { - "match": "(?x)\n((@)see)\n\\s+\n(?:\n # URL\n (\n (?=https?://)\n (?:[^\\s*]|\\*[^/])+\n )\n |\n # JSDoc namepath\n (\n (?!\n # Avoid matching bare URIs (also acceptable as links)\n https?://\n |\n # Avoid matching {@inline tags}; we match those below\n (?:\\[[^\\[\\]]*\\])? # Possible description [preceding]{@tag}\n {@(?:link|linkcode|linkplain|tutorial)\\b\n )\n # Matched namepath\n (?:[^@\\s*/]|\\*[^/])+\n )\n)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.link.underline.jsdoc" - }, - "4": { - "name": "entity.name.type.instance.jsdoc" - } - } - }, - { - "match": "(?x)\n((@)template)\n\\s+\n# One or more valid identifiers\n(\n [A-Za-z_$] # First character: non-numeric word character\n [\\w$.\\[\\]]* # Rest of identifier\n (?: # Possible list of additional identifiers\n \\s* , \\s*\n [A-Za-z_$]\n [\\w$.\\[\\]]*\n )*\n)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.jsdoc" - } - } - }, - { - "begin": "(?x)((@)template)\\s+(?={)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", - "patterns": [ - { - "include": "#jsdoctype" - }, - { - "name": "variable.other.jsdoc", - "match": "([A-Za-z_$][\\w$.\\[\\]]*)" - } - ] - }, - { - "match": "(?x)\n(\n (@)\n (?:arg|argument|const|constant|member|namespace|param|var)\n)\n\\s+\n(\n [A-Za-z_$]\n [\\w$.\\[\\]]*\n)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.jsdoc" - } - } - }, - { - "begin": "((@)typedef)\\s+(?={)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", - "patterns": [ - { - "include": "#jsdoctype" - }, - { - "name": "entity.name.type.instance.jsdoc", - "match": "(?:[^@\\s*/]|\\*[^/])+" - } - ] - }, - { - "begin": "((@)(?:arg|argument|const|constant|member|namespace|param|prop|property|var))\\s+(?={)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", - "patterns": [ - { - "include": "#jsdoctype" - }, - { - "name": "variable.other.jsdoc", - "match": "([A-Za-z_$][\\w$.\\[\\]]*)" - }, - { - "name": "variable.other.jsdoc", - "match": "(?x)\n(\\[)\\s*\n[\\w$]+\n(?:\n (?:\\[\\])? # Foo[ ].bar properties within an array\n \\. # Foo.Bar namespaced parameter\n [\\w$]+\n)*\n(?:\n \\s*\n (=) # [foo=bar] Default parameter value\n \\s*\n (\n # The inner regexes are to stop the match early at */ and to not stop at escaped quotes\n (?>\n \"(?:(?:\\*(?!/))|(?:\\\\(?!\"))|[^*\\\\])*?\" | # [foo=\"bar\"] Double-quoted\n '(?:(?:\\*(?!/))|(?:\\\\(?!'))|[^*\\\\])*?' | # [foo='bar'] Single-quoted\n \\[ (?:(?:\\*(?!/))|[^*])*? \\] | # [foo=[1,2]] Array literal\n (?:(?:\\*(?!/))|\\s(?!\\s*\\])|\\[.*?(?:\\]|(?=\\*/))|[^*\\s\\[\\]])* # Everything else\n )*\n )\n)?\n\\s*(?:(\\])((?:[^*\\s]|\\*[^\\s/])+)?|(?=\\*/))", - "captures": { - "1": { - "name": "punctuation.definition.optional-value.begin.bracket.square.jsdoc" - }, - "2": { - "name": "keyword.operator.assignment.jsdoc" - }, - "3": { - "name": "source.embedded.js.jsx" - }, - "4": { - "name": "punctuation.definition.optional-value.end.bracket.square.jsdoc" - }, - "5": { - "name": "invalid.illegal.syntax.jsdoc" - } - } - } - ] - }, - { - "begin": "(?x)\n(\n (@)\n (?:define|enum|exception|export|extends|lends|implements|modifies\n |namespace|private|protected|returns?|suppress|this|throws|type\n |yields?)\n)\n\\s+(?={)", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - }, - "end": "(?=\\s|\\*/|[^{}\\[\\]A-Za-z_$])", - "patterns": [ - { - "include": "#jsdoctype" - } - ] - }, - { - "match": "(?x)\n(\n (@)\n (?:alias|augments|callback|constructs|emits|event|fires|exports?\n |extends|external|function|func|host|lends|listens|interface|memberof!?\n |method|module|mixes|mixin|name|requires|see|this|typedef|uses)\n)\n\\s+\n(\n (?:\n [^{}@\\s*] | \\*[^/]\n )+\n)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "entity.name.type.instance.jsdoc" - } - } - }, - { - "contentName": "variable.other.jsdoc", - "begin": "((@)(?:default(?:value)?|license|version))\\s+(([''\"]))", - "beginCaptures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.jsdoc" - }, - "4": { - "name": "punctuation.definition.string.begin.jsdoc" - } - }, - "end": "(\\3)|(?=$|\\*/)", - "endCaptures": { - "0": { - "name": "variable.other.jsdoc" - }, - "1": { - "name": "punctuation.definition.string.end.jsdoc" - } - } - }, - { - "match": "((@)(?:default(?:value)?|license|tutorial|variation|version))\\s+([^\\s*]+)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - }, - "3": { - "name": "variable.other.jsdoc" - } - } - }, - { - "name": "storage.type.class.jsdoc", - "match": "(?x) (@) (?:abstract|access|alias|api|arg|argument|async|attribute|augments|author|beta|borrows|bubbles |callback|chainable|class|classdesc|code|config|const|constant|constructor|constructs|copyright |default|defaultvalue|define|deprecated|desc|description|dict|emits|enum|event|example|exception |exports?|extends|extension(?:_?for)?|external|externs|file|fileoverview|final|fires|for|func |function|generator|global|hideconstructor|host|ignore|implements|implicitCast|inherit[Dd]oc |inner|instance|interface|internal|kind|lends|license|listens|main|member|memberof!?|method |mixes|mixins?|modifies|module|name|namespace|noalias|nocollapse|nocompile|nosideeffects |override|overview|package|param|polymer(?:Behavior)?|preserve|private|prop|property|protected |public|read[Oo]nly|record|require[ds]|returns?|see|since|static|struct|submodule|summary |suppress|template|this|throws|todo|tutorial|type|typedef|unrestricted|uses|var|variation |version|virtual|writeOnce|yields?) \\b", - "captures": { - "1": { - "name": "punctuation.definition.block.tag.jsdoc" - } - } - }, - { - "include": "#inline-tags" - }, - { - "match": "((@)(?:[_$[:alpha:]][_$[:alnum:]]*))(?=\\s+)", - "captures": { - "1": { - "name": "storage.type.class.jsdoc" - }, - "2": { - "name": "punctuation.definition.block.tag.jsdoc" - } - } - } - ] - }, - "brackets": { - "patterns": [ - { - "begin": "{", - "end": "}|(?=\\*/)", - "patterns": [ - { - "include": "#brackets" - } - ] - }, - { - "begin": "\\[", - "end": "\\]|(?=\\*/)", - "patterns": [ - { - "include": "#brackets" - } - ] - } - ] - }, - "inline-tags": { - "patterns": [ - { - "name": "constant.other.description.jsdoc", - "match": "(\\[)[^\\]]+(\\])(?={@(?:link|linkcode|linkplain|tutorial))", - "captures": { - "1": { - "name": "punctuation.definition.bracket.square.begin.jsdoc" - }, - "2": { - "name": "punctuation.definition.bracket.square.end.jsdoc" - } - } - }, - { - "name": "entity.name.type.instance.jsdoc", - "begin": "({)((@)(?:link(?:code|plain)?|tutorial))\\s*", - "beginCaptures": { - "1": { - "name": "punctuation.definition.bracket.curly.begin.jsdoc" - }, - "2": { - "name": "storage.type.class.jsdoc" - }, - "3": { - "name": "punctuation.definition.inline.tag.jsdoc" - } - }, - "end": "}|(?=\\*/)", - "endCaptures": { - "0": { - "name": "punctuation.definition.bracket.curly.end.jsdoc" - } - }, - "patterns": [ - { - "match": "\\G((?=https?://)(?:[^|}\\s*]|\\*[/])+)(\\|)?", - "captures": { - "1": { - "name": "variable.other.link.underline.jsdoc" - }, - "2": { - "name": "punctuation.separator.pipe.jsdoc" - } - } - }, - { - "match": "\\G((?:[^{}@\\s|*]|\\*[^/])+)(\\|)?", - "captures": { - "1": { - "name": "variable.other.description.jsdoc" - }, - "2": { - "name": "punctuation.separator.pipe.jsdoc" - } - } - } - ] - } - ] - }, - "jsdoctype": { - "patterns": [ - { - "contentName": "entity.name.type.instance.jsdoc", - "begin": "\\G({)", - "beginCaptures": { - "0": { - "name": "entity.name.type.instance.jsdoc" - }, - "1": { - "name": "punctuation.definition.bracket.curly.begin.jsdoc" - } - }, - "end": "((}))\\s*|(?=\\*/)", - "endCaptures": { - "1": { - "name": "entity.name.type.instance.jsdoc" - }, - "2": { - "name": "punctuation.definition.bracket.curly.end.jsdoc" - } - }, - "patterns": [ - { - "include": "#brackets" - } - ] - } - ] - }, - "jsx": { - "patterns": [ - { - "include": "#jsx-tag-without-attributes-in-expression" - }, - { - "include": "#jsx-tag-in-expression" - } - ] - }, - "jsx-tag-without-attributes-in-expression": { - "begin": "(?:*]|&&|\\|\\||\\?|\\*\\/|^await|[^\\._$[:alnum:]]await|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^yield|[^\\._$[:alnum:]]yield|^)\\s*(?=(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "end": "(?!(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "patterns": [ - { - "include": "#jsx-tag-without-attributes" - } - ] - }, - "jsx-tag-without-attributes": { - "name": "meta.tag.without-attributes.js.jsx", - "begin": "(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?)", - "end": "()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.js.jsx" - }, - "2": { - "name": "entity.name.tag.namespace.js.jsx" - }, - "3": { - "name": "punctuation.separator.namespace.js.jsx" - }, - "4": { - "name": "entity.name.tag.js.jsx" - }, - "5": { - "name": "support.class.component.js.jsx" - }, - "6": { - "name": "punctuation.definition.tag.end.js.jsx" - } - }, - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.js.jsx" - }, - "2": { - "name": "entity.name.tag.namespace.js.jsx" - }, - "3": { - "name": "punctuation.separator.namespace.js.jsx" - }, - "4": { - "name": "entity.name.tag.js.jsx" - }, - "5": { - "name": "support.class.component.js.jsx" - }, - "6": { - "name": "punctuation.definition.tag.end.js.jsx" - } - }, - "contentName": "meta.jsx.children.js.jsx", - "patterns": [ - { - "include": "#jsx-children" - } - ] - }, - "jsx-tag-in-expression": { - "begin": "(?x)\n (?:*]|&&|\\|\\||\\?|\\*\\/|^await|[^\\._$[:alnum:]]await|^return|[^\\._$[:alnum:]]return|^default|[^\\._$[:alnum:]]default|^yield|[^\\._$[:alnum:]]yield|^)\\s*\n (?!<\\s*[_$[:alpha:]][_$[:alnum:]]*((\\s+extends\\s+[^=>])|,)) # look ahead is not type parameter of arrow\n (?=(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "end": "(?!(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "patterns": [ - { - "include": "#jsx-tag" - } - ] - }, - "jsx-tag": { - "name": "meta.tag.js.jsx", - "begin": "(?=(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?))", - "end": "(/>)|(?:())", - "endCaptures": { - "1": { - "name": "punctuation.definition.tag.end.js.jsx" - }, - "2": { - "name": "punctuation.definition.tag.begin.js.jsx" - }, - "3": { - "name": "entity.name.tag.namespace.js.jsx" - }, - "4": { - "name": "punctuation.separator.namespace.js.jsx" - }, - "5": { - "name": "entity.name.tag.js.jsx" - }, - "6": { - "name": "support.class.component.js.jsx" - }, - "7": { - "name": "punctuation.definition.tag.end.js.jsx" - } - }, - "patterns": [ - { - "begin": "(<)\\s*(?:([_$[:alpha:]][-_$[:alnum:].]*)(?)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.begin.js.jsx" - }, - "2": { - "name": "entity.name.tag.namespace.js.jsx" - }, - "3": { - "name": "punctuation.separator.namespace.js.jsx" - }, - "4": { - "name": "entity.name.tag.js.jsx" - }, - "5": { - "name": "support.class.component.js.jsx" - } - }, - "end": "(?=[/]?>)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#type-arguments" - }, - { - "include": "#jsx-tag-attributes" - } - ] - }, - { - "begin": "(>)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.tag.end.js.jsx" - } - }, - "end": "(?=)", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#jsx-tag-attribute-name" - }, - { - "include": "#jsx-tag-attribute-assignment" - }, - { - "include": "#jsx-string-double-quoted" - }, - { - "include": "#jsx-string-single-quoted" - }, - { - "include": "#jsx-evaluated-code" - }, - { - "include": "#jsx-tag-attributes-illegal" - } - ] - }, - "jsx-tag-attribute-name": { - "match": "(?x)\n \\s*\n (?:([_$[:alpha:]][-_$[:alnum:].]*)(:))?\n ([_$[:alpha:]][-_$[:alnum:]]*)\n (?=\\s|=|/?>|/\\*|//)", - "captures": { - "1": { - "name": "entity.other.attribute-name.namespace.js.jsx" - }, - "2": { - "name": "punctuation.separator.namespace.js.jsx" - }, - "3": { - "name": "entity.other.attribute-name.js.jsx" - } - } - }, - "jsx-tag-attribute-assignment": { - "name": "keyword.operator.assignment.js.jsx", - "match": "=(?=\\s*(?:'|\"|{|/\\*|//|\\n))" - }, - "jsx-string-double-quoted": { - "name": "string.quoted.double.js.jsx", - "begin": "\"", - "end": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.js.jsx" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.js.jsx" - } - }, - "patterns": [ - { - "include": "#jsx-entities" - } - ] - }, - "jsx-string-single-quoted": { - "name": "string.quoted.single.js.jsx", - "begin": "'", - "end": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.js.jsx" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.js.jsx" - } - }, - "patterns": [ - { - "include": "#jsx-entities" - } - ] - }, - "jsx-tag-attributes-illegal": { - "name": "invalid.illegal.attribute.js.jsx", - "match": "\\S+" - } - } -} \ No newline at end of file diff --git a/extensions/javascript/syntaxes/Readme.md b/extensions/javascript/syntaxes/Readme.md deleted file mode 100644 index bc29199fd7..0000000000 --- a/extensions/javascript/syntaxes/Readme.md +++ /dev/null @@ -1,10 +0,0 @@ -The file `JavaScript.tmLanguage.json` is derived from [TypeScriptReact.tmLanguage](https://github.com/microsoft/TypeScript-TmLanguage/blob/master/TypeScriptReact.tmLanguage). - -To update to the latest version: -- `cd extensions/typescript` and run `npm run update-grammars` -- don't forget to run the integration tests at `./scripts/test-integration.sh` - -The script does the following changes: -- fileTypes .tsx -> .js & .jsx -- scopeName scope.tsx -> scope.js -- update all rule names .tsx -> .js diff --git a/extensions/javascript/syntaxes/Regular Expressions (JavaScript).tmLanguage b/extensions/javascript/syntaxes/Regular Expressions (JavaScript).tmLanguage deleted file mode 100644 index 1dda780649..0000000000 --- a/extensions/javascript/syntaxes/Regular Expressions (JavaScript).tmLanguage +++ /dev/null @@ -1,237 +0,0 @@ - - - - - fileTypes - - hideFromUser - - name - Regular Expressions (JavaScript) - patterns - - - include - #regexp - - - repository - - regex-character-class - - patterns - - - match - \\[wWsSdD]|\. - name - constant.character.character-class.regexp - - - match - \\([0-7]{3}|x\h\h|u\h\h\h\h) - name - constant.character.numeric.regexp - - - match - \\c[A-Z] - name - constant.character.control.regexp - - - match - \\. - name - constant.character.escape.backslash.regexp - - - - regexp - - patterns - - - match - \\[bB]|\^|\$ - name - keyword.control.anchor.regexp - - - match - \\[1-9]\d* - name - keyword.other.back-reference.regexp - - - match - [?+*]|\{(\d+,\d+|\d+,|,\d+|\d+)\}\?? - name - keyword.operator.quantifier.regexp - - - match - \| - name - keyword.operator.or.regexp - - - begin - (\()((\?=)|(\?!)) - beginCaptures - - 1 - - name - punctuation.definition.group.regexp - - 3 - - name - meta.assertion.look-ahead.regexp - - 4 - - name - meta.assertion.negative-look-ahead.regexp - - - end - (\)) - endCaptures - - 1 - - name - punctuation.definition.group.regexp - - - name - meta.group.assertion.regexp - patterns - - - include - #regexp - - - - - begin - \((\?:)? - beginCaptures - - 0 - - name - punctuation.definition.group.regexp - - - end - \) - endCaptures - - 0 - - name - punctuation.definition.group.regexp - - - name - meta.group.regexp - patterns - - - include - #regexp - - - - - begin - (\[)(\^)? - beginCaptures - - 1 - - name - punctuation.definition.character-class.regexp - - 2 - - name - keyword.operator.negation.regexp - - - end - (\]) - endCaptures - - 1 - - name - punctuation.definition.character-class.regexp - - - name - constant.other.character-class.set.regexp - patterns - - - captures - - 1 - - name - constant.character.numeric.regexp - - 2 - - name - constant.character.control.regexp - - 3 - - name - constant.character.escape.backslash.regexp - - 4 - - name - constant.character.numeric.regexp - - 5 - - name - constant.character.control.regexp - - 6 - - name - constant.character.escape.backslash.regexp - - - match - (?:.|(\\(?:[0-7]{3}|x\h\h|u\h\h\h\h))|(\\c[A-Z])|(\\.))\-(?:[^\]\\]|(\\(?:[0-7]{3}|x\h\h|u\h\h\h\h))|(\\c[A-Z])|(\\.)) - name - constant.other.character-class.range.regexp - - - include - #regex-character-class - - - - - include - #regex-character-class - - - - - scopeName - source.js.regexp - uuid - AC8679DE-3AC7-4056-84F9-69A7ADC29DDD - - diff --git a/extensions/javascript/tags-language-configuration.json b/extensions/javascript/tags-language-configuration.json deleted file mode 100644 index 98fa584d7c..0000000000 --- a/extensions/javascript/tags-language-configuration.json +++ /dev/null @@ -1,165 +0,0 @@ -{ - "comments": { - "blockComment": [ - "{/*", - "*/}" - ] - }, - "brackets": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "<", - ">" - ] - ], - "colorizedBracketPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "colorizedBracketPairs": [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ] - ], - "autoClosingPairs": [ - { - "open": "{", - "close": "}" - }, - { - "open": "[", - "close": "]" - }, - { - "open": "(", - "close": ")" - }, - { - "open": "'", - "close": "'", - "notIn": [ - "string", - "comment" - ] - }, - { - "open": "\"", - "close": "\"", - "notIn": [ - "string" - ] - }, - { - "open": "/**", - "close": " */", - "notIn": [ - "string" - ] - } - ], - "surroundingPairs": [ - [ - "{", - "}" - ], - [ - "[", - "]" - ], - [ - "(", - ")" - ], - [ - "<", - ">" - ], - [ - "'", - "'" - ], - [ - "\"", - "\"" - ] - ], - "wordPattern": { - "pattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\$\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:'\"\\,\\.\\<\\>\\/\\s]+)" - }, - "onEnterRules": [ - { - "beforeText": { - "pattern": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr))([_:\\w][_:\\w\\-.\\d]*)([^/>]*(?!/)>)[^<]*$", - "flags": "i" - }, - "afterText": { - "pattern": "^<\\/([_:\\w][_:\\w-.\\d]*)\\s*>$", - "flags": "i" - }, - "action": { - "indent": "indentOutdent" - } - }, - { - "beforeText": { - "pattern": "<(?!(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr))([_:\\w][_:\\w\\-.\\d]*)([^/>]*(?!/)>)[^<]*$", - "flags": "i" - }, - "action": { - "indent": "indent" - } - }, - { - // `beforeText` only applies to tokens of a given language. Since we are dealing with jsx-tags, - // make sure we apply to the closing `>` of a tag so that mixed language spans - // such as `
` are handled properly. - "beforeText": { - "pattern": "^>$" - }, - "afterText": { - "pattern": "^<\\/([_:\\w][_:\\w-.\\d]*)\\s*>$", - "flags": "i" - }, - "action": { - "indent": "indentOutdent" - } - }, - { - "beforeText": { - "pattern": "^>$" - }, - "action": { - "indent": "indent" - } - } - ], -} diff --git a/extensions/json-language-features/client/src/browser/jsonClientMain.ts b/extensions/json-language-features/client/src/browser/jsonClientMain.ts index 42a792f9f6..cc917ff7a7 100644 --- a/extensions/json-language-features/client/src/browser/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/browser/jsonClientMain.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, Uri } from 'vscode'; +import { ExtensionContext, Uri, l10n } from 'vscode'; import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor, SchemaRequestService } from '../jsonClient'; import { LanguageClient } from 'vscode-languageclient/browser'; @@ -21,6 +21,8 @@ export async function activate(context: ExtensionContext) { const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/jsonServerMain.js'); try { const worker = new Worker(serverMain.toString()); + worker.postMessage({ i10lLocation: l10n.uri?.toString(false) ?? '' }); + const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => { return new LanguageClient(id, name, clientOptions, worker); }; diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index ccfe6da151..ad55fa6c7f 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -2,26 +2,23 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vscode-nls'; - -const localize = nls.loadMessageBundle(); export type JSONLanguageStatus = { schemas: string[] }; import { workspace, window, languages, commands, ExtensionContext, extensions, Uri, ColorInformation, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, FoldingRange, - ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation + ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation, l10n } from 'vscode'; import { - LanguageClientOptions, RequestType, NotificationType, + LanguageClientOptions, RequestType, NotificationType, FormattingOptions as LSPFormattingOptions, DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, BaseLanguageClient, ProvideFoldingRangeSignature, ProvideDocumentSymbolsSignature, ProvideDocumentColorsSignature } from 'vscode-languageclient'; import { hash } from './utils/hash'; -import { createDocumentColorsLimitItem, createDocumentSymbolsLimitItem, createFoldingRangeLimitItem, createLanguageStatusItem, createLimitStatusItem } from './languageStatus'; +import { createDocumentSymbolsLimitItem, createLanguageStatusItem, createLimitStatusItem } from './languageStatus'; namespace VSCodeContentRequest { export const type: RequestType = new RequestType('vscode/content'); @@ -39,6 +36,23 @@ namespace LanguageStatusRequest { export const type: RequestType = new RequestType('json/languageStatus'); } +interface SortOptions extends LSPFormattingOptions { +} + +interface DocumentSortingParams { + /** + * The uri of the document to sort. + */ + readonly uri: string; + /** + * The sort options + */ + readonly options: SortOptions; +} + +namespace DocumentSortingRequest { + export const type: RequestType = new RequestType('json/sort'); +} export interface ISchemaAssociations { [pattern: string]: string[]; @@ -60,6 +74,10 @@ type Settings = { keepLines?: { enable?: boolean }; validate?: { enable?: boolean }; resultLimit?: number; + jsonFoldingLimit?: number; + jsoncFoldingLimit?: number; + jsonColorDecoratorLimit?: number; + jsoncColorDecoratorLimit?: number; }; http?: { proxy?: string; @@ -71,6 +89,7 @@ export type JSONSchemaSettings = { fileMatch?: string[]; url?: string; schema?: any; + folderUri?: string; }; export namespace SettingIds { @@ -79,6 +98,12 @@ export namespace SettingIds { export const enableValidation = 'json.validate.enable'; export const enableSchemaDownload = 'json.schemaDownload.enable'; export const maxItemsComputed = 'json.maxItemsComputed'; + export const editorFoldingMaximumRegions = 'editor.foldingMaximumRegions'; + export const editorColorDecoratorsLimit = 'editor.colorDecoratorsLimit'; + + export const editorSection = 'editor'; + export const foldingMaximumRegions = 'foldingMaximumRegions'; + export const colorDecoratorsLimit = 'colorDecoratorsLimit'; } export interface TelemetryReporter { @@ -101,9 +126,13 @@ export interface SchemaRequestService { clearCache?(): Promise; } -export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server'); +export const languageServerDescription = l10n.t('JSON Language Server'); let resultLimit = 5000; +let jsonFoldingLimit = 5000; +let jsoncFoldingLimit = 5000; +let jsonColorDecoratorLimit = 5000; +let jsoncColorDecoratorLimit = 5000; export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { @@ -114,7 +143,7 @@ export async function startClient(context: ExtensionContext, newLanguageClient: const documentSelector = ['json', 'jsonc']; const schemaResolutionErrorStatusBarItem = window.createStatusBarItem('status.json.resolveError', StatusBarAlignment.Right, 0); - schemaResolutionErrorStatusBarItem.name = localize('json.resolveError', "JSON: Schema Resolution Error"); + schemaResolutionErrorStatusBarItem.name = l10n.t('JSON: Schema Resolution Error'); schemaResolutionErrorStatusBarItem.text = '$(alert)'; toDispose.push(schemaResolutionErrorStatusBarItem); @@ -123,17 +152,46 @@ export async function startClient(context: ExtensionContext, newLanguageClient: let isClientReady = false; - const foldingRangeLimitStatusBarItem = createLimitStatusItem((limit: number) => createFoldingRangeLimitItem(documentSelector, SettingIds.maxItemsComputed, limit)); const documentSymbolsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentSymbolsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit)); - const documentColorsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentColorsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit)); - toDispose.push(foldingRangeLimitStatusBarItem, documentSymbolsLimitStatusbarItem, documentColorsLimitStatusbarItem); + toDispose.push(documentSymbolsLimitStatusbarItem); toDispose.push(commands.registerCommand('json.clearCache', async () => { if (isClientReady && runtime.schemaRequests.clearCache) { const cachedSchemas = await runtime.schemaRequests.clearCache(); await client.sendNotification(SchemaContentChangeNotification.type, cachedSchemas); } - window.showInformationMessage(localize('json.clearCache.completed', "JSON schema cache cleared.")); + window.showInformationMessage(l10n.t('JSON schema cache cleared.')); + })); + + toDispose.push(commands.registerCommand('json.sort', async () => { + + if (isClientReady) { + const textEditor = window.activeTextEditor; + if (textEditor) { + const document = textEditor.document; + const filesConfig = workspace.getConfiguration('files', document); + const options: SortOptions = { + tabSize: textEditor.options.tabSize ? Number(textEditor.options.tabSize) : 4, + insertSpaces: textEditor.options.insertSpaces ? Boolean(textEditor.options.insertSpaces) : true, + trimTrailingWhitespace: filesConfig.get('trimTrailingWhitespace'), + trimFinalNewlines: filesConfig.get('trimFinalNewlines'), + insertFinalNewline: filesConfig.get('insertFinalNewline'), + }; + const params: DocumentSortingParams = { + uri: document.uri.toString(), + options + }; + const textEdits = await client.sendRequest(DocumentSortingRequest.type, params); + const success = await textEditor.edit(mutator => { + for (const edit of textEdits) { + mutator.replace(client.protocol2CodeConverter.asRange(edit.range), edit.newText); + } + }); + if (!success) { + window.showErrorMessage(l10n.t('Failed to sort the JSONC document, please consider opening an issue.')); + } + } + } })); // Options to control the language client @@ -214,36 +272,18 @@ export async function startClient(context: ExtensionContext, newLanguageClient: return updateHover(r); }, provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken, next: ProvideFoldingRangeSignature) { - function checkLimit(r: FoldingRange[] | null | undefined): FoldingRange[] | null | undefined { - if (Array.isArray(r) && r.length > resultLimit) { - r.length = resultLimit; // truncate - foldingRangeLimitStatusBarItem.update(document, resultLimit); - } else { - foldingRangeLimitStatusBarItem.update(document, false); - } - return r; - } const r = next(document, context, token); if (isThenable(r)) { - return r.then(checkLimit); - } - return checkLimit(r); - }, - provideDocumentColors(document: TextDocument, token: CancellationToken, next: ProvideDocumentColorsSignature) { - function checkLimit(r: ColorInformation[] | null | undefined): ColorInformation[] | null | undefined { - if (Array.isArray(r) && r.length > resultLimit) { - r.length = resultLimit; // truncate - documentColorsLimitStatusbarItem.update(document, resultLimit); - } else { - documentColorsLimitStatusbarItem.update(document, false); - } return r; } + return r; + }, + provideDocumentColors(document: TextDocument, token: CancellationToken, next: ProvideDocumentColorsSignature) { const r = next(document, token); if (isThenable(r)) { - return r.then(checkLimit); + return r; } - return checkLimit(r); + return r; }, provideDocumentSymbols(document: TextDocument, token: CancellationToken, next: ProvideDocumentSymbolsSignature) { type T = SymbolInformation[] | DocumentSymbol[]; @@ -280,7 +320,7 @@ export async function startClient(context: ExtensionContext, newLanguageClient: client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { const uri = Uri.parse(uriPath); if (uri.scheme === 'untitled') { - return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString()))); + return Promise.reject(new ResponseError(3, l10n.t('Unable to load {0}', uri.toString()))); } if (uri.scheme !== 'http' && uri.scheme !== 'https') { return workspace.openTextDocument(uri).then(doc => { @@ -304,7 +344,7 @@ export async function startClient(context: ExtensionContext, newLanguageClient: return Promise.reject(new ResponseError(4, e.toString())); }); } else { - return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload))); + return Promise.reject(new ResponseError(1, l10n.t('Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload))); } }); @@ -380,6 +420,8 @@ export async function startClient(context: ExtensionContext, newLanguageClient: updateFormatterRegistration(); } else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) { updateSchemaDownloadSetting(); + } else if (e.affectsConfiguration(SettingIds.editorFoldingMaximumRegions) || e.affectsConfiguration(SettingIds.editorColorDecoratorsLimit)) { + client.sendNotification(DidChangeConfigurationNotification.type, { settings: getSettings() }); } })); @@ -420,11 +462,11 @@ export async function startClient(context: ExtensionContext, newLanguageClient: function updateSchemaDownloadSetting() { schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false; if (schemaDownloadEnabled) { - schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.'); + schemaResolutionErrorStatusBarItem.tooltip = l10n.t('Unable to resolve schema. Click to retry.'); schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema'; handleRetryResolveSchemaCommand(); } else { - schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.'); + schemaResolutionErrorStatusBarItem.tooltip = l10n.t('Downloading schemas is disabled. Click to configure.'); schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' }; } } @@ -472,7 +514,18 @@ function getSettings(): Settings { const configuration = workspace.getConfiguration(); const httpSettings = workspace.getConfiguration('http'); - resultLimit = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000; + const normalizeLimit = (settingValue: any) => Math.trunc(Math.max(0, Number(settingValue))) || 5000; + + resultLimit = normalizeLimit(workspace.getConfiguration().get(SettingIds.maxItemsComputed)); + const editorJSONSettings = workspace.getConfiguration(SettingIds.editorSection, { languageId: 'json' }); + const editorJSONCSettings = workspace.getConfiguration(SettingIds.editorSection, { languageId: 'jsonc' }); + + jsonFoldingLimit = normalizeLimit(editorJSONSettings.get(SettingIds.foldingMaximumRegions)); + jsoncFoldingLimit = normalizeLimit(editorJSONCSettings.get(SettingIds.foldingMaximumRegions)); + jsonColorDecoratorLimit = normalizeLimit(editorJSONSettings.get(SettingIds.colorDecoratorsLimit)); + jsoncColorDecoratorLimit = normalizeLimit(editorJSONCSettings.get(SettingIds.colorDecoratorsLimit)); + + const schemas: JSONSchemaSettings[] = []; const settings: Settings = { http: { @@ -483,96 +536,67 @@ function getSettings(): Settings { validate: { enable: configuration.get(SettingIds.enableValidation) }, format: { enable: configuration.get(SettingIds.enableFormatter) }, keepLines: { enable: configuration.get(SettingIds.enableKeepLines) }, - schemas: [], - resultLimit: resultLimit + 1 // ask for one more so we can detect if the limit has been exceeded + schemas, + resultLimit: resultLimit + 1, // ask for one more so we can detect if the limit has been exceeded + jsonFoldingLimit: jsonFoldingLimit + 1, + jsoncFoldingLimit: jsoncFoldingLimit + 1, + jsonColorDecoratorLimit: jsonColorDecoratorLimit + 1, + jsoncColorDecoratorLimit: jsoncColorDecoratorLimit + 1 } }; - const schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null); - const collectSchemaSettings = (schemaSettings: JSONSchemaSettings[], folderUri?: Uri, isMultiRoot?: boolean) => { - let fileMatchPrefix = undefined; - if (folderUri && isMultiRoot) { - fileMatchPrefix = folderUri.toString(); - if (fileMatchPrefix[fileMatchPrefix.length - 1] === '/') { - fileMatchPrefix = fileMatchPrefix.substr(0, fileMatchPrefix.length - 1); - } - } - for (const setting of schemaSettings) { - const url = getSchemaId(setting, folderUri); - if (!url) { - continue; - } - let schemaSetting = schemaSettingsById[url]; - if (!schemaSetting) { - schemaSetting = schemaSettingsById[url] = { url, fileMatch: [] }; - settings.json!.schemas!.push(schemaSetting); - } - const fileMatches = setting.fileMatch; - if (Array.isArray(fileMatches)) { - const resultingFileMatches = schemaSetting.fileMatch || []; - schemaSetting.fileMatch = resultingFileMatches; - const addMatch = (pattern: string) => { // filter duplicates - if (resultingFileMatches.indexOf(pattern) === -1) { - resultingFileMatches.push(pattern); - } - }; - for (const fileMatch of fileMatches) { - if (fileMatchPrefix) { - if (fileMatch[0] === '/') { - addMatch(fileMatchPrefix + fileMatch); - addMatch(fileMatchPrefix + '/*' + fileMatch); - } else { - addMatch(fileMatchPrefix + '/' + fileMatch); - addMatch(fileMatchPrefix + '/*/' + fileMatch); - } - } else { - addMatch(fileMatch); - } + /* + * Add schemas from the settings + * folderUri to which folder the setting is scoped to. `undefined` means global (also external files) + * settingsLocation against which path relative schema URLs are resolved + */ + const collectSchemaSettings = (schemaSettings: JSONSchemaSettings[] | undefined, folderUri: string | undefined, settingsLocation: Uri | undefined) => { + if (schemaSettings) { + for (const setting of schemaSettings) { + const url = getSchemaId(setting, settingsLocation); + if (url) { + const schemaSetting: JSONSchemaSettings = { url, fileMatch: setting.fileMatch, folderUri, schema: setting.schema }; + schemas.push(schemaSetting); } } - if (setting.schema && !schemaSetting.schema) { - schemaSetting.schema = setting.schema; - } } }; - const folders = workspace.workspaceFolders; + const folders = workspace.workspaceFolders ?? []; - // merge global and folder settings. Qualify all file matches with the folder path. - const globalSettings = workspace.getConfiguration('json', null).get('schemas'); - if (Array.isArray(globalSettings)) { - if (!folders) { - collectSchemaSettings(globalSettings); - } - } - if (folders) { - const isMultiRoot = folders.length > 1; - for (const folder of folders) { - const folderUri = folder.uri; - - const schemaConfigInfo = workspace.getConfiguration('json', folderUri).inspect('schemas'); - - const folderSchemas = schemaConfigInfo!.workspaceFolderValue; - if (Array.isArray(folderSchemas)) { - collectSchemaSettings(folderSchemas, folderUri, isMultiRoot); + const schemaConfigInfo = workspace.getConfiguration('json', null).inspect('schemas'); + if (schemaConfigInfo) { + // settings in user config + collectSchemaSettings(schemaConfigInfo.globalValue, undefined, undefined); + if (workspace.workspaceFile) { + if (schemaConfigInfo.workspaceValue) { + const settingsLocation = Uri.joinPath(workspace.workspaceFile, '..'); + // settings in the workspace configuration file apply to all files (also external files) + collectSchemaSettings(schemaConfigInfo.workspaceValue, undefined, settingsLocation); } - if (Array.isArray(globalSettings)) { - collectSchemaSettings(globalSettings, folderUri, isMultiRoot); + for (const folder of folders) { + const folderUri = folder.uri; + const folderSchemaConfigInfo = workspace.getConfiguration('json', folderUri).inspect('schemas'); + collectSchemaSettings(folderSchemaConfigInfo?.workspaceFolderValue, folderUri.toString(false), folderUri); + } + } else { + if (schemaConfigInfo.workspaceValue && folders.length === 1) { + // single folder workspace: settings apply to all files (also external files) + collectSchemaSettings(schemaConfigInfo.workspaceValue, undefined, folders[0].uri); } - } } return settings; } -function getSchemaId(schema: JSONSchemaSettings, folderUri?: Uri): string | undefined { +function getSchemaId(schema: JSONSchemaSettings, settingsLocation?: Uri): string | undefined { let url = schema.url; if (!url) { if (schema.schema) { url = schema.schema.id || `vscode://schemas/custom/${encodeURIComponent(hash(schema.schema).toString(16))}`; } - } else if (folderUri && (url[0] === '.' || url[0] === '/')) { - url = Uri.joinPath(folderUri, url).toString(); + } else if (settingsLocation && (url[0] === '.' || url[0] === '/')) { + url = Uri.joinPath(settingsLocation, url).toString(false); } return url; } diff --git a/extensions/json-language-features/client/src/languageStatus.ts b/extensions/json-language-features/client/src/languageStatus.ts index 7b18cb6f86..5a0e47705b 100644 --- a/extensions/json-language-features/client/src/languageStatus.ts +++ b/extensions/json-language-features/client/src/languageStatus.ts @@ -6,14 +6,10 @@ import { window, languages, Uri, Disposable, commands, QuickPickItem, extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind, - ThemeIcon, TextDocument, LanguageStatusSeverity + ThemeIcon, TextDocument, LanguageStatusSeverity, l10n } from 'vscode'; import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient'; -import * as nls from 'vscode-nls'; - -const localize = nls.loadMessageBundle(); - type ShowSchemasInput = { schemas: string[]; uri: string; @@ -47,9 +43,9 @@ function getExtensionSchemaAssociations() { if (association.fullUri === uri) { return { label: association.label, - detail: localize('schemaFromextension', 'Configured by extension: {0}', association.extension.id), + detail: l10n.t('Configured by extension: {0}', association.extension.id), uri: Uri.parse(association.fullUri), - buttons: [{ iconPath: new ThemeIcon('extensions'), tooltip: localize('openExtension', 'Open Extension') }], + buttons: [{ iconPath: new ThemeIcon('extensions'), tooltip: l10n.t('Open Extension') }], buttonCommands: [() => commands.executeCommand('workbench.extensions.action.showExtensionsWithIds', [[association.extension.id]])] }; } @@ -101,9 +97,9 @@ function getSettingsSchemaAssociations(uri: string) { if (association.fullUri === uri) { return { label: association.label, - detail: association.workspaceFolder ? localize('schemaFromFolderSettings', 'Configured in workspace settings') : localize('schemaFromUserSettings', 'Configured in user settings'), + detail: association.workspaceFolder ? l10n.t('Configured in workspace settings') : l10n.t('Configured in user settings'), uri: Uri.parse(association.fullUri), - buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: localize('openSettings', 'Open Settings') }], + buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: l10n.t('Open Settings') }], buttonCommands: [() => commands.executeCommand(association.workspaceFolder ? 'workbench.action.openWorkspaceSettingsFile' : 'workbench.action.openSettingsJson', ['json.schemas'])] }; } @@ -139,18 +135,17 @@ function showSchemaList(input: ShowSchemasInput) { const items: ShowSchemasItem[] = [...extensionEntries, ...settingsEntries, ...otherEntries]; if (items.length === 0) { items.push({ - label: localize('schema.noSchema', 'No schema configured for this file'), - buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: localize('openSettings', 'Open Settings') }], + label: l10n.t('No schema configured for this file'), + buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: l10n.t('Open Settings') }], buttonCommands: [() => commands.executeCommand('workbench.action.openSettingsJson', ['json.schemas'])] }); } items.push({ label: '', kind: QuickPickItemKind.Separator }); - items.push({ label: localize('schema.showdocs', 'Learn more about JSON schema configuration...'), uri: Uri.parse('https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings') }); + items.push({ label: l10n.t('Learn more about JSON schema configuration...'), uri: Uri.parse('https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings') }); const quickPick = window.createQuickPick(); - quickPick.title = localize('schemaPicker.title', 'JSON Schemas used for {0}', input.uri); - // quickPick.placeholder = items.length ? localize('schemaPicker.placeholder', 'Select the schema to open') : undefined; + quickPick.placeholder = items.length ? l10n.t('Select the schema to use for {0}', input.uri) : undefined; quickPick.items = items; quickPick.show(); quickPick.onDidAccept(() => { @@ -170,7 +165,7 @@ function showSchemaList(input: ShowSchemasInput) { export function createLanguageStatusItem(documentSelector: string[], statusRequest: (uri: string) => Promise): Disposable { const statusItem = languages.createLanguageStatusItem('json.projectStatus', documentSelector); - statusItem.name = localize('statusItem.name', "JSON Validation Status"); + statusItem.name = l10n.t('JSON Validation Status'); statusItem.severity = LanguageStatusSeverity.Information; const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', showSchemaList); @@ -181,36 +176,36 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque async function updateLanguageStatus() { const document = window.activeTextEditor?.document; - if (document && documentSelector.indexOf(document.languageId) !== -1) { + if (document) { try { statusItem.text = '$(loading~spin)'; - statusItem.detail = localize('pending.detail', 'Loading JSON info'); + statusItem.detail = l10n.t('Loading JSON info'); statusItem.command = undefined; const schemas = (await statusRequest(document.uri.toString())).schemas; statusItem.detail = undefined; if (schemas.length === 0) { - statusItem.text = localize('status.noSchema.short', "No Schema Validation"); - statusItem.detail = localize('status.noSchema', 'no JSON schema configured'); + statusItem.text = l10n.t('No Schema Validation'); + statusItem.detail = l10n.t('no JSON schema configured'); } else if (schemas.length === 1) { - statusItem.text = localize('status.withSchema.short', "Schema Validated"); - statusItem.detail = localize('status.singleSchema', 'JSON schema configured'); + statusItem.text = l10n.t('Schema Validated'); + statusItem.detail = l10n.t('JSON schema configured'); } else { - statusItem.text = localize('status.withSchemas.short', "Schema Validated"); - statusItem.detail = localize('status.multipleSchema', 'multiple JSON schemas configured'); + statusItem.text = l10n.t('Schema Validated'); + statusItem.detail = l10n.t('multiple JSON schemas configured'); } statusItem.command = { command: '_json.showAssociatedSchemaList', - title: localize('status.openSchemasLink', 'Show Schemas'), + title: l10n.t('Show Schemas'), arguments: [{ schemas, uri: document.uri.toString() } as ShowSchemasInput] }; } catch (e) { - statusItem.text = localize('status.error', 'Unable to compute used schemas'); + statusItem.text = l10n.t('Unable to compute used schemas: {0}', e.message); statusItem.detail = undefined; statusItem.command = undefined; } } else { - statusItem.text = localize('status.notJSON', 'Not a JSON editor'); + statusItem.text = l10n.t('Unable to compute used schemas: No document'); statusItem.detail = undefined; statusItem.command = undefined; } @@ -271,35 +266,16 @@ export function createLimitStatusItem(newItem: (limit: number) => Disposable) { } const openSettingsCommand = 'workbench.action.openSettings'; -const configureSettingsLabel = localize('status.button.configure', "Configure"); - -export function createFoldingRangeLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable { - const statusItem = languages.createLanguageStatusItem('json.foldingRangesStatus', documentSelector); - statusItem.name = localize('foldingRangesStatusItem.name', "JSON Folding Status"); - statusItem.severity = LanguageStatusSeverity.Warning; - statusItem.text = localize('status.limitedFoldingRanges.short', "Folding Ranges Limited"); - statusItem.detail = localize('status.limitedFoldingRanges.details', 'only {0} folding ranges shown', limit); - statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel }; - return Disposable.from(statusItem); -} +const configureSettingsLabel = l10n.t('Configure'); export function createDocumentSymbolsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable { const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector); - statusItem.name = localize('documentSymbolsStatusItem.name', "JSON Outline Status"); + statusItem.name = l10n.t('JSON Outline Status'); statusItem.severity = LanguageStatusSeverity.Warning; - statusItem.text = localize('status.limitedDocumentSymbols.short', "Outline Limited"); - statusItem.detail = localize('status.limitedDocumentSymbols.details', 'only {0} document symbols shown', limit); + statusItem.text = l10n.t('Outline'); + statusItem.detail = l10n.t('only {0} document symbols shown for performance reasons', limit); statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel }; return Disposable.from(statusItem); } -export function createDocumentColorsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable { - const statusItem = languages.createLanguageStatusItem('json.documentColorsStatus', documentSelector); - statusItem.name = localize('documentColorsStatusItem.name', "JSON Color Symbol Status"); - statusItem.severity = LanguageStatusSeverity.Warning; - statusItem.text = localize('status.limitedDocumentColors.short', "Color Symbols Limited"); - statusItem.detail = localize('status.limitedDocumentColors.details', 'only {0} color decorators shown', limit); - statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel }; - return Disposable.from(statusItem); -} diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index 46a2e4cd58..a8d9d17003 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext, OutputChannel, window, workspace } from 'vscode'; +import { ExtensionContext, OutputChannel, window, workspace, l10n } from 'vscode'; import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription } from '../jsonClient'; import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; @@ -20,7 +20,7 @@ let client: BaseLanguageClient | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { const clientPackageJSON = await getPackageInfo(context); - telemetry = new TelemetryReporter(clientPackageJSON.name, clientPackageJSON.version, clientPackageJSON.aiKey); + telemetry = new TelemetryReporter(clientPackageJSON.aiKey); const outputChannel = window.createOutputChannel(languageServerDescription); @@ -44,6 +44,9 @@ export async function activate(context: ExtensionContext) { const log = getLog(outputChannel); context.subscriptions.push(log); + // pass the location of the localization bundle to the server + process.env['VSCODE_L10N_BUNDLE_LOCATION'] = l10n.uri?.toString() ?? ''; + const schemaRequests = await getSchemaRequestService(context, log); client = await startClient(context, newLanguageClient, { schemaRequests, telemetry }); diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 8f3345c2a6..2b43041ff0 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -7,13 +7,13 @@ "license": "MIT", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { - "vscode": "0.10.x" + "vscode": "^1.77.0" }, + "enabledApiProposals": [], "icon": "icons/json.png", "activationEvents": [ "onLanguage:json", - "onLanguage:jsonc", - "onCommand:json.clearCache" + "onLanguage:jsonc" ], "main": "./client/out/node/jsonClientMain", "browser": "./client/dist/browser/jsonClientMain", @@ -149,14 +149,18 @@ "command": "json.clearCache", "title": "%json.command.clearCache%", "category": "JSON" + }, + { + "command": "json.sort", + "title": "%json.command.sort%", + "category": "JSON" } ] }, "dependencies": { - "@vscode/extension-telemetry": "0.6.2", - "request-light": "^0.5.8", - "vscode-languageclient": "^8.0.2-next.5", - "vscode-nls": "^5.0.1" + "@vscode/extension-telemetry": "^0.7.5", + "request-light": "^0.7.0", + "vscode-languageclient": "^8.2.0-next.1" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index e1c2c8e608..1732a6c7fe 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -17,5 +17,6 @@ "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.", - "json.command.clearCache": "Clear schema cache" + "json.command.clearCache": "Clear Schema Cache", + "json.command.sort": "Sort Document" } diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index bc9b22e4ff..e9875ba597 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -66,10 +66,12 @@ The server supports the following settings: - `enable`: Whether the server should validate. Defaults to `true` if not set. - `schemas`: Configures association of file names to schema URL or schemas and/or associations of schema URL to schema content. - `fileMatch`: an array of file names or paths (separated by `/`). `*` can be used as a wildcard. Exclusion patterns can also be defined and start with '!'. A file matches when there is at least one matching pattern and the last matching pattern is not an exclusion pattern. + - `folderUri`: If provided, the association is only used if the document is located in the given folder (directly or in a subfolder) - `url`: The URL of the schema, optional when also a schema is provided. - - `schema`: The schema content. - - `resultLimit`: The max number folding ranges and outline symbols to be computed (for performance reasons) - + - `schema`: The schema content, optional + - `resultLimit`: The max number of color decorators and outline symbols to be computed (for performance reasons) + - `jsonFoldingLimit`: The max number of folding ranges to be computed for json documents (for performance reasons) + - `jsoncFoldingLimit`: The max number of folding ranges to be computed for jsonc documents (for performance reasons) ```json { "http": { @@ -169,6 +171,10 @@ interface ISchemaAssociation { * A match succeeds when there is at least one pattern matching and last matching pattern does not start with '!'. */ fileMatch: string[]; + /** + * If provided, the association is only used if the validated document is located in the given folder (directly or in a subfolder) + */ + folderUri?: string; /* * The schema for the given URI. * If no schema is provided, the schema will be fetched with the schema request service (if available). @@ -187,7 +193,8 @@ Notification: ### Item Limit -If the setting `resultLimit` is set, the JSON language server will limit the number of folding ranges and document symbols computed. +If the setting `resultLimit` is set, the JSON language server will limit the number of color symbols and document symbols computed. +If the setting `jsonFoldingLimit` or `jsoncFoldingLimit` is set, the JSON language server will limit the number of folding ranges computed. ## Try diff --git a/extensions/json-language-features/server/extension-browser.webpack.config.js b/extensions/json-language-features/server/extension-browser.webpack.config.js index 9f2b265a16..057d4321ff 100644 --- a/extensions/json-language-features/server/extension-browser.webpack.config.js +++ b/extensions/json-language-features/server/extension-browser.webpack.config.js @@ -13,7 +13,7 @@ const path = require('path'); module.exports = withBrowserDefaults({ context: __dirname, entry: { - extension: './src/browser/jsonServerMain.ts', + extension: './src/browser/jsonServerWorkerMain.ts', }, output: { filename: 'jsonServerMain.js', diff --git a/extensions/json-language-features/server/extension.webpack.config.js b/extensions/json-language-features/server/extension.webpack.config.js index 0dbeb1bd0e..29be89b8e5 100644 --- a/extensions/json-language-features/server/extension.webpack.config.js +++ b/extensions/json-language-features/server/extension.webpack.config.js @@ -13,7 +13,7 @@ const path = require('path'); const config = withDefaults({ context: path.join(__dirname), entry: { - extension: './src/node/jsonServerMain.ts', + extension: './src/node/jsonServerNodeMain.ts', }, output: { filename: 'jsonServerMain.js', diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index abae73c408..72e707a58e 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,11 +12,12 @@ }, "main": "./out/node/jsonServerMain", "dependencies": { - "jsonc-parser": "^3.1.0", - "request-light": "^0.5.8", - "vscode-json-languageservice": "^5.1.0", - "vscode-languageserver": "^8.0.2-next.5", - "vscode-uri": "^3.0.3" + "@vscode/l10n": "^0.0.14", + "jsonc-parser": "^3.2.0", + "request-light": "^0.7.0", + "vscode-json-languageservice": "^5.3.5", + "vscode-languageserver": "^8.2.0-next.1", + "vscode-uri": "^3.0.7" }, "devDependencies": { "@types/mocha": "^9.1.1", diff --git a/extensions/json-language-features/server/src/browser/jsonServerMain.ts b/extensions/json-language-features/server/src/browser/jsonServerMain.ts index e133ec3ed9..1fb90f8889 100644 --- a/extensions/json-language-features/server/src/browser/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/browser/jsonServerMain.ts @@ -6,13 +6,15 @@ import { createConnection, BrowserMessageReader, BrowserMessageWriter, Disposable } from 'vscode-languageserver/browser'; import { RuntimeEnvironment, startServer } from '../jsonServer'; -declare let self: any; const messageReader = new BrowserMessageReader(self); const messageWriter = new BrowserMessageWriter(self); const connection = createConnection(messageReader, messageWriter); +console.log = connection.console.log.bind(connection.console); +console.error = connection.console.error.bind(connection.console); + const runtime: RuntimeEnvironment = { timer: { setImmediate(callback: (...args: any[]) => void, ...args: any[]): Disposable { diff --git a/extensions/json-language-features/server/src/browser/jsonServerWorkerMain.ts b/extensions/json-language-features/server/src/browser/jsonServerWorkerMain.ts new file mode 100644 index 0000000000..98bbcbcb83 --- /dev/null +++ b/extensions/json-language-features/server/src/browser/jsonServerWorkerMain.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as l10n from '@vscode/l10n'; + +let initialized = false; +const pendingMessages: any[] = []; +const messageHandler = async (e: any) => { + if (!initialized) { + const l10nLog: string[] = []; + initialized = true; + const i10lLocation = e.data.i10lLocation; + if (i10lLocation) { + try { + await l10n.config({ uri: i10lLocation }); + l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}.`); + } catch (e) { + l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}.`); + } + } else { + l10nLog.push(`l10n: No bundle configured.`); + } + await import('./jsonServerMain'); + if (self.onmessage !== messageHandler) { + pendingMessages.forEach(msg => self.onmessage?.(msg)); + pendingMessages.length = 0; + } + l10nLog.forEach(console.log); + } else { + pendingMessages.push(e); + } +}; +self.onmessage = messageHandler; diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index db08e1dbf4..1ddfe321bc 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -11,7 +11,7 @@ import { import { runSafe, runSafeAsync } from './utils/runner'; import { DiagnosticsSupport, registerDiagnosticsPullSupport, registerDiagnosticsPushSupport } from './utils/validation'; -import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Range, Position } from 'vscode-json-languageservice'; +import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Range, Position, SortOptions } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; import { Utils, URI } from 'vscode-uri'; @@ -39,6 +39,20 @@ namespace LanguageStatusRequest { export const type: RequestType = new RequestType('json/languageStatus'); } +export interface DocumentSortingParams { + /** + * The uri of the document to sort. + */ + uri: string; + /** + * The sort options + */ + options: SortOptions; +} + +namespace DocumentSortingRequest { + export const type: RequestType = new RequestType('json/sort'); +} const workspaceContext = { resolveRelativePath: (relativePath: string, resource: string) => { @@ -106,10 +120,13 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let hierarchicalDocumentSymbolSupport = false; let foldingRangeLimitDefault = Number.MAX_VALUE; - let foldingRangeLimit = Number.MAX_VALUE; let resultLimit = Number.MAX_VALUE; - let formatterMaxNumberOfEdits = Number.MAX_VALUE; + let jsonFoldingRangeLimit = Number.MAX_VALUE; + let jsoncFoldingRangeLimit = Number.MAX_VALUE; + let jsonColorDecoratorLimit = Number.MAX_VALUE; + let jsoncColorDecoratorLimit = Number.MAX_VALUE; + let formatterMaxNumberOfEdits = Number.MAX_VALUE; let diagnosticsSupport: DiagnosticsSupport | undefined; @@ -187,6 +204,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) keepLines?: { enable?: boolean }; validate?: { enable?: boolean }; resultLimit?: number; + jsonFoldingLimit?: number; + jsoncFoldingLimit?: number; + jsonColorDecoratorLimit?: number; + jsoncColorDecoratorLimit?: number; }; http?: { proxy?: string; @@ -198,6 +219,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) fileMatch?: string[]; url?: string; schema?: JSONSchema; + folderUri?: string; } @@ -217,8 +239,12 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) keepLinesEnabled = settings.json?.keepLines?.enable || false; updateConfiguration(); - foldingRangeLimit = Math.trunc(Math.max(settings.json?.resultLimit || foldingRangeLimitDefault, 0)); - resultLimit = Math.trunc(Math.max(settings.json?.resultLimit || Number.MAX_VALUE, 0)); + const sanitizeLimitSetting = (settingValue: any) => Math.trunc(Math.max(settingValue, 0)); + resultLimit = sanitizeLimitSetting(settings.json?.resultLimit || Number.MAX_VALUE); + jsonFoldingRangeLimit = sanitizeLimitSetting(settings.json?.jsonFoldingLimit || foldingRangeLimitDefault); + jsoncFoldingRangeLimit = sanitizeLimitSetting(settings.json?.jsoncFoldingLimit || foldingRangeLimitDefault); + jsonColorDecoratorLimit = sanitizeLimitSetting(settings.json?.jsonColorDecoratorLimit || Number.MAX_VALUE); + jsoncColorDecoratorLimit = sanitizeLimitSetting(settings.json?.jsoncColorDecoratorLimit || Number.MAX_VALUE); // dynamically enable & disable the formatter if (dynamicFormatterRegistration) { @@ -281,6 +307,16 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } }); + connection.onRequest(DocumentSortingRequest.type, async params => { + const uri = params.uri; + const options = params.options; + const document = documents.get(uri); + if (document) { + return languageService.sort(document, options); + } + return []; + }); + function updateConfiguration() { const languageSettings = { validate: validateEnabled, @@ -308,7 +344,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) uri = schema.schema.id || `vscode://schemas/custom/${index}`; } if (uri) { - languageSettings.schemas.push({ uri, fileMatch: schema.fileMatch, schema: schema.schema }); + languageSettings.schemas.push({ uri, fileMatch: schema.fileMatch, schema: schema.schema, folderUri: schema.folderUri }); } }); } @@ -389,6 +425,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); function onFormat(textDocument: TextDocumentIdentifier, range: Range | undefined, options: FormattingOptions): TextEdit[] { + options.keepLines = keepLinesEnabled; const document = documents.get(textDocument.uri); if (document) { @@ -416,6 +453,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) if (document) { const jsonDocument = getJSONDocument(document); + const resultLimit = document.languageId === 'jsonc' ? jsoncColorDecoratorLimit : jsonColorDecoratorLimit; return languageService.findDocumentColors(document, jsonDocument, { resultLimit }); } return []; @@ -437,7 +475,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) return runSafe(runtime, () => { const document = documents.get(params.textDocument.uri); if (document) { - return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit }); + const rangeLimit = document.languageId === 'jsonc' ? jsoncFoldingRangeLimit : jsonFoldingRangeLimit; + return languageService.getFoldingRanges(document, { rangeLimit }); } return null; }, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token); diff --git a/extensions/json-language-features/server/src/node/jsonServerNodeMain.ts b/extensions/json-language-features/server/src/node/jsonServerNodeMain.ts new file mode 100644 index 0000000000..72079c5b65 --- /dev/null +++ b/extensions/json-language-features/server/src/node/jsonServerNodeMain.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as l10n from '@vscode/l10n'; + +async function setupMain() { + const l10nLog: string[] = []; + + const i10lLocation = process.env['VSCODE_L10N_BUNDLE_LOCATION']; + if (i10lLocation) { + try { + await l10n.config({ uri: i10lLocation }); + l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}`); + } catch (e) { + l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}`); + } + } + await import('./jsonServerMain'); + l10nLog.forEach(console.log); +} +setupMain(); diff --git a/extensions/json-language-features/server/tsconfig.json b/extensions/json-language-features/server/tsconfig.json index 48493e51e2..2e8a6e0e35 100644 --- a/extensions/json-language-features/server/tsconfig.json +++ b/extensions/json-language-features/server/tsconfig.json @@ -3,7 +3,11 @@ "compilerOptions": { "outDir": "./out", "sourceMap": true, - "sourceRoot": "../src" + "sourceRoot": "../src", + "lib": [ + "ES2020", + "WebWorker" + ] }, "include": [ "src/**/*" diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index e0b300c908..c8fabcbc15 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -12,68 +12,73 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -jsonc-parser@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.1.0.tgz#73b8f0e5c940b83d03476bc2e51a20ef0932615d" - integrity sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg== +"@vscode/l10n@^0.0.13": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.13.tgz#f51ff130b8c98f189476c5f812d214b8efb09590" + integrity sha512-A3uY356uOU9nGa+TQIT/i3ziWUgJjVMUrGGXSrtRiTwklyCFjGVWIOHoEIHbJpiyhDkJd9kvIWUOfXK1IkK8XQ== -request-light@^0.5.8: - version "0.5.8" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" - integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== +"@vscode/l10n@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.14.tgz#431e5814c35c3cb11ee21873bc70a4b0fbf90fcf" + integrity sha512-/yrv59IEnmh655z1oeDnGcvMYwnEzNzHLgeYcQCkhYX0xBvYWrAuefoiLcPBUkMpJsb46bqQ6Yv4pwTTQ4d3Qg== -vscode-json-languageservice@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.1.0.tgz#b1f197a60338cb378189fcb41489a84846724dd9" - integrity sha512-D5612D7h/Gh4A0JmdttPveWzT9dur21WXvBHWKPdOt0sLO6ILz8vN6+IzWnvwDOVAEFTpzIAMVMZwbKZkwGGiA== +jsonc-parser@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" + integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== + +request-light@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.7.0.tgz#885628bb2f8040c26401ebf258ec51c4ae98ac2a" + integrity sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q== + +vscode-json-languageservice@^5.3.5: + version "5.3.5" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.3.5.tgz#20acd827e13ea4bdeb9976df84ec2bfbb2452c73" + integrity sha512-DasT+bKtpaS2rTPEB4VMROnvO1WES2KD8RZZxXbumnk9sk5wco10VdB6sJgTlsKQN14tHQLZDXuHnSoSAlE8LQ== dependencies: - jsonc-parser "^3.1.0" - vscode-languageserver-textdocument "^1.0.4" - vscode-languageserver-types "^3.17.1" - vscode-nls "^5.0.1" - vscode-uri "^3.0.3" + "@vscode/l10n" "^0.0.13" + jsonc-parser "^3.2.0" + vscode-languageserver-textdocument "^1.0.8" + vscode-languageserver-types "^3.17.3" + vscode-uri "^3.0.7" -vscode-jsonrpc@8.0.2-next.1: - version "8.0.2-next.1" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6" - integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA== +vscode-jsonrpc@8.2.0-next.0: + version "8.2.0-next.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0-next.0.tgz#41409413c8cebf10f2f1b7cc87e330f0e292814c" + integrity sha512-13jYzaFQpTz5qQ2P+l5c/iTVsj1wUpflP0CR/v4XaEpM0oToLEXZBTcuuox1WaGIbu3Av3xxmGNU4Hydl1iNKg== -vscode-languageserver-protocol@3.17.2-next.6: - version "3.17.2-next.6" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc" - integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA== +vscode-languageserver-protocol@3.17.4-next.1: + version "3.17.4-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.1.tgz#a15480e1bc663853ae90ded226efafc5ab333616" + integrity sha512-qrK4BycgPR/+nkRN9PRVTblkLp+kUPUmAgF6rDhFzZIPXW4/MqWwFUT8uswIMGdlTPPgCEkFO/AYEZK1fDXODg== dependencies: - vscode-jsonrpc "8.0.2-next.1" - vscode-languageserver-types "3.17.2-next.2" + vscode-jsonrpc "8.2.0-next.0" + vscode-languageserver-types "3.17.4-next.0" -vscode-languageserver-textdocument@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" - integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== +vscode-languageserver-textdocument@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0" + integrity sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q== -vscode-languageserver-types@3.17.2-next.2: - version "3.17.2-next.2" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596" - integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w== +vscode-languageserver-types@3.17.4-next.0: + version "3.17.4-next.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.4-next.0.tgz#4b5238d21cceaeb836d36a05d23c61a8c0238de2" + integrity sha512-2FPKboHnT04xYjfM8JpJVBz4a/tryMw58jmzucaabZMZN5hzoFBrhc97jNG4n6edr9JUb9+QSwwcAcYpDTAoag== -vscode-languageserver-types@^3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" - integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== +vscode-languageserver-types@^3.17.3: + version "3.17.3" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64" + integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA== -vscode-languageserver@^8.0.2-next.5: - version "8.0.2-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2-next.5.tgz#39a2dd4c504fb88042375e7ac706a714bdaab4e5" - integrity sha512-2ZDb7O/4atS9mJKufPPz637z+51kCyZfgnobFW5eSrUdS3c0UB/nMS4Ng1EavYTX84GVaVMKCrmP0f2ceLmR0A== +vscode-languageserver@^8.2.0-next.1: + version "8.2.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.2.0-next.1.tgz#ad2558d74392b1cfaccd427febe9a368fc328f8b" + integrity sha512-994AXMKBijzjlnpf8p9M+ntsNJDjR8pr55NJPYxKjy/nUhVkg962dAomelH6Z94401kBZmSbfP/K/20cB54aFA== dependencies: - vscode-languageserver-protocol "3.17.2-next.6" + vscode-languageserver-protocol "3.17.4-next.1" -vscode-nls@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" - integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== - -vscode-uri@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84" - integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA== +vscode-uri@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.7.tgz#6d19fef387ee6b46c479e5fb00870e15e58c1eb8" + integrity sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA== diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 2732f71b9f..5bec70a992 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -2,72 +2,320 @@ # yarn lockfile v1 -"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" - integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + tslib "^2.2.0" -"@microsoft/1ds-post-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" - integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== +"@azure/core-auth@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e" + integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ== dependencies: - "@microsoft/1ds-core-js" "3.2.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== +"@azure/core-rest-pipeline@^1.10.0": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz#348290847ca31b9eecf9cf5de7519aaccdd30968" + integrity sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA== dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.0.0" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" + uuid "^8.3.0" -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== +"@azure/core-tracing@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" + integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw== + dependencies: + tslib "^2.2.0" -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== +"@azure/core-util@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.1.1.tgz#8f87b3dd468795df0f0849d9f096c3e7b29452c1" + integrity sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.3.tgz#6e36704aa51be7d4a1bae24731ea580836293c96" + integrity sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g== + dependencies: + tslib "^2.2.0" + +"@microsoft/1ds-core-js@3.2.8", "@microsoft/1ds-core-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.8.tgz#1b6b7d9bb858238c818ccf4e4b58ece7aeae5760" + integrity sha512-9o9SUAamJiTXIYwpkQDuueYt83uZfXp8zp8YFix1IwVPwC9RmE36T2CX9gXOeq1nDckOuOduYpA8qHvdh5BGfQ== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/1ds-post-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.8.tgz#46793842cca161bf7a2a5b6053c349f429e55110" + integrity sha512-SjlRoNcXcXBH6WQD/5SkkaCHIVqldH3gDu+bI7YagrOVJ5APxwT1Duw9gm3L1FjFa9S2i81fvJ3EVSKpp9wULA== + dependencies: + "@microsoft/1ds-core-js" "3.2.8" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-channel-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.9.tgz#840656f3c716de8b3eb0a98c122aa1b92bb8ebfb" + integrity sha512-fMBsAEB7pWtPn43y72q9Xy5E5y55r6gMuDQqRRccccVoQDPXyS57VCj5IdATblctru0C6A8XpL2vRyNmEsu0Vg== + dependencies: + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-common@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.9.tgz#a75e4a3143a7fd797687830c0ddd2069fd900827" + integrity sha512-mObn1moElyxZaGIRF/IU3cOaeKMgxghXnYEoHNUCA2e+rNwBIgxjyKkblFIpmGuHf4X7Oz3o3yBWpaC6AoMpig== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-core-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.9.tgz#0e5d207acfae6986a6fc97249eeb6117e523bf1b" + integrity sha512-HRuIuZ6aOWezcg/G5VyFDDWGL8hDNe/ljPP01J7ImH2kRPEgbtcfPSUMjkamGMefgdq81GZsSoC/NNGTP4pp2w== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085" + integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg== + +"@microsoft/applicationinsights-web-basic@^2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.9.tgz#eed2f3d1e19069962ed2155915c1656e6936e1d5" + integrity sha512-CH0J8JFOy7MjK8JO4pXXU+EML+Ilix+94PMZTX5EJlBU1in+mrik74/8qSg3UC4ekPi12KwrXaHCQSVC3WseXQ== + dependencies: + "@microsoft/applicationinsights-channel-js" "2.8.9" + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-web-snippet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz#6bb788b2902e48bf5d460c38c6bb7fedd686ddd7" + integrity sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ== + +"@microsoft/dynamicproto-js@^1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz#ede48dd3f85af14ee369c805e5ed5b84222b9fe2" + integrity sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA== + +"@opentelemetry/api@^1.0.4": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.2.0.tgz#89ef99401cde6208cff98760b67663726ef26686" + integrity sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g== + +"@opentelemetry/core@1.7.0", "@opentelemetry/core@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.7.0.tgz#83bdd1b7a4ceafcdffd6590420657caec5f7b34c" + integrity sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/resources@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.7.0.tgz#90ccd3a6a86b4dfba4e833e73944bd64958d78c5" + integrity sha512-u1M0yZotkjyKx8dj+46Sg5thwtOTBmtRieNXqdCRiWUp6SfFiIP0bI+1XK3LhuXqXkBXA1awJZaTqKduNMStRg== + dependencies: + "@opentelemetry/core" "1.7.0" + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/sdk-trace-base@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.7.0.tgz#b498424e0c6340a9d80de63fd408c5c2130a60a5" + integrity sha512-Iz84C+FVOskmauh9FNnj4+VrA+hG5o+tkMzXuoesvSfunVSioXib0syVFeNXwOm4+M5GdWCuW632LVjqEXStIg== + dependencies: + "@opentelemetry/core" "1.7.0" + "@opentelemetry/resources" "1.7.0" + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/semantic-conventions@1.7.0", "@opentelemetry/semantic-conventions@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.7.0.tgz#af80a1ef7cf110ea3a68242acd95648991bcd763" + integrity sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA== + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@types/node@16.x": version "16.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -"@vscode/extension-telemetry@0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" - integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== +"@vscode/extension-telemetry@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.7.5.tgz#bf965731816e08c3f146f96d901ec67954fc913b" + integrity sha512-fJ5y3TcpqqkFYHneabYaoB4XAhDdVflVm+TDKshw9VOs77jkgNS4UA7LNXrWeO0eDne3Sh3JgURf+xzc1rk69w== dependencies: - "@microsoft/1ds-core-js" "^3.2.3" - "@microsoft/1ds-post-js" "^3.2.3" + "@microsoft/1ds-core-js" "^3.2.8" + "@microsoft/1ds-post-js" "^3.2.8" + "@microsoft/applicationinsights-web-basic" "^2.8.9" + applicationinsights "2.4.1" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +applicationinsights@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-2.4.1.tgz#4de4c4dd3c7c4a44445cfbf3d15808fc0dcc423d" + integrity sha512-0n0Ikd0gzSm460xm+M0UTWIwXrhrH/0bqfZatcJjYObWyefxfAxapGEyNnSGd1Tg90neHz+Yhf+Ff/zgvPiQYA== + dependencies: + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.10.0" + "@microsoft/applicationinsights-web-snippet" "^1.0.1" + "@opentelemetry/api" "^1.0.4" + "@opentelemetry/core" "^1.0.1" + "@opentelemetry/sdk-trace-base" "^1.0.1" + "@opentelemetry/semantic-conventions" "^1.0.1" + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "1.1.0" + diagnostic-channel-publishers "1.0.5" + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 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== +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" - 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= +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +diagnostic-channel-publishers@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz#df8c317086c50f5727fdfb5d2fce214d2e4130ae" + integrity sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg== + +diagnostic-channel@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz#6985e9dfedfbc072d91dc4388477e4087147756e" + integrity sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ== + dependencies: + semver "^5.3.0" + +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" lru-cache@^6.0.0: version "6.0.0" @@ -76,56 +324,93 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -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== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== dependencies: - brace-expansion "^1.1.7" + mime-db "1.52.0" -request-light@^0.5.8: - version "0.5.8" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" - integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== +minimatch@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" -semver@^7.3.5: - version "7.5.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.3.tgz#161ce8c2c6b4b3bdca6caadc9fa3317a4c4fe88e" - integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== +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== + +request-light@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.7.0.tgz#885628bb2f8040c26401ebf258ec51c4ae98ac2a" + integrity sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q== + +semver@^5.3.0, semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -vscode-jsonrpc@8.0.2-next.1: - version "8.0.2-next.1" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6" - integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA== +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== -vscode-languageclient@^8.0.2-next.5: - version "8.0.2-next.5" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.2-next.5.tgz#3238a388585c3119e247f761b4355273cc2fd909" - integrity sha512-g87RJLHz0XlRyk6DOTbAk4JHcj8CKggXy4JiFL7OlhETkcYzTOR8d+Qdb4GqZr37PDs1Cl21omtTNK5LyR/RQg== +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== + +tslib@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +vscode-jsonrpc@8.2.0-next.0: + version "8.2.0-next.0" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0-next.0.tgz#41409413c8cebf10f2f1b7cc87e330f0e292814c" + integrity sha512-13jYzaFQpTz5qQ2P+l5c/iTVsj1wUpflP0CR/v4XaEpM0oToLEXZBTcuuox1WaGIbu3Av3xxmGNU4Hydl1iNKg== + +vscode-languageclient@^8.2.0-next.1: + version "8.2.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.2.0-next.1.tgz#a3f98b80cfa3225fde0583aa6a5c9b20219fa37e" + integrity sha512-oITaqHQ10PM3zXCUu/104wriMeDutXMkQXMaRBWh1jKihcNcUBLC/os7RhqiVGypY0nl+F0pwStAf4Koc8inaw== dependencies: - minimatch "^3.0.4" - semver "^7.3.5" - vscode-languageserver-protocol "3.17.2-next.6" + minimatch "^5.1.0" + semver "^7.3.7" + vscode-languageserver-protocol "3.17.4-next.1" -vscode-languageserver-protocol@3.17.2-next.6: - version "3.17.2-next.6" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc" - integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA== +vscode-languageserver-protocol@3.17.4-next.1: + version "3.17.4-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.4-next.1.tgz#a15480e1bc663853ae90ded226efafc5ab333616" + integrity sha512-qrK4BycgPR/+nkRN9PRVTblkLp+kUPUmAgF6rDhFzZIPXW4/MqWwFUT8uswIMGdlTPPgCEkFO/AYEZK1fDXODg== dependencies: - vscode-jsonrpc "8.0.2-next.1" - vscode-languageserver-types "3.17.2-next.2" + vscode-jsonrpc "8.2.0-next.0" + vscode-languageserver-types "3.17.4-next.0" -vscode-languageserver-types@3.17.2-next.2: - version "3.17.2-next.2" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596" - integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w== - -vscode-nls@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" - integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== +vscode-languageserver-types@3.17.4-next.0: + version "3.17.4-next.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.4-next.0.tgz#4b5238d21cceaeb836d36a05d23c61a8c0238de2" + integrity sha512-2FPKboHnT04xYjfM8JpJVBz4a/tryMw58jmzucaabZMZN5hzoFBrhc97jNG4n6edr9JUb9+QSwwcAcYpDTAoag== yallist@^4.0.0: version "4.0.0" diff --git a/extensions/json/build/update-grammars.js b/extensions/json/build/update-grammars.js index 18a2916325..03001dc848 100644 --- a/extensions/json/build/update-grammars.js +++ b/extensions/json/build/update-grammars.js @@ -6,8 +6,8 @@ var updateGrammar = require('vscode-grammar-updater'); -function adaptJSON(grammar, replacementScope) { - grammar.name = 'JSON with comments'; +function adaptJSON(grammar, name, replacementScope) { + grammar.name = name; grammar.scopeName = `source${replacementScope}`; var fixScopeNames = function (rule) { @@ -33,9 +33,5 @@ function adaptJSON(grammar, replacementScope) { var tsGrammarRepo = 'microsoft/vscode-JSON.tmLanguage'; updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSON.tmLanguage.json'); -updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONC.tmLanguage.json', grammar => adaptJSON(grammar, '.json.comments')); - - - - - +updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONC.tmLanguage.json', grammar => adaptJSON(grammar, 'JSON with Comments', '.json.comments')); +updateGrammar.update(tsGrammarRepo, 'JSON.tmLanguage', './syntaxes/JSONL.tmLanguage.json', grammar => adaptJSON(grammar, 'JSON Lines', '.json.lines')); diff --git a/extensions/json/package.json b/extensions/json/package.json index 8cb003806d..45daa5aa5c 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -6,7 +6,7 @@ "publisher": "vscode", "license": "MIT", "engines": { - "vscode": "0.10.x" + "vscode": "0.10.0" }, "scripts": { "update-grammar": "node ./build/update-grammars.js" @@ -31,7 +31,8 @@ ".jslintrc", ".jsonld", ".geojson", - ".ipynb" + ".ipynb", + ".vuerc" ], "filenames": [ "composer.lock", @@ -61,9 +62,21 @@ "filenames": [ "babel.config.json", ".babelrc.json", - ".ember-cli" + ".ember-cli", + "typedoc.json" ], "configuration": "./language-configuration.json" + }, + { + "id": "jsonl", + "aliases": [ + "JSON Lines" + ], + "extensions": [ + ".jsonl" + ], + "filenames": [], + "configuration": "./language-configuration.json" } ], "grammars": [ @@ -76,6 +89,11 @@ "language": "jsonc", "scopeName": "source.json.comments", "path": "./syntaxes/JSONC.tmLanguage.json" + }, + { + "language": "jsonl", + "scopeName": "source.json.lines", + "path": "./syntaxes/JSONL.tmLanguage.json" } ] }, diff --git a/extensions/json/syntaxes/JSONC.tmLanguage.json b/extensions/json/syntaxes/JSONC.tmLanguage.json index 31828ba65b..ae5430630f 100644 --- a/extensions/json/syntaxes/JSONC.tmLanguage.json +++ b/extensions/json/syntaxes/JSONC.tmLanguage.json @@ -5,7 +5,7 @@ "Once accepted there, we are happy to receive an update request." ], "version": "https://github.com/microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70", - "name": "JSON with comments", + "name": "JSON with Comments", "scopeName": "source.json.comments", "patterns": [ { diff --git a/extensions/json/syntaxes/JSONL.tmLanguage.json b/extensions/json/syntaxes/JSONL.tmLanguage.json new file mode 100644 index 0000000000..26de8d856f --- /dev/null +++ b/extensions/json/syntaxes/JSONL.tmLanguage.json @@ -0,0 +1,213 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/microsoft/vscode-JSON.tmLanguage/blob/master/JSON.tmLanguage", + "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-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70", + "name": "JSON Lines", + "scopeName": "source.json.lines", + "patterns": [ + { + "include": "#value" + } + ], + "repository": { + "array": { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.array.begin.json.lines" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.array.end.json.lines" + } + }, + "name": "meta.structure.array.json.lines", + "patterns": [ + { + "include": "#value" + }, + { + "match": ",", + "name": "punctuation.separator.array.json.lines" + }, + { + "match": "[^\\s\\]]", + "name": "invalid.illegal.expected-array-separator.json.lines" + } + ] + }, + "comments": { + "patterns": [ + { + "begin": "/\\*\\*(?!/)", + "captures": { + "0": { + "name": "punctuation.definition.comment.json.lines" + } + }, + "end": "\\*/", + "name": "comment.block.documentation.json.lines" + }, + { + "begin": "/\\*", + "captures": { + "0": { + "name": "punctuation.definition.comment.json.lines" + } + }, + "end": "\\*/", + "name": "comment.block.json.lines" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.comment.json.lines" + } + }, + "match": "(//).*$\\n?", + "name": "comment.line.double-slash.js" + } + ] + }, + "constant": { + "match": "\\b(?:true|false|null)\\b", + "name": "constant.language.json.lines" + }, + "number": { + "match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional", + "name": "constant.numeric.json.lines" + }, + "object": { + "begin": "\\{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.dictionary.begin.json.lines" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.dictionary.end.json.lines" + } + }, + "name": "meta.structure.dictionary.json.lines", + "patterns": [ + { + "comment": "the JSON object key", + "include": "#objectkey" + }, + { + "include": "#comments" + }, + { + "begin": ":", + "beginCaptures": { + "0": { + "name": "punctuation.separator.dictionary.key-value.json.lines" + } + }, + "end": "(,)|(?=\\})", + "endCaptures": { + "1": { + "name": "punctuation.separator.dictionary.pair.json.lines" + } + }, + "name": "meta.structure.dictionary.value.json.lines", + "patterns": [ + { + "comment": "the JSON object value", + "include": "#value" + }, + { + "match": "[^\\s,]", + "name": "invalid.illegal.expected-dictionary-separator.json.lines" + } + ] + }, + { + "match": "[^\\s\\}]", + "name": "invalid.illegal.expected-dictionary-separator.json.lines" + } + ] + }, + "string": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.json.lines" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.json.lines" + } + }, + "name": "string.quoted.double.json.lines", + "patterns": [ + { + "include": "#stringcontent" + } + ] + }, + "objectkey": { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.support.type.property-name.begin.json.lines" + } + }, + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.support.type.property-name.end.json.lines" + } + }, + "name": "string.json.lines support.type.property-name.json.lines", + "patterns": [ + { + "include": "#stringcontent" + } + ] + }, + "stringcontent": { + "patterns": [ + { + "match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits", + "name": "constant.character.escape.json.lines" + }, + { + "match": "\\\\.", + "name": "invalid.illegal.unrecognized-string-escape.json.lines" + } + ] + }, + "value": { + "patterns": [ + { + "include": "#constant" + }, + { + "include": "#number" + }, + { + "include": "#string" + }, + { + "include": "#array" + }, + { + "include": "#object" + }, + { + "include": "#comments" + } + ] + } + } +} \ No newline at end of file diff --git a/extensions/julia/.vscodeignore b/extensions/julia/.vscodeignore deleted file mode 100644 index d9011becfb..0000000000 --- a/extensions/julia/.vscodeignore +++ /dev/null @@ -1,2 +0,0 @@ -build/** -cgmanifest.json diff --git a/extensions/julia/cgmanifest.json b/extensions/julia/cgmanifest.json deleted file mode 100644 index 4b247a3d2a..0000000000 --- a/extensions/julia/cgmanifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "registrations": [ - { - "component": { - "type": "git", - "git": { - "name": "JuliaEditorSupport/atom-language-julia", - "repositoryUrl": "https://github.com/JuliaEditorSupport/atom-language-julia", - "commitHash": "7b7801f41ce4ac1303bd17e057dbe677e24f597f" - } - }, - "license": "MIT", - "version": "0.22.1" - } - ], - "version": 1 -} diff --git a/extensions/julia/language-configuration.json b/extensions/julia/language-configuration.json deleted file mode 100644 index 1fa88a486d..0000000000 --- a/extensions/julia/language-configuration.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "comments": { - "lineComment": "#", - "blockComment": [ "#=", "=#" ] - }, - "brackets": [ - ["{", "}"], - ["[", "]"], - ["(", ")"] - ], - "autoClosingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["`", "`"], - { "open": "\"", "close": "\"", "notIn": ["string", "comment"] } - ], - "surroundingPairs": [ - ["{", "}"], - ["[", "]"], - ["(", ")"], - ["\"", "\""], - ["`", "`"] - ], - "folding": { - "markers": { - "start": "^\\s*#region", - "end": "^\\s*#endregion" - } - }, - "indentationRules": { - "increaseIndentPattern": "^(\\s*|.*=\\s*|.*@\\w*\\s*)[\\w\\s]*(?:[\"'`][^\"'`]*[\"'`])*[\\w\\s]*\\b(if|while|for|function|macro|(mutable\\s+)?struct|abstract\\s+type|primitive\\s+type|let|quote|try|begin|.*\\)\\s*do|else|elseif|catch|finally)\\b(?!(?:.*\\bend\\b[^\\]]*)|(?:[^\\[]*\\].*)$).*$", - "decreaseIndentPattern": "^\\s*(end|else|elseif|catch|finally)\\b.*$" - } -} diff --git a/extensions/julia/package.json b/extensions/julia/package.json deleted file mode 100644 index 33fc5c36d9..0000000000 --- a/extensions/julia/package.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name": "julia", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "vscode", - "license": "MIT", - "engines": { - "vscode": "0.10.x" - }, - "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin JuliaEditorSupport/atom-language-julia grammars/julia_vscode.json ./syntaxes/julia.tmLanguage.json" - }, - "contributes": { - "languages": [ - { - "id": "julia", - "aliases": [ - "Julia", - "julia" - ], - "extensions": [ - ".jl" - ], - "firstLine": "^#!\\s*/.*\\bjulia[0-9.-]*\\b", - "configuration": "./language-configuration.json" - }, - { - "id": "juliamarkdown", - "aliases": [ - "Julia Markdown", - "juliamarkdown" - ], - "extensions": [ - ".jmd" - ] - } - ], - "grammars": [ - { - "language": "julia", - "scopeName": "source.julia", - "path": "./syntaxes/julia.tmLanguage.json", - "embeddedLanguages": { - "meta.embedded.inline.cpp": "cpp", - "meta.embedded.inline.javascript": "javascript", - "meta.embedded.inline.python": "python", - "meta.embedded.inline.r": "r", - "meta.embedded.inline.sql": "sql" - } - } - ] - } -} diff --git a/extensions/julia/package.nls.json b/extensions/julia/package.nls.json deleted file mode 100644 index 0da344107c..0000000000 --- a/extensions/julia/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "Julia Language Basics", - "description": "Provides syntax highlighting & bracket matching in Julia files." -} diff --git a/extensions/julia/syntaxes/julia.tmLanguage.json b/extensions/julia/syntaxes/julia.tmLanguage.json deleted file mode 100644 index bd3acb8d6b..0000000000 --- a/extensions/julia/syntaxes/julia.tmLanguage.json +++ /dev/null @@ -1,945 +0,0 @@ -{ - "information_for_contributors": [ - "This file has been converted from https://github.com/JuliaEditorSupport/atom-language-julia/blob/master/grammars/julia_vscode.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/JuliaEditorSupport/atom-language-julia/commit/7b7801f41ce4ac1303bd17e057dbe677e24f597f", - "name": "Julia", - "scopeName": "source.julia", - "comment": "This grammar is used by Atom (Oniguruma), GitHub (PCRE), and VSCode (Oniguruma),\nso all regexps must be compatible with both engines.\n\nSpecs:\n- https://github.com/kkos/oniguruma/blob/master/doc/RE\n- https://www.pcre.org/current/doc/html/", - "patterns": [ - { - "include": "#operator" - }, - { - "include": "#array" - }, - { - "include": "#string" - }, - { - "include": "#parentheses" - }, - { - "include": "#bracket" - }, - { - "include": "#function_decl" - }, - { - "include": "#function_call" - }, - { - "include": "#keyword" - }, - { - "include": "#number" - }, - { - "include": "#comment" - }, - { - "include": "#type_decl" - }, - { - "include": "#symbol" - } - ], - "repository": { - "array": { - "patterns": [ - { - "begin": "\\[", - "beginCaptures": { - "0": { - "name": "meta.bracket.julia" - } - }, - "end": "(\\])((?:\\.)?'*)", - "endCaptures": { - "1": { - "name": "meta.bracket.julia" - }, - "2": { - "name": "keyword.operator.transpose.julia" - } - }, - "name": "meta.array.julia", - "patterns": [ - { - "match": "\\bbegin\\b", - "name": "constant.numeric.julia" - }, - { - "match": "\\bend\\b", - "name": "constant.numeric.julia" - }, - { - "match": "\\bfor\\b", - "name": "keyword.control.julia" - }, - { - "include": "$self" - } - ] - } - ] - }, - "parentheses": { - "patterns": [ - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "meta.bracket.julia" - } - }, - "end": "(\\))((?:\\.)?'*)", - "endCaptures": { - "1": { - "name": "meta.bracket.julia" - }, - "2": { - "name": "keyword.operator.transpose.julia" - } - }, - "patterns": [ - { - "include": "$self" - } - ] - } - ] - }, - "bracket": { - "patterns": [ - { - "match": "(?:\\(|\\)|\\[|\\]|\\{|\\}|,|;)(?!('|(?:\\.'))*\\.?')", - "name": "meta.bracket.julia" - } - ] - }, - "comment": { - "patterns": [ - { - "include": "#comment_block" - }, - { - "begin": "#", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.julia" - } - }, - "end": "\\n", - "name": "comment.line.number-sign.julia" - } - ] - }, - "comment_block": { - "patterns": [ - { - "begin": "#=", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.begin.julia" - } - }, - "end": "=#", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.end.julia" - } - }, - "name": "comment.block.number-sign-equals.julia", - "patterns": [ - { - "include": "#comment_block" - } - ] - } - ] - }, - "function_call": { - "patterns": [ - { - "begin": "((?:[[:alpha:]_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{So}â†-⇿])(?:[[:word:]_!\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{Mn}\u0001-¡]|[^\\P{Mc}\u0001-¡]|[^\\P{Nd}\u0001-¡]|[^\\P{Pc}\u0001-¡]|[^\\P{Sk}\u0001-¡]|[^\\P{Me}\u0001-¡]|[^\\P{No}\u0001-¡]|[′-‷â—]|[^\\P{So}â†-⇿])*)({(?:[^{}]|{(?:[^{}]|{[^{}]*})*})*})?\\.?(\\()", - "beginCaptures": { - "1": { - "name": "support.function.julia" - }, - "2": { - "name": "support.type.julia" - }, - "3": { - "name": "meta.bracket.julia" - } - }, - "end": "\\)(('|(\\.'))*\\.?')?", - "endCaptures": { - "0": { - "name": "meta.bracket.julia" - }, - "1": { - "name": "keyword.operator.transposed-func.julia" - } - }, - "patterns": [ - { - "match": "\\bfor\\b", - "name": "keyword.control.julia" - }, - { - "include": "$self" - } - ] - } - ] - }, - "function_decl": { - "patterns": [ - { - "captures": { - "1": { - "name": "entity.name.function.julia" - }, - "2": { - "name": "support.type.julia" - } - }, - "match": "((?:[[:alpha:]_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{So}â†-⇿])(?:[[:word:]_!\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{Mn}\u0001-¡]|[^\\P{Mc}\u0001-¡]|[^\\P{Nd}\u0001-¡]|[^\\P{Pc}\u0001-¡]|[^\\P{Sk}\u0001-¡]|[^\\P{Me}\u0001-¡]|[^\\P{No}\u0001-¡]|[′-‷â—]|[^\\P{So}â†-⇿])*)({(?:[^{}]|{(?:[^{}]|{[^{}]*})*})*})?(?=\\([^#]*\\)(::[^\\s]+)?(\\s*\\bwhere\\b\\s+.+?)?\\s*?=(?![=>]))", - "comment": "first group is function name\nSecond group is type parameters (e.g. {T<:Number, S})\nThen open parens\nThen a lookahead ensures that we are followed by:\n - anything (function argumnets)\n - 0 or more spaces\n - Finally an equal sign\nNegative lookahead ensures we don't have another equal sign (not `==`)" - }, - { - "captures": { - "1": { - "name": "keyword.other.julia" - }, - "2": { - "name": "keyword.operator.dots.julia" - }, - "3": { - "name": "entity.name.function.julia" - }, - "4": { - "name": "support.type.julia" - } - }, - "match": "\\b(function|macro)(?:\\s+(?:(?:[[:alpha:]_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{So}â†-⇿])(?:[[:word:]_!\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{Mn}\u0001-¡]|[^\\P{Mc}\u0001-¡]|[^\\P{Nd}\u0001-¡]|[^\\P{Pc}\u0001-¡]|[^\\P{Sk}\u0001-¡]|[^\\P{Me}\u0001-¡]|[^\\P{No}\u0001-¡]|[′-‷â—]|[^\\P{So}â†-⇿])*(\\.))?((?:[[:alpha:]_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{So}â†-⇿])(?:[[:word:]_!\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{Mn}\u0001-¡]|[^\\P{Mc}\u0001-¡]|[^\\P{Nd}\u0001-¡]|[^\\P{Pc}\u0001-¡]|[^\\P{Sk}\u0001-¡]|[^\\P{Me}\u0001-¡]|[^\\P{No}\u0001-¡]|[′-‷â—]|[^\\P{So}â†-⇿])*)({(?:[^{}]|{(?:[^{}]|{[^{}]*})*})*})?|\\s*)(?=\\()", - "comment": "similar regex to previous, but with keyword not 1-line syntax" - } - ] - }, - "keyword": { - "patterns": [ - { - "match": "\\b(?|<-|-->|=>)", - "name": "keyword.operator.arrow.julia" - }, - { - "match": "(?::=|\\+=|-=|\\*=|//=|/=|\\.//=|\\./=|\\.\\*=|\\\\=|\\.\\\\=|\\^=|\\.\\^=|%=|\\.%=|÷=|\\.÷=|\\|=|&=|\\.&=|⊻=|\\.⊻=|\\$=|<<=|>>=|>>>=|=(?!=))", - "name": "keyword.operator.update.julia" - }, - { - "match": "(?:<<|>>>|>>|\\.>>>|\\.>>|\\.<<)", - "name": "keyword.operator.shift.julia" - }, - { - "match": "(?:\\s*(::|>:|<:)\\s*((?:(?:Union)?\\([^)]*\\)|[[:alpha:]_$∇][[:word:]âº-ₜ!′\\.]*(?:(?:{(?:[^{}]|{(?:[^{}]|{[^{}]*})*})*})|(?:\".+?(?=|\\.>|\\.<=|\\.<|\\.≤|\\.≥|==|\\.!=|\\.=|\\.!|<:|>:|:>|(?)>=|(?|<|≥|≤)", - "name": "keyword.operator.relation.julia" - }, - { - "match": "(?<=\\s)(?:\\?)(?=\\s)", - "name": "keyword.operator.ternary.julia" - }, - { - "match": "(?<=\\s)(?:\\:)(?=\\s)", - "name": "keyword.operator.ternary.julia" - }, - { - "match": "(?:\\|\\||&&|(?)", - "name": "keyword.operator.applies.julia" - }, - { - "match": "(?:\\||\\.\\||\\&|\\.\\&|~|\\.~|⊻|\\.⊻)", - "name": "keyword.operator.bitwise.julia" - }, - { - "match": "(?:\\+\\+|--|\\+|\\.\\+|-|\\.\\-|\\*|\\.\\*|//(?!=)|\\.//(?!=)|/|\\./|%|\\.%|\\\\|\\.\\\\|\\^|\\.\\^|÷|\\.÷|â‹…|\\.â‹…|∩|\\.∩|∪|\\.∪|×|√|∛)", - "name": "keyword.operator.arithmetic.julia" - }, - { - "match": "(?:∘)", - "name": "keyword.operator.compose.julia" - }, - { - "match": "(?:::|(?<=\\s)isa(?=\\s))", - "name": "keyword.operator.isa.julia" - }, - { - "match": "(?:(?<=\\s)in(?=\\s))", - "name": "keyword.operator.relation.in.julia" - }, - { - "match": "(?:\\.(?=(?:@|_|\\p{L}))|\\.\\.+)", - "name": "keyword.operator.dots.julia" - }, - { - "match": "(?:\\$)(?=.+)", - "name": "keyword.operator.interpolation.julia" - }, - { - "captures": { - "2": { - "name": "keyword.operator.transposed-variable.julia" - } - }, - "match": "((?:[[:alpha:]_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{So}â†-⇿])(?:[[:word:]_!\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{Mn}\u0001-¡]|[^\\P{Mc}\u0001-¡]|[^\\P{Nd}\u0001-¡]|[^\\P{Pc}\u0001-¡]|[^\\P{Sk}\u0001-¡]|[^\\P{Me}\u0001-¡]|[^\\P{No}\u0001-¡]|[′-‷â—]|[^\\P{So}â†-⇿])*)(('|(\\.'))*\\.?')" - }, - { - "captures": { - "1": { - "name": "bracket.end.julia" - }, - "2": { - "name": "keyword.operator.transposed-matrix.julia" - } - }, - "match": "(\\])((?:'|(?:\\.'))*\\.?')" - }, - { - "captures": { - "1": { - "name": "bracket.end.julia" - }, - "2": { - "name": "keyword.operator.transposed-parens.julia" - } - }, - "match": "(\\))((?:'|(?:\\.'))*\\.?')" - } - ] - }, - "string": { - "patterns": [ - { - "begin": "(?:(@doc)\\s((?:doc)?\"\"\")|(doc\"\"\"))", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "(\"\"\") ?(->)?", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.julia" - }, - "2": { - "name": "keyword.operator.arrow.julia" - } - }, - "name": "string.docstring.julia", - "patterns": [ - { - "include": "#string_escaped_char" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "begin": "(i?cxx)(\"\"\")", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "\"\"\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.julia" - } - }, - "name": "embed.cxx.julia", - "contentName": "meta.embedded.inline.cpp", - "patterns": [ - { - "include": "source.cpp#root_context" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "begin": "(py)(\"\"\")", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "([\\s\\w]*)(\"\"\")", - "endCaptures": { - "2": { - "name": "punctuation.definition.string.end.julia" - } - }, - "name": "embed.python.julia", - "contentName": "meta.embedded.inline.python", - "patterns": [ - { - "include": "source.python" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "begin": "(js)(\"\"\")", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "\"\"\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.julia" - } - }, - "name": "embed.js.julia", - "contentName": "meta.embedded.inline.javascript", - "patterns": [ - { - "include": "source.js" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "begin": "(R)(\"\"\")", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "\"\"\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.julia" - } - }, - "name": "embed.R.julia", - "contentName": "meta.embedded.inline.r", - "patterns": [ - { - "include": "source.r" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "begin": "(raw)(\"\"\")", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "\"\"\"", - "name": "string.quoted.other.julia", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.julia" - } - } - }, - { - "begin": "(raw)(\")", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "\"", - "name": "string.quoted.other.julia", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.julia" - } - } - }, - { - "begin": "(sql)(\"\"\")", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "\"\"\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.julia" - } - }, - "name": "embed.sql.julia", - "contentName": "meta.embedded.inline.sql", - "patterns": [ - { - "include": "source.sql" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "begin": "var\"\"\"", - "end": "\"\"\"", - "name": "constant.other.symbol.julia" - }, - { - "begin": "var\"", - "end": "\"", - "name": "constant.other.symbol.julia" - }, - { - "begin": "^\\s?(doc)?(\"\"\")\\s?$", - "beginCaptures": { - "1": { - "name": "support.function.macro.julia" - }, - "2": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "(\"\"\")", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.end.julia" - } - }, - "name": "string.docstring.julia", - "comment": "This only matches docstrings that start and end with triple quotes on\ntheir own line in the void", - "patterns": [ - { - "include": "#string_escaped_char" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "begin": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "'(?!')", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.julia" - } - }, - "name": "string.quoted.single.julia", - "patterns": [ - { - "include": "#string_escaped_char" - } - ] - }, - { - "begin": "\"\"\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.multiline.begin.julia" - } - }, - "end": "\"\"\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.multiline.end.julia" - } - }, - "name": "string.quoted.triple.double.julia", - "comment": "multi-line string with triple double quotes", - "patterns": [ - { - "include": "#string_escaped_char" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "name": "string.quoted.double.julia", - "begin": "\"(?!\"\")", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.julia" - } - }, - "end": "\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.julia" - } - }, - "comment": "String with single pair of double quotes. Regex matches isolated double quote", - "patterns": [ - { - "include": "#string_escaped_char" - }, - { - "include": "#string_dollar_sign_interpolate" - } - ] - }, - { - "begin": "r\"\"\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.regexp.begin.julia" - } - }, - "end": "(\"\"\")([imsx]{0,4})?", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.regexp.end.julia" - }, - "2": { - "comment": "I took this scope name from python regex grammar", - "name": "keyword.other.option-toggle.regexp.julia" - } - }, - "name": "string.regexp.julia", - "patterns": [ - { - "include": "#string_escaped_char" - } - ] - }, - { - "begin": "r\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.regexp.begin.julia" - } - }, - "end": "(\")([imsx]{0,4})?", - "endCaptures": { - "1": { - "name": "punctuation.definition.string.regexp.end.julia" - }, - "2": { - "comment": "I took this scope name from python regex grammar", - "name": "keyword.other.option-toggle.regexp.julia" - } - }, - "name": "string.regexp.julia", - "patterns": [ - { - "include": "#string_escaped_char" - } - ] - }, - { - "begin": "(?!:_)(?:struct|mutable\\s+struct|abstract\\s+type|primitive\\s+type)\\s+((?:[[:alpha:]_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{So}â†-⇿])(?:[[:word:]_!\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{Mn}\u0001-¡]|[^\\P{Mc}\u0001-¡]|[^\\P{Nd}\u0001-¡]|[^\\P{Pc}\u0001-¡]|[^\\P{Sk}\u0001-¡]|[^\\P{Me}\u0001-¡]|[^\\P{No}\u0001-¡]|[′-‷â—]|[^\\P{So}â†-⇿])*)(\\s*(<:)\\s*(?:[[:alpha:]_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{So}â†-⇿])(?:[[:word:]_!\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\p{Sc}â…€-⅄∿⊾⊿⊤⊥∂∅-∇∎âˆâˆâˆ‘∞∟∫-∳⋀-⋃◸-◿♯⟘⟙⟀âŸâ¦°-⦴⨀-⨆⨉-⨖⨛⨜ð›ð››ð›»ðœ•ðœµðð¯ðž‰ðž©ðŸƒâ±-â¾â‚-₎∠-∢⦛-⦯℘℮゛-゜ðŸŽ-ðŸ¡]|[^\\P{Mn}\u0001-¡]|[^\\P{Mc}\u0001-¡]|[^\\P{Nd}\u0001-¡]|[^\\P{Pc}\u0001-¡]|[^\\P{Sk}\u0001-¡]|[^\\P{Me}\u0001-¡]|[^\\P{No}\u0001-¡]|[′-‷â—]|[^\\P{So}â†-⇿])*(?:{.*})?)?", - "name": "meta.type.julia" - } - ] - } - } -} \ No newline at end of file diff --git a/extensions/kusto/.vscodeignore b/extensions/kusto/.vscodeignore index 777c123f0d..0afa2d2d54 100644 --- a/extensions/kusto/.vscodeignore +++ b/extensions/kusto/.vscodeignore @@ -1,8 +1,13 @@ +build/** src/** +web/** +test/** +test-workspace/** out/** tsconfig.json extension.webpack.config.js extension-browser.webpack.config.js +cgmanifest.json yarn.lock .gitignore .vscode diff --git a/extensions/mangle-loader.js b/extensions/mangle-loader.js new file mode 100644 index 0000000000..24267320e2 --- /dev/null +++ b/extensions/mangle-loader.js @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const fs = require('fs'); +const webpack = require('webpack'); +const fancyLog = require('fancy-log'); +const ansiColors = require('ansi-colors'); +const { Mangler } = require('../build/lib/mangleTypeScript'); + +/** + * Map of project paths to mangled file contents + * + * @type {Map>} + */ +const mangleMap = new Map(); + +/** + * @param {string} projectPath + */ +function getMangledFileContents(projectPath) { + let entry = mangleMap.get(projectPath); + if (!entry) { + const log = (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data); + log(`Mangling ${projectPath}`); + const ts2tsMangler = new Mangler(projectPath, log); + entry = ts2tsMangler.computeNewFileContents(); + mangleMap.set(projectPath, entry); + } + + return entry; +} + +/** + * @type {webpack.LoaderDefinitionFunction} + */ +module.exports = async function (source, sourceMap, meta) { + return source; // {{SQL CARBON EDIT}} skip mangling +}; diff --git a/extensions/markdown-basics/cgmanifest.json b/extensions/markdown-basics/cgmanifest.json index 46b4bae228..d914bf08dc 100644 --- a/extensions/markdown-basics/cgmanifest.json +++ b/extensions/markdown-basics/cgmanifest.json @@ -33,7 +33,7 @@ "git": { "name": "microsoft/vscode-markdown-tm-grammar", "repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar", - "commitHash": "69d3321b4923ca2d5e8e900018887cc38b5fe04a" + "commitHash": "ca2caf2157d0674be3d641f71499b84d514e4e5e" } }, "license": "MIT", diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index 69e4da10d2..9021acac87 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -93,7 +93,8 @@ "configurationDefaults": { "[markdown]": { "editor.unicodeHighlight.ambiguousCharacters": false, - "editor.unicodeHighlight.invisibleCharacters": false + "editor.unicodeHighlight.invisibleCharacters": false, + "diffEditor.ignoreTrimWhitespace": false } } }, diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index 895836a188..57e14e862a 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/69d3321b4923ca2d5e8e900018887cc38b5fe04a", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/ca2caf2157d0674be3d641f71499b84d514e4e5e", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -63,7 +63,7 @@ "while": "(^|\\G)\\s*(>) ?" }, "fenced_code_block_css": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(css|css.erb)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -96,7 +96,7 @@ ] }, "fenced_code_block_basic": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(html|htm|shtml|xhtml|inc|tmpl|tpl)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(html|htm|shtml|xhtml|inc|tmpl|tpl)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -129,7 +129,7 @@ ] }, "fenced_code_block_ini": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ini|conf)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ini|conf)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -162,7 +162,7 @@ ] }, "fenced_code_block_java": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(java|bsh)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(java|bsh)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -195,7 +195,7 @@ ] }, "fenced_code_block_lua": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(lua)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(lua)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -228,7 +228,7 @@ ] }, "fenced_code_block_makefile": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(Makefile|makefile|GNUmakefile|OCamlMakefile)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(Makefile|makefile|GNUmakefile|OCamlMakefile)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -261,7 +261,7 @@ ] }, "fenced_code_block_perl": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl|pl|pm|pod|t|PL|psgi|vcl)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl|pl|pm|pod|t|PL|psgi|vcl)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -294,7 +294,7 @@ ] }, "fenced_code_block_r": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(R|r|s|S|Rprofile|\\{\\.r.+?\\})((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(R|r|s|S|Rprofile|\\{\\.r.+?\\})((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -327,7 +327,7 @@ ] }, "fenced_code_block_ruby": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(ruby|rb|rbx|rjs|Rakefile|rake|cgi|fcgi|gemspec|irbrc|Capfile|ru|prawn|Cheffile|Gemfile|Guardfile|Hobofile|Vagrantfile|Appraisals|Rantfile|Berksfile|Berksfile.lock|Thorfile|Puppetfile)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -360,7 +360,7 @@ ] }, "fenced_code_block_php": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(php|php3|php4|php5|phpt|phtml|aw|ctp)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(php|php3|php4|php5|phpt|phtml|aw|ctp)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -396,7 +396,7 @@ ] }, "fenced_code_block_sql": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(sql|ddl|dml)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(sql|ddl|dml)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -429,7 +429,7 @@ ] }, "fenced_code_block_vs_net": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(vb)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(vb)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -462,7 +462,7 @@ ] }, "fenced_code_block_xml": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xml|xsd|tld|jsp|pt|cpt|dtml|rss|opml)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -495,7 +495,7 @@ ] }, "fenced_code_block_xsl": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xsl|xslt)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(xsl|xslt)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -528,7 +528,7 @@ ] }, "fenced_code_block_yaml": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yaml|yml)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yaml|yml)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -561,7 +561,7 @@ ] }, "fenced_code_block_dosbatch": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bat|batch)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bat|batch)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -594,7 +594,7 @@ ] }, "fenced_code_block_clojure": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(clj|cljs|clojure)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(clj|cljs|clojure)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -627,7 +627,7 @@ ] }, "fenced_code_block_coffee": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(coffee|Cakefile|coffee.erb)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(coffee|Cakefile|coffee.erb)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -660,7 +660,7 @@ ] }, "fenced_code_block_c": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(c|h)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(c|h)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -693,7 +693,7 @@ ] }, "fenced_code_block_cpp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cpp|c\\+\\+|cxx)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cpp|c\\+\\+|cxx)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -726,7 +726,7 @@ ] }, "fenced_code_block_diff": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(patch|diff|rej)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(patch|diff|rej)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -759,7 +759,7 @@ ] }, "fenced_code_block_dockerfile": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dockerfile|Dockerfile)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dockerfile|Dockerfile)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -792,7 +792,7 @@ ] }, "fenced_code_block_git_commit": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(COMMIT_EDITMSG|MERGE_MSG)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(COMMIT_EDITMSG|MERGE_MSG)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -825,7 +825,7 @@ ] }, "fenced_code_block_git_rebase": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(git-rebase-todo)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(git-rebase-todo)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -858,7 +858,7 @@ ] }, "fenced_code_block_go": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(go|golang)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(go|golang)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -891,7 +891,7 @@ ] }, "fenced_code_block_groovy": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(groovy|gvy)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(groovy|gvy)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -924,7 +924,7 @@ ] }, "fenced_code_block_pug": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jade|pug)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jade|pug)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -957,7 +957,7 @@ ] }, "fenced_code_block_js": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|cjs|dataviewjs|\\{\\.js.+?\\})((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|cjs|dataviewjs|\\{\\.js.+?\\})((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -990,7 +990,7 @@ ] }, "fenced_code_block_js_regexp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(regexp)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(regexp)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1023,7 +1023,7 @@ ] }, "fenced_code_block_json": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(json|json5|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(json|json5|sublime-settings|sublime-menu|sublime-keymap|sublime-mousemap|sublime-theme|sublime-build|sublime-project|sublime-completions)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1056,7 +1056,7 @@ ] }, "fenced_code_block_jsonc": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonc)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonc)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1089,7 +1089,7 @@ ] }, "fenced_code_block_less": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1122,7 +1122,7 @@ ] }, "fenced_code_block_objc": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(objectivec|objective-c|mm|objc|obj-c|m|h)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(objectivec|objective-c|mm|objc|obj-c|m|h)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1155,7 +1155,7 @@ ] }, "fenced_code_block_swift": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(swift)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(swift)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1188,7 +1188,7 @@ ] }, "fenced_code_block_scss": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scss)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scss)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1221,7 +1221,7 @@ ] }, "fenced_code_block_perl6": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl6|p6|pl6|pm6|nqp)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(perl6|p6|pl6|pm6|nqp)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1254,7 +1254,7 @@ ] }, "fenced_code_block_powershell": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(powershell|ps1|psm1|psd1)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(powershell|ps1|psm1|psd1)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1287,7 +1287,7 @@ ] }, "fenced_code_block_python": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi|\\{\\.python.+?\\})((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(python|py|py3|rpy|pyw|cpy|SConstruct|Sconstruct|sconstruct|SConscript|gyp|gypi|\\{\\.python.+?\\})((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1320,7 +1320,7 @@ ] }, "fenced_code_block_julia": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(julia|\\{\\.julia.+?\\})((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(julia|\\{\\.julia.+?\\})((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1353,7 +1353,7 @@ ] }, "fenced_code_block_regexp_python": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(re)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(re)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1386,7 +1386,7 @@ ] }, "fenced_code_block_rust": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rust|rs|\\{\\.rust.+?\\})((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(rust|rs|\\{\\.rust.+?\\})((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1419,7 +1419,7 @@ ] }, "fenced_code_block_scala": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scala|sbt)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(scala|sbt)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1452,7 +1452,7 @@ ] }, "fenced_code_block_shell": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init|\\{\\.bash.+?\\})((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(shell|sh|bash|zsh|bashrc|bash_profile|bash_login|profile|bash_logout|.textmate_init|\\{\\.bash.+?\\})((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1485,7 +1485,7 @@ ] }, "fenced_code_block_ts": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(typescript|ts)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(typescript|ts)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1518,7 +1518,7 @@ ] }, "fenced_code_block_tsx": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(tsx)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(tsx)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1551,7 +1551,7 @@ ] }, "fenced_code_block_csharp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cs|csharp|c#)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(cs|csharp|c#)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1584,7 +1584,7 @@ ] }, "fenced_code_block_fsharp": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(fs|fsharp|f#)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(fs|fsharp|f#)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1617,7 +1617,7 @@ ] }, "fenced_code_block_dart": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dart)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(dart)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1650,7 +1650,7 @@ ] }, "fenced_code_block_handlebars": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(handlebars|hbs)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(handlebars|hbs)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1683,7 +1683,7 @@ ] }, "fenced_code_block_markdown": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(markdown|md)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(markdown|md)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1716,7 +1716,7 @@ ] }, "fenced_code_block_log": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(log)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1749,7 +1749,7 @@ ] }, "fenced_code_block_erlang": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(erlang)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(erlang)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1782,7 +1782,7 @@ ] }, "fenced_code_block_elixir": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(elixir)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(elixir)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1815,7 +1815,7 @@ ] }, "fenced_code_block_latex": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(latex|tex)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(latex|tex)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1848,7 +1848,7 @@ ] }, "fenced_code_block_bibtex": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bibtex)((\\s+|:|,|\\{|\\?)[^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(bibtex)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", "beginCaptures": { @@ -1880,6 +1880,39 @@ } ] }, + "fenced_code_block_twig": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(twig)((\\s+|:|,|\\{|\\?)[^`]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.twig", + "patterns": [ + { + "include": "source.twig" + } + ] + } + ] + }, "fenced_code_block": { "patterns": [ { @@ -2047,13 +2080,16 @@ { "include": "#fenced_code_block_bibtex" }, + { + "include": "#fenced_code_block_twig" + }, { "include": "#fenced_code_block_unknown" } ] }, "fenced_code_block_unknown": { - "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?=([^`~]*)?$)", + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?=([^`]*)?$)", "beginCaptures": { "3": { "name": "punctuation.definition.markdown" @@ -2766,7 +2802,24 @@ "name": "punctuation.definition.link.title.begin.markdown" }, "2": { - "name": "string.other.link.title.markdown" + "name": "string.other.link.title.markdown", + "patterns": [ + { + "include": "#raw" + }, + { + "include": "#bold" + }, + { + "include": "#italic" + }, + { + "include": "#strikethrough" + }, + { + "include": "#image-inline" + } + ] }, "4": { "name": "punctuation.definition.link.title.end.markdown" @@ -2826,7 +2879,24 @@ "name": "punctuation.definition.link.title.begin.markdown" }, "2": { - "name": "string.other.link.title.markdown" + "name": "string.other.link.title.markdown", + "patterns": [ + { + "include": "#raw" + }, + { + "include": "#bold" + }, + { + "include": "#italic" + }, + { + "include": "#strikethrough" + }, + { + "include": "#image-inline" + } + ] }, "4": { "name": "punctuation.definition.link.title.end.markdown" @@ -2957,7 +3027,7 @@ "name": "punctuation.definition.strikethrough.markdown" } }, - "match": "(~{2,})((?:[^~]|(?!(? = (ctx) => { code { font-size: 1em; + font-family: var(--vscode-editor-font-family); } pre code { - font-family: var(--vscode-editor-font-family); - line-height: 1.357em; white-space: pre-wrap; } @@ -378,7 +393,7 @@ function slugify(text: string): string { .toLowerCase() .replace(/\s+/g, '-') // Replace whitespace with - // allow-any-unicode-next-line - .replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~\`。,ã€ï¼›ï¼šï¼Ÿï¼â€¦â€”·ˉ¨‘’“â€ã€…~‖∶"'`|〃〔〕〈〉《》「ã€ã€Žã€ï¼Žã€–〗ã€ã€‘()[]{ï½]/g, '') // Remove known punctuators + .replace(/[\]\[\!\/\'\"\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`。,ã€ï¼›ï¼šï¼Ÿï¼â€¦â€”·ˉ¨‘’“â€ã€…~‖∶"'`|〃〔〕〈〉《》「ã€ã€Žã€ï¼Žã€–〗ã€ã€‘()[]{ï½]/g, '') // Remove known punctuators .replace(/^\-+/, '') // Remove leading - .replace(/\-+$/, '') // Remove trailing - ); diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 8be3817687..8240a451ea 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -8,7 +8,7 @@ "license": "MIT", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { - "vscode": "^1.20.0" + "vscode": "^1.70.0" }, "main": "./out/extension", "browser": "./dist/browser/extension", @@ -17,22 +17,13 @@ ], "enabledApiProposals": [ "documentPaste", - "notebookEditor" + "dropMetadata" ], "activationEvents": [ "onLanguage:markdown", - "onCommand:markdown.preview.toggleLock", - "onCommand:markdown.preview.refresh", - "onCommand:markdown.showPreview", - "onCommand:markdown.showPreviewToSide", - "onCommand:markdown.showLockedPreviewToSide", - "onCommand:markdown.showSource", - "onCommand:markdown.showPreviewSecuritySelector", "onCommand:markdown.api.render", "onCommand:markdown.api.reloadPlugins", - "onCommand:markdown.findAllFileReferences", - "onWebviewPanel:markdown.preview", - "onCustomEditor:vscode.markdown.preview.editor" + "onWebviewPanel:markdown.preview" ], "capabilities": { "virtualWorkspaces": true, @@ -176,6 +167,18 @@ "command": "markdown.findAllFileReferences", "title": "%markdown.findAllFileReferences%", "category": "Markdown" + }, + { + "command": "markdown.editor.insertLinkFromWorkspace", + "title": "%markdown.editor.insertLinkFromWorkspace%", + "category": "Markdown", + "enablement": "editorLangId == markdown && !activeEditorIsReadonly" + }, + { + "command": "markdown.editor.insertImageFromWorkspace", + "title": "%markdown.editor.insertImageFromWorkspace%", + "category": "Markdown", + "enablement": "editorLangId == markdown && !activeEditorIsReadonly" } ], "menus": { @@ -188,22 +191,22 @@ }, { "command": "markdown.showSource", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "navigation" }, { "command": "markdown.preview.refresh", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "1_markdown" }, { "command": "markdown.preview.toggleLock", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "1_markdown" }, { "command": "markdown.showPreviewSecuritySelector", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "1_markdown" } ], @@ -248,7 +251,7 @@ }, { "command": "markdown.showSource", - "when": "markdownPreviewFocus", + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'", "group": "navigation" }, { @@ -257,11 +260,11 @@ }, { "command": "markdown.showPreviewSecuritySelector", - "when": "markdownPreviewFocus" + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'" }, { "command": "markdown.preview.toggleLock", - "when": "markdownPreviewFocus" + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'" }, { "command": "markdown.preview.refresh", @@ -269,7 +272,7 @@ }, { "command": "markdown.preview.refresh", - "when": "markdownPreviewFocus" + "when": "activeWebviewPanelId == 'markdown.preview' || activeCustomEditorId == 'vscode.markdown.preview.editor'" }, { "command": "markdown.findAllFileReferences", @@ -399,6 +402,22 @@ "description": "%configuration.markdown.suggest.paths.enabled.description%", "scope": "resource" }, + "markdown.suggest.paths.includeWorkspaceHeaderCompletions": { + "type": "string", + "default": "onDoubleHash", + "scope": "resource", + "markdownDescription": "%configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions%", + "enum": [ + "never", + "onDoubleHash", + "onSingleOrDoubleHash" + ], + "markdownEnumDescriptions": [ + "%configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.never%", + "%configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.onDoubleHash%", + "%configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.onSingleOrDoubleHash%" + ] + }, "markdown.trace.extension": { "type": "string", "enum": [ @@ -420,95 +439,211 @@ "default": "off", "description": "%markdown.trace.server.desc%" }, + "markdown.server.log": { + "type": "string", + "scope": "window", + "enum": [ + "off", + "debug", + "trace" + ], + "default": "off", + "description": "%markdown.server.log.desc%" + }, "markdown.editor.drop.enabled": { "type": "boolean", "default": true, "markdownDescription": "%configuration.markdown.editor.drop.enabled%", "scope": "resource" }, - "markdown.experimental.editor.pasteLinks.enabled": { + "markdown.editor.drop.copyIntoWorkspace": { + "type": "string", + "markdownDescription": "%configuration.markdown.editor.drop.copyIntoWorkspace%", + "default": "mediaFiles", + "enum": [ + "mediaFiles", + "never" + ], + "markdownEnumDescriptions": [ + "%configuration.copyIntoWorkspace.mediaFiles%", + "%configuration.copyIntoWorkspace.never%" + ] + }, + "markdown.editor.filePaste.enabled": { "type": "boolean", "scope": "resource", - "markdownDescription": "%configuration.markdown.editor.pasteLinks.enabled%", - "default": true, - "tags": [ - "experimental" + "markdownDescription": "%configuration.markdown.editor.filePaste.enabled%", + "default": true + }, + "markdown.editor.filePaste.copyIntoWorkspace": { + "type": "string", + "markdownDescription": "%configuration.markdown.editor.filePaste.copyIntoWorkspace%", + "default": "mediaFiles", + "enum": [ + "mediaFiles", + "never" + ], + "markdownEnumDescriptions": [ + "%configuration.copyIntoWorkspace.mediaFiles%", + "%configuration.copyIntoWorkspace.never%" ] }, - "markdown.experimental.validate.enabled": { + "markdown.validate.enabled": { "type": "boolean", "scope": "resource", - "description": "%configuration.markdown.experimental.validate.enabled.description%", - "default": false, - "tags": [ - "experimental" - ] + "description": "%configuration.markdown.validate.enabled.description%", + "default": false }, - "markdown.experimental.validate.referenceLinks.enabled": { + "markdown.validate.referenceLinks.enabled": { "type": "string", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.referenceLinks.enabled.description%", + "markdownDescription": "%configuration.markdown.validate.referenceLinks.enabled.description%", "default": "warning", "enum": [ "ignore", "warning", "error" - ], - "tags": [ - "experimental" ] }, - "markdown.experimental.validate.fragmentLinks.enabled": { + "markdown.validate.fragmentLinks.enabled": { "type": "string", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.fragmentLinks.enabled.description%", + "markdownDescription": "%configuration.markdown.validate.fragmentLinks.enabled.description%", "default": "warning", "enum": [ "ignore", "warning", "error" - ], - "tags": [ - "experimental" ] }, - "markdown.experimental.validate.fileLinks.enabled": { + "markdown.validate.fileLinks.enabled": { "type": "string", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.enabled.description%", + "markdownDescription": "%configuration.markdown.validate.fileLinks.enabled.description%", "default": "warning", "enum": [ "ignore", "warning", "error" - ], - "tags": [ - "experimental" ] }, - "markdown.experimental.validate.fileLinks.markdownFragmentLinks": { + "markdown.validate.fileLinks.markdownFragmentLinks": { "type": "string", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description%", - "default": "ignore", + "markdownDescription": "%configuration.markdown.validate.fileLinks.markdownFragmentLinks.description%", + "default": "inherit", "enum": [ + "inherit", "ignore", "warning", "error" - ], - "tags": [ - "experimental" ] }, - "markdown.experimental.validate.ignoreLinks": { + "markdown.validate.ignoredLinks": { "type": "array", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.ignoreLinks.description%", + "markdownDescription": "%configuration.markdown.validate.ignoredLinks.description%", "items": { "type": "string" + } + }, + "markdown.validate.unusedLinkDefinitions.enabled": { + "type": "string", + "scope": "resource", + "markdownDescription": "%configuration.markdown.validate.unusedLinkDefinitions.description%", + "default": "hint", + "enum": [ + "ignore", + "hint", + "warning", + "error" + ] + }, + "markdown.validate.duplicateLinkDefinitions.enabled": { + "type": "string", + "scope": "resource", + "markdownDescription": "%configuration.markdown.validate.duplicateLinkDefinitions.description%", + "default": "warning", + "enum": [ + "ignore", + "warning", + "error" + ] + }, + "markdown.updateLinksOnFileMove.enabled": { + "type": "string", + "enum": [ + "prompt", + "always", + "never" + ], + "markdownEnumDescriptions": [ + "%configuration.markdown.updateLinksOnFileMove.enabled.prompt%", + "%configuration.markdown.updateLinksOnFileMove.enabled.always%", + "%configuration.markdown.updateLinksOnFileMove.enabled.never%" + ], + "default": "never", + "markdownDescription": "%configuration.markdown.updateLinksOnFileMove.enabled%", + "scope": "window" + }, + "markdown.updateLinksOnFileMove.include": { + "type": "array", + "markdownDescription": "%configuration.markdown.updateLinksOnFileMove.include%", + "scope": "window", + "items": { + "type": "string", + "description": "%configuration.markdown.updateLinksOnFileMove.include.property%" }, - "tags": [ - "experimental" + "default": [ + "**/*.{md,mkd,mdwn,mdown,markdown,markdn,mdtxt,mdtext,workbook}", + "**/*.{jpg,jpe,jpeg,png,bmp,gif,ico,webp,avif,tiff,svg,mp4}" + ] + }, + "markdown.updateLinksOnFileMove.enableForDirectories": { + "type": "boolean", + "default": true, + "description": "%configuration.markdown.updateLinksOnFileMove.enableForDirectories%", + "scope": "window" + }, + "markdown.occurrencesHighlight.enabled": { + "type": "boolean", + "default": false, + "description": "%configuration.markdown.occurrencesHighlight.enabled%", + "scope": "resource" + }, + "markdown.copyFiles.destination": { + "type": "object", + "markdownDescription": "%configuration.markdown.copyFiles.destination%", + "additionalProperties": { + "type": "string" + } + }, + "markdown.copyFiles.overwriteBehavior": { + "type": "string", + "markdownDescription": "%configuration.markdown.copyFiles.overwriteBehavior%", + "default": "nameIncrementally", + "enum": [ + "nameIncrementally", + "overwrite" + ], + "markdownEnumDescriptions": [ + "%configuration.markdown.copyFiles.overwriteBehavior.nameIncrementally%", + "%configuration.markdown.copyFiles.overwriteBehavior.overwrite%" + ] + }, + "markdown.preferredMdPathExtensionStyle": { + "type": "string", + "default": "auto", + "markdownDescription": "%configuration.markdown.preferredMdPathExtensionStyle%", + "enum": [ + "auto", + "includeExtension", + "removeExtension" + ], + "markdownEnumDescriptions": [ + "%configuration.markdown.preferredMdPathExtensionStyle.auto%", + "%configuration.markdown.preferredMdPathExtensionStyle.includeExtension%", + "%configuration.markdown.preferredMdPathExtensionStyle.removeExtension%" ] } } @@ -516,7 +651,11 @@ "configurationDefaults": { "[markdown]": { "editor.wordWrap": "on", - "editor.quickSuggestions": false + "editor.quickSuggestions": { + "comments": "off", + "strings": "off", + "other": "off" + } } }, "jsonValidation": [ @@ -556,16 +695,14 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "0.6.2", - "dompurify": "^2.3.3", + "@vscode/extension-telemetry": "0.7.5", + "dompurify": "^2.4.1", "highlight.js": "^11.4.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.1", "morphdom": "^2.6.1", "picomatch": "^2.3.1", - "vscode-languageclient": "^8.0.1", - "vscode-languageserver-textdocument": "^1.0.4", - "vscode-nls": "^5.0.0", + "vscode-languageclient": "^8.0.2", "vscode-uri": "^3.0.3" }, "devDependencies": { @@ -577,7 +714,7 @@ "@types/vscode-webview": "^1.57.0", "lodash.throttle": "^4.1.1", "vscode-languageserver-types": "^3.17.2", - "vscode-markdown-languageservice": "^0.0.0-alpha.10" + "vscode-markdown-languageservice": "^0.3.0-alpha.3" }, "repository": { "type": "git", diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 1c32e447e0..8049ad9447 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -2,9 +2,9 @@ "displayName": "Markdown Language Features", "description": "Provides rich language support for Markdown.", "markdown.preview.breaks.desc": "Sets how line-breaks are rendered in the Markdown preview. Setting it to 'true' creates a
for newlines inside paragraphs.", - "markdown.preview.linkify": "Enable or disable conversion of URL-like text to links in the Markdown preview.", - "markdown.preview.typographer": "Enable or disable some language-neutral replacement and quotes beautification in the Markdown preview.", - "markdown.preview.doubleClickToSwitchToEditor.desc": "Double click in the Markdown preview to switch to the editor.", + "markdown.preview.linkify": "Convert URL-like text to links in the Markdown preview.", + "markdown.preview.typographer": "Enable some language-neutral replacement and quotes beautification in the Markdown preview.", + "markdown.preview.doubleClickToSwitchToEditor.desc": "Double-click in the Markdown preview to switch to the editor.", "markdown.preview.fontFamily.desc": "Controls the font family used in the Markdown preview.", "markdown.preview.fontSize.desc": "Controls the font size in pixels used in the Markdown preview.", "markdown.preview.lineHeight.desc": "Controls the line height used in the Markdown preview. This number is relative to the font size.", @@ -19,23 +19,52 @@ "markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings", "markdown.trace.extension.desc": "Enable debug logging for the Markdown extension.", "markdown.trace.server.desc": "Traces the communication between VS Code and the Markdown language server.", + "markdown.server.log.desc": "Controls the logging level of the Markdown language server.", "markdown.preview.refresh.title": "Refresh Preview", "markdown.preview.toggleLock.title": "Toggle Preview Locking", "markdown.findAllFileReferences": "Find File References", + "markdown.editor.insertLinkFromWorkspace": "Insert Link to File in Workspace", + "markdown.editor.insertImageFromWorkspace": "Insert Image from Workspace", "configuration.markdown.preview.openMarkdownLinks.description": "Controls how links to other Markdown files in the Markdown preview should be opened.", "configuration.markdown.preview.openMarkdownLinks.inEditor": "Try to open links in the editor.", "configuration.markdown.preview.openMarkdownLinks.inPreview": "Try to open links in the Markdown preview.", "configuration.markdown.links.openLocation.description": "Controls where links in Markdown files should be opened.", "configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.", "configuration.markdown.links.openLocation.beside": "Open links beside the active editor.", - "configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links", - "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#editor.dropIntoEditor.enabled#`.", - "configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links. Requires enabling `#editor.experimental.pasteActions.enabled#`.", - "configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.", - "configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", - "configuration.markdown.experimental.validate.fragmentLinks.enabled.description": "Validate fragment links to headers in the current Markdown file, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.", - "configuration.markdown.experimental.validate.fileLinks.enabled.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.", - "configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description": "Validate the fragment part of links to headers in other files in Markdown files, e.g. `[link](/path/to/file.md#header)`. Inherits the setting value from `#markdown.experimental.validate.fragmentLinks.enabled#` by default.", - "configuration.markdown.experimental.validate.ignoreLinks.description": "Configure links that should not be validated. For example `/about` would not validate the link `[about](/about)`, while the glob `/assets/**/*.svg` would let you skip validation for any link to `.svg` files under the `assets` directory.", + "configuration.markdown.suggest.paths.enabled.description": "Enable path suggestions while writing links in Markdown files.", + "configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions": "Enable suggestions for headers in other Markdown files in the current workspace. Accepting one of these suggestions inserts the full path to header in that file, for example: `[link text](/path/to/file.md#header)`.", + "configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.never": "Disable workspace header suggestions.", + "configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.onDoubleHash": "Enable workspace header suggestions after typing `##` in a path, for example: `[link text](##`.", + "configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.onSingleOrDoubleHash": "Enable workspace header suggestions after typing either `##` or `#` in a path, for example: `[link text](#` or `[link text](##`.", + "configuration.markdown.editor.drop.enabled": "Enable dropping files into a Markdown editor while holding Shift. Requires enabling `#editor.dropIntoEditor.enabled#`.", + "configuration.markdown.editor.drop.copyIntoWorkspace": "Controls if files outside of the workspace that are dropped into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied dropped files should be created", + "configuration.markdown.editor.filePaste.enabled": "Enable pasting files into a Markdown editor to create Markdown links. Requires enabling `#editor.pasteAs.enabled#`.", + "configuration.markdown.editor.filePaste.copyIntoWorkspace": "Controls if files outside of the workspace that are pasted into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied files should be created.", + "configuration.copyIntoWorkspace.mediaFiles": "Try to copy external image and video files into the workspace.", + "configuration.copyIntoWorkspace.never": "Do not copy external files into the workspace.", + "configuration.markdown.validate.enabled.description": "Enable all error reporting in Markdown files.", + "configuration.markdown.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, for example: `[link][ref]`. Requires enabling `#markdown.validate.enabled#`.", + "configuration.markdown.validate.fragmentLinks.enabled.description": "Validate fragment links to headers in the current Markdown file, for example: `[link](#header)`. Requires enabling `#markdown.validate.enabled#`.", + "configuration.markdown.validate.fileLinks.enabled.description": "Validate links to other files in Markdown files, for example `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.validate.enabled#`.", + "configuration.markdown.validate.fileLinks.markdownFragmentLinks.description": "Validate the fragment part of links to headers in other files in Markdown files, for example: `[link](/path/to/file.md#header)`. Inherits the setting value from `#markdown.validate.fragmentLinks.enabled#` by default.", + "configuration.markdown.validate.ignoredLinks.description": "Configure links that should not be validated. For example adding `/about` would not validate the link `[about](/about)`, while the glob `/assets/**/*.svg` would let you skip validation for any link to `.svg` files under the `assets` directory.", + "configuration.markdown.validate.unusedLinkDefinitions.description": "Validate link definitions that are unused in the current file.", + "configuration.markdown.validate.duplicateLinkDefinitions.description": "Validate duplicated definitions in the current file.", + "configuration.markdown.updateLinksOnFileMove.enabled": "Try to update links in Markdown files when a file is renamed/moved in the workspace. Use `#markdown.updateLinksOnFileMove.include#` to configure which files trigger link updates.", + "configuration.markdown.updateLinksOnFileMove.enabled.prompt": "Prompt on each file move.", + "configuration.markdown.updateLinksOnFileMove.enabled.always": "Always update links automatically.", + "configuration.markdown.updateLinksOnFileMove.enabled.never": "Never try to update link and don't prompt.", + "configuration.markdown.updateLinksOnFileMove.include": "Glob patterns that specifies files that trigger automatic link updates. See `#markdown.updateLinksOnFileMove.enabled#` for details about this feature.", + "configuration.markdown.updateLinksOnFileMove.include.property": "The glob pattern to match file paths against. Set to true to enable the pattern.", + "configuration.markdown.updateLinksOnFileMove.enableForDirectories": "Enable updating links when a directory is moved or renamed in the workspace.", + "configuration.markdown.occurrencesHighlight.enabled": "Enable highlighting link occurrences in the current document.", + "configuration.markdown.copyFiles.destination": "Defines where files copied created by drop or paste should be created. This is a map from globs that match on the Markdown document to destinations.\n\nThe destinations may use the following variables:\n\n- `${documentFileName}` — The full filename of the Markdown document, for example: `readme.md`.\n- `${documentBaseName}` — The basename of Markdown document, for example: `readme`.\n- `${documentExtName}` — The extension of the Markdown document, for example: `md`.\n- `${documentDirName}` — The name of the Markdown document's parent directory.\n- `${documentWorkspaceFolder}` — The workspace folder for the Markdown document, for example: `/Users/me/myProject`. This is the same as `${documentDirName}` if the file is not part of a workspace.\n- `${fileName}` — The file name of the dropped file, for example: `image.png`.", + "configuration.markdown.copyFiles.overwriteBehavior": "Controls if files created by drop or paste should overwrite existing files.", + "configuration.markdown.copyFiles.overwriteBehavior.nameIncrementally": "If a file with the same name already exists, append a number to the file name, for example: `image.png` becomes `image-1.png`.", + "configuration.markdown.copyFiles.overwriteBehavior.overwrite": "If a file with the same name already exists, overwrite it.", + "configuration.markdown.preferredMdPathExtensionStyle": "Controls if file extensions (e.g. `.md`) are added or not for links to Markdown files. This setting is used when file paths are added by tooling such as path completions or file renames.", + "configuration.markdown.preferredMdPathExtensionStyle.auto": "For existing paths, try to maintain the file extension style. For new paths, add file extensions.", + "configuration.markdown.preferredMdPathExtensionStyle.includeExtension": "Prefer including the file extension. For example, path completions to a file named `file.md` will insert `file.md`.", + "configuration.markdown.preferredMdPathExtensionStyle.removeExtension": "Prefer removing the file extension. For example, path completions to a file named `file.md` will insert `file` without the `.md`.", "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/extensions/markdown-language-features/preview-src/activeLineMarker.ts b/extensions/markdown-language-features/preview-src/activeLineMarker.ts index 76791e9092..b17166366c 100644 --- a/extensions/markdown-language-features/preview-src/activeLineMarker.ts +++ b/extensions/markdown-language-features/preview-src/activeLineMarker.ts @@ -9,7 +9,7 @@ export class ActiveLineMarker { onDidChangeTextEditorSelection(line: number, documentVersion: number) { const { previous } = getElementsForSourceLine(line, documentVersion); - this._update(previous && previous.element); + this._update(previous && (previous.codeElement || previous.element)); } _update(before: HTMLElement | undefined) { @@ -22,13 +22,14 @@ export class ActiveLineMarker { if (!element) { return; } - element.className = element.className.replace(/\bcode-active-line\b/g, ''); + element.classList.toggle('code-active-line', false); } _markActiveElement(element: HTMLElement | undefined) { if (!element) { return; } - element.className += ' code-active-line'; + + element.classList.toggle('code-active-line', true); } } diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 908822f834..9b23a09cb1 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -10,6 +10,7 @@ import { getEditorLineNumberForPageOffset, scrollToRevealSourceLine, getLineElem import { SettingsManager, getData } from './settings'; import throttle = require('lodash.throttle'); import morphdom from 'morphdom'; +import type { ToWebviewMessage } from '../types/previewMessaging'; let scrollDisabledCount = 0; @@ -21,13 +22,16 @@ let documentResource = settings.settings.source; const vscode = acquireVsCodeApi(); -const originalState = vscode.getState(); - +const originalState = vscode.getState() ?? {} as any; const state = { - ...(typeof originalState === 'object' ? originalState : {}), + ...originalState, ...getData('data-state') }; +if (typeof originalState.scrollProgress !== 'undefined' && originalState?.resource !== state.resource) { + state.scrollProgress = 0; +} + // Make sure to sync VS Code state here vscode.setState(state); @@ -62,7 +66,9 @@ onceDocumentLoaded(() => { if (typeof scrollProgress === 'number' && !settings.settings.fragment) { doAfterImagesLoaded(() => { scrollDisabledCount += 1; - window.scrollTo(0, scrollProgress * document.body.clientHeight); + // Always set scroll of at least 1 to prevent VS Code's webview code from auto scrolling us + const scrollToY = Math.max(1, scrollProgress * document.body.clientHeight); + window.scrollTo(0, scrollToY); }); return; } @@ -71,10 +77,16 @@ onceDocumentLoaded(() => { doAfterImagesLoaded(() => { // Try to scroll to fragment if available if (settings.settings.fragment) { + let fragment: string; + try { + fragment = encodeURIComponent(settings.settings.fragment); + } catch { + fragment = settings.settings.fragment; + } state.fragment = undefined; vscode.setState(state); - const element = getLineElementForFragment(settings.settings.fragment, documentVersion); + const element = getLineElementForFragment(fragment, documentVersion); if (element) { scrollDisabledCount += 1; scrollToRevealSourceLine(element.line, documentVersion, settings); @@ -87,6 +99,10 @@ onceDocumentLoaded(() => { } }); } + + if (typeof settings.settings.selectedLine === 'number') { + marker.onDidChangeTextEditorSelection(settings.settings.selectedLine, documentVersion); + } }); const onUpdateView = (() => { @@ -110,17 +126,17 @@ window.addEventListener('resize', () => { }, true); window.addEventListener('message', async event => { - - switch (event.data.type) { + const data = event.data as ToWebviewMessage.Type; + switch (data.type) { case 'onDidChangeTextEditorSelection': - if (event.data.source === documentResource) { - marker.onDidChangeTextEditorSelection(event.data.line, documentVersion); + if (data.source === documentResource) { + marker.onDidChangeTextEditorSelection(data.line, documentVersion); } return; case 'updateView': - if (event.data.source === documentResource) { - onUpdateView(event.data.line); + if (data.source === documentResource) { + onUpdateView(data.line); } return; @@ -128,7 +144,7 @@ window.addEventListener('message', async event => { const root = document.querySelector('.markdown-body')!; const parser = new DOMParser(); - const newContent = parser.parseFromString(event.data.content, 'text/html'); + const newContent = parser.parseFromString(data.content, 'text/html'); // Strip out meta http-equiv tags for (const metaElement of Array.from(newContent.querySelectorAll('meta'))) { @@ -137,11 +153,15 @@ window.addEventListener('message', async event => { } } - if (event.data.source !== documentResource) { + if (data.source !== documentResource) { root.replaceWith(newContent.querySelector('.markdown-body')!); - documentResource = event.data.source; + documentResource = data.source; } else { - // Compare two elements but skip `data-line` + const skippedAttrs = [ + 'open', // for details + ]; + + // Compare two elements but some elements const areEqual = (a: Element, b: Element): boolean => { if (a.isEqualNode(b)) { return true; @@ -151,8 +171,8 @@ window.addEventListener('message', async event => { return false; } - const aAttrs = a.attributes; - const bAttrs = b.attributes; + const aAttrs = [...a.attributes].filter(attr => !skippedAttrs.includes(attr.name)); + const bAttrs = [...b.attributes].filter(attr => !skippedAttrs.includes(attr.name)); if (aAttrs.length !== bAttrs.length) { return false; } @@ -183,15 +203,13 @@ window.addEventListener('message', async event => { style.remove(); } newRoot.prepend(...styles); - morphdom(root, newRoot, { childrenOnly: true, onBeforeElUpdated: (fromEl, toEl) => { if (areEqual(fromEl, toEl)) { - // areEqual doesn't look at `data-line` so copy those over - + // areEqual doesn't look at `data-line` so copy those over manually const fromLines = fromEl.querySelectorAll('[data-line]'); - const toLines = fromEl.querySelectorAll('[data-line]'); + const toLines = toEl.querySelectorAll('[data-line]'); if (fromLines.length !== toLines.length) { console.log('unexpected line number change'); } @@ -207,6 +225,12 @@ window.addEventListener('message', async event => { return false; } + if (fromEl.tagName === 'DETAILS' && toEl.tagName === 'DETAILS') { + if (fromEl.hasAttribute('open')) { + toEl.setAttribute('open', ''); + } + } + return true; } }); @@ -235,7 +259,7 @@ document.addEventListener('dblclick', event => { } const offset = event.pageY; - const line = getEditorLineNumberForPageOffset(offset, documentVersion, settings); + const line = getEditorLineNumberForPageOffset(offset, documentVersion); if (typeof line === 'number' && !isNaN(line)) { messaging.postMessage('didClick', { line: Math.floor(line) }); } @@ -284,7 +308,7 @@ window.addEventListener('scroll', throttle(() => { if (scrollDisabledCount > 0) { scrollDisabledCount -= 1; } else { - const line = getEditorLineNumberForPageOffset(window.scrollY, documentVersion, settings); + const line = getEditorLineNumberForPageOffset(window.scrollY, documentVersion); if (typeof line === 'number' && !isNaN(line)) { messaging.postMessage('revealLine', { line }); } diff --git a/extensions/markdown-language-features/preview-src/messaging.ts b/extensions/markdown-language-features/preview-src/messaging.ts index 588baa99ce..1c0bc75fe5 100644 --- a/extensions/markdown-language-features/preview-src/messaging.ts +++ b/extensions/markdown-language-features/preview-src/messaging.ts @@ -4,21 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import { SettingsManager } from './settings'; +import type { FromWebviewMessage } from '../types/previewMessaging'; export interface MessagePoster { /** * Post a message to the markdown extension */ - postMessage(type: string, body: object): void; + postMessage( + type: T['type'], + body: Omit + ): void; } -export const createPosterForVsCode = (vscode: any, settingsManager: SettingsManager) => { - return new class implements MessagePoster { - postMessage(type: string, body: object): void { +export const createPosterForVsCode = (vscode: any, settingsManager: SettingsManager): MessagePoster => { + return { + postMessage( + type: T['type'], + body: Omit + ): void { vscode.postMessage({ type, source: settingsManager.settings!.source, - body + ...body }); } }; diff --git a/extensions/markdown-language-features/preview-src/scroll-sync.ts b/extensions/markdown-language-features/preview-src/scroll-sync.ts index 35b9727453..7e7d56bf3f 100644 --- a/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/extensions/markdown-language-features/preview-src/scroll-sync.ts @@ -7,18 +7,21 @@ import { SettingsManager } from './settings'; const codeLineClass = 'code-line'; -function clamp(min: number, max: number, value: number) { - return Math.min(max, Math.max(min, value)); -} -function clampLine(line: number, lineCount: number) { - return clamp(0, lineCount - 1, line); -} +export class CodeLineElement { + private readonly _detailParentElements: readonly HTMLDetailsElement[]; + constructor( + readonly element: HTMLElement, + readonly line: number, + readonly codeElement?: HTMLElement, + ) { + this._detailParentElements = Array.from(getParentsWithTagName(element, 'DETAILS')); + } -export interface CodeLineElement { - element: HTMLElement; - line: number; + get isVisible(): boolean { + return !this._detailParentElements.some(x => !x.open); + } } const getCodeLineElements = (() => { @@ -27,19 +30,26 @@ const getCodeLineElements = (() => { return (documentVersion: number) => { if (!cachedElements || documentVersion !== cachedVersion) { cachedVersion = documentVersion; - cachedElements = [{ element: document.body, line: 0 }]; + cachedElements = [new CodeLineElement(document.body, -1)]; for (const element of document.getElementsByClassName(codeLineClass)) { + if (!(element instanceof HTMLElement)) { + continue; + } + const line = +element.getAttribute('data-line')!; if (isNaN(line)) { continue; } + if (element.tagName === 'CODE' && element.parentElement && element.parentElement.tagName === 'PRE') { - // Fenched code blocks are a special case since the `code-line` can only be marked on + // Fenced code blocks are a special case since the `code-line` can only be marked on // the `` element and not the parent `
` element.
-					cachedElements.push({ element: element.parentElement as HTMLElement, line });
+					cachedElements.push(new CodeLineElement(element.parentElement, line, element));
+				} else if (element.tagName === 'UL' || element.tagName === 'OL') {
+					// Skip adding list elements since the first child has the same code line (and should be preferred)
 				} else {
-					cachedElements.push({ element: element as HTMLElement, line });
+					cachedElements.push(new CodeLineElement(element, line));
 				}
 			}
 		}
@@ -72,7 +82,7 @@ export function getElementsForSourceLine(targetLine: number, documentVersion: nu
  * Find the html elements that are at a specific pixel offset on the page.
  */
 export function getLineElementsAtPageOffset(offset: number, documentVersion: number): { previous: CodeLineElement; next?: CodeLineElement } {
-	const lines = getCodeLineElements(documentVersion);
+	const lines = getCodeLineElements(documentVersion).filter(x => x.isVisible);
 	const position = offset - window.scrollY;
 	let lo = -1;
 	let hi = lines.length - 1;
@@ -139,8 +149,9 @@ export function scrollToRevealSourceLine(line: number, documentVersion: number,
 	if (next && next.line !== previous.line) {
 		// Between two elements. Go to percentage offset between them.
 		const betweenProgress = (line - previous.line) / (next.line - previous.line);
-		const elementOffset = next.element.getBoundingClientRect().top - previousTop;
-		scrollTo = previousTop + betweenProgress * elementOffset;
+		const previousEnd = previousTop + rect.height;
+		const betweenHeight = next.element.getBoundingClientRect().top - previousEnd;
+		scrollTo = previousEnd + betweenProgress * betweenHeight;
 	} else {
 		const progressInElement = line - Math.floor(line);
 		scrollTo = previousTop + (rect.height * progressInElement);
@@ -149,20 +160,20 @@ export function scrollToRevealSourceLine(line: number, documentVersion: number,
 	window.scroll(window.scrollX, Math.max(1, window.scrollY + scrollTo));
 }
 
-export function getEditorLineNumberForPageOffset(offset: number, documentVersion: number, settingsManager: SettingsManager) {
-	const lineCount = settingsManager.settings?.lineCount ?? 0;
+export function getEditorLineNumberForPageOffset(offset: number, documentVersion: number): number | null {
 	const { previous, next } = getLineElementsAtPageOffset(offset, documentVersion);
 	if (previous) {
+		if (previous.line < 0) {
+			return 0;
+		}
 		const previousBounds = getElementBounds(previous);
 		const offsetFromPrevious = (offset - window.scrollY - previousBounds.top);
 		if (next) {
 			const progressBetweenElements = offsetFromPrevious / (getElementBounds(next).top - previousBounds.top);
-			const line = previous.line + progressBetweenElements * (next.line - previous.line);
-			return clampLine(line, lineCount);
+			return previous.line + progressBetweenElements * (next.line - previous.line);
 		} else {
 			const progressWithinElement = offsetFromPrevious / (previousBounds.height);
-			const line = previous.line + progressWithinElement;
-			return clampLine(line, lineCount);
+			return previous.line + progressWithinElement;
 		}
 	}
 	return null;
@@ -176,3 +187,11 @@ export function getLineElementForFragment(fragment: string, documentVersion: num
 		return element.element.id === fragment;
 	});
 }
+
+function* getParentsWithTagName(element: HTMLElement, tagName: string): Iterable {
+	for (let parent = element.parentElement; parent; parent = parent.parentElement) {
+		if (parent.tagName === tagName) {
+			yield parent as T;
+		}
+	}
+}
diff --git a/extensions/markdown-language-features/preview-src/settings.ts b/extensions/markdown-language-features/preview-src/settings.ts
index 83594633a9..eb48cdf58f 100644
--- a/extensions/markdown-language-features/preview-src/settings.ts
+++ b/extensions/markdown-language-features/preview-src/settings.ts
@@ -7,7 +7,8 @@ export interface PreviewSettings {
 	readonly source: string;
 	readonly line?: number;
 	readonly fragment?: string;
-	readonly lineCount: number;
+	readonly selectedLine?: number;
+
 	readonly scrollPreviewWithEditor?: boolean;
 	readonly scrollEditorWithPreview: boolean;
 	readonly disableSecurityWarnings: boolean;
diff --git a/extensions/markdown-language-features/server/CHANGELOG.md b/extensions/markdown-language-features/server/CHANGELOG.md
new file mode 100644
index 0000000000..98ac9c9868
--- /dev/null
+++ b/extensions/markdown-language-features/server/CHANGELOG.md
@@ -0,0 +1,2 @@
+# 0.3.0 - March 28, 2023
+- Pick up [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) 0.3.0. See [CHANGELOG](https://github.com/microsoft/vscode-markdown-languageservice/blob/main/CHANGELOG.md#030--march-16-2023) for details.
diff --git a/extensions/markdown-language-features/server/README.md b/extensions/markdown-language-features/server/README.md
index de4e33926c..1fd3830219 100644
--- a/extensions/markdown-language-features/server/README.md
+++ b/extensions/markdown-language-features/server/README.md
@@ -23,12 +23,20 @@ This server uses the [Markdown Language Service](https://github.com/microsoft/vs
 
 - [Find all references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) to headers and links across all Markdown files in the workspace.
 
-- [Rename](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename) of headers and links across all Markdown files in the workspace.
-
 - [Go to definition](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_definition) from links to headers or link definitions.
 
-- (experimental) [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links.
+- [Rename](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_rename) of headers and links across all Markdown files in the workspace.
 
+- Find all references to a file. Uses a custom `markdown/getReferencesToFileInWorkspace` message.
+
+- [Code Actions](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_codeAction)
+
+	- Organize link definitions source action.
+	- Extract link to definition refactoring.
+
+- Updating links when a file is moved / renamed. Uses a custom `markdown/getEditForFileRenames` message.
+
+- [Pull diagnostics (validation)](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_pullDiagnostics) for links.
 
 
 ## Client requirements
@@ -48,17 +56,24 @@ The server supports the following settings:
 	- `suggest`
 		- `paths`
 			- `enabled` — Enable/disable path suggestions.
-	- `experimental`
-		- `validate`
-			- `enabled` — Enable/disable all validation.
-			- `referenceLinks`
-				- `enabled` — Enable/disable validation of reference links: `[text][ref]`
-			- `fragmentLinks`
-				- `enabled` — Enable/disable validation of links to fragments in the current files: `[text](#head)`
-			- `fileLinks`
-				- `enabled` — Enable/disable validation of links to file in the workspace.
-				- `markdownFragmentLinks` — Enable/disable validation of links to headers in other Markdown files.
-			- `ignoreLinks` — Array of glob patterns for files that should not be validated.
+
+	- `occurrencesHighlight`
+		- `enabled` — Enable/disable highlighting of link occurrences.
+
+	- `validate`
+		- `enabled` — Enable/disable all validation.
+		- `referenceLinks`
+			- `enabled` — Enable/disable validation of reference links: `[text][ref]`
+		- `fragmentLinks`
+			- `enabled` — Enable/disable validation of links to fragments in the current files: `[text](#head)`
+		- `fileLinks`
+			- `enabled` — Enable/disable validation of links to file in the workspace.
+			- `markdownFragmentLinks` — Enable/disable validation of links to headers in other Markdown files. Use `inherit` to inherit the `fragmentLinks` setting.
+		- `ignoredLinks` — Array of glob patterns for files that should not be validated.
+		- `unusedLinkDefinitions`
+			- `enabled` — Enable/disable validation of unused link definitions.
+		- `duplicateLinkDefinitions`
+			- `enabled` — Enable/disable validation of duplicated link definitions.
 
 ### Custom requests
 
diff --git a/extensions/markdown-language-features/server/build/pipeline.yml b/extensions/markdown-language-features/server/build/pipeline.yml
new file mode 100644
index 0000000000..c229f78cfb
--- /dev/null
+++ b/extensions/markdown-language-features/server/build/pipeline.yml
@@ -0,0 +1,34 @@
+name: $(Date:yyyyMMdd)$(Rev:.r)
+
+trigger: none
+pr: none
+
+resources:
+  repositories:
+    - repository: templates
+      type: github
+      name: microsoft/vscode-engineering
+      ref: main
+      endpoint: Monaco
+
+parameters:
+  - name: publishPackage
+    displayName: Publish vscode-markdown-languageserver
+    type: boolean
+    default: false
+
+extends:
+  template: azure-pipelines/npm-package/pipeline.yml@templates
+  parameters:
+    npmPackages:
+      - name: vscode-markdown-languageserver
+        workingDirectory: extensions/markdown-language-features/server
+
+        buildSteps:
+          - script: yarn install
+            displayName: Install dependencies
+
+          - script: gulp compile-extension:markdown-language-features-server
+            displayName: Compile
+
+        publishPackage: ${{ parameters.publishPackage }}
diff --git a/extensions/markdown-language-features/server/extension-browser.webpack.config.js b/extensions/markdown-language-features/server/extension-browser.webpack.config.js
index 6a9bd10794..92784d470a 100644
--- a/extensions/markdown-language-features/server/extension-browser.webpack.config.js
+++ b/extensions/markdown-language-features/server/extension-browser.webpack.config.js
@@ -13,10 +13,10 @@ const path = require('path');
 module.exports = withBrowserDefaults({
 	context: __dirname,
 	entry: {
-		extension: './src/browser/main.ts',
+		extension: './src/browser/workerMain.ts',
 	},
 	output: {
-		filename: 'main.js',
+		filename: 'workerMain.js',
 		path: path.join(__dirname, 'dist', 'browser'),
 		libraryTarget: 'var',
 		library: 'serverExportVar'
diff --git a/extensions/markdown-language-features/server/extension.webpack.config.js b/extensions/markdown-language-features/server/extension.webpack.config.js
index 6b4fdccfea..49e0bf8295 100644
--- a/extensions/markdown-language-features/server/extension.webpack.config.js
+++ b/extensions/markdown-language-features/server/extension.webpack.config.js
@@ -13,10 +13,10 @@ const path = require('path');
 module.exports = withDefaults({
 	context: path.join(__dirname),
 	entry: {
-		extension: './src/node/main.ts',
+		extension: './src/node/workerMain.ts',
 	},
 	output: {
-		filename: 'main.js',
+		filename: 'workerMain.js',
 		path: path.join(__dirname, 'dist', 'node'),
 	}
 });
diff --git a/extensions/markdown-language-features/server/package.json b/extensions/markdown-language-features/server/package.json
index a6ef241f4f..55fc5fdd84 100644
--- a/extensions/markdown-language-features/server/package.json
+++ b/extensions/markdown-language-features/server/package.json
@@ -1,7 +1,7 @@
 {
   "name": "vscode-markdown-languageserver",
   "description": "Markdown language server",
-  "version": "0.0.0-alpha-1",
+  "version": "0.4.0-alpha.2",
   "author": "Microsoft Corporation",
   "license": "MIT",
   "engines": {
@@ -9,18 +9,26 @@
   },
   "main": "./out/node/main",
   "browser": "./dist/browser/main",
+  "files": [
+    "dist/**/*.js",
+    "out/**/*.js"
+  ],
   "dependencies": {
-    "vscode-languageserver": "^8.0.2-next.5`",
-    "vscode-languageserver-textdocument": "^1.0.5",
-    "vscode-languageserver-types": "^3.17.1",
-    "vscode-markdown-languageservice": "^0.0.0-alpha.12",
-    "vscode-uri": "^3.0.3"
+    "@vscode/l10n": "^0.0.11",
+    "vscode-languageserver": "^8.1.0",
+    "vscode-languageserver-textdocument": "^1.0.8",
+    "vscode-languageserver-types": "^3.17.3",
+    "vscode-markdown-languageservice": "^0.4.0-alpha.2",
+    "vscode-uri": "^3.0.7"
   },
   "devDependencies": {
     "@types/node": "16.x"
   },
   "scripts": {
     "compile": "gulp compile-extension:markdown-language-features-server",
-    "watch": "gulp watch-extension:markdown-language-features-server"
+    "prepublishOnly": "npm run compile",
+    "watch": "gulp watch-extension:markdown-language-features-server",
+    "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none",
+    "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
   }
 }
diff --git a/extensions/markdown-language-features/server/src/browser/main.ts b/extensions/markdown-language-features/server/src/browser/main.ts
index 8caa4c51e3..034e743f97 100644
--- a/extensions/markdown-language-features/server/src/browser/main.ts
+++ b/extensions/markdown-language-features/server/src/browser/main.ts
@@ -4,13 +4,11 @@
  *--------------------------------------------------------------------------------------------*/
 
 import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser';
-import { startServer } from '../server';
-
-declare let self: any;
+import { startVsCodeServer } from '../server';
 
 const messageReader = new BrowserMessageReader(self);
 const messageWriter = new BrowserMessageWriter(self);
 
 const connection = createConnection(messageReader, messageWriter);
 
-startServer(connection);
+startVsCodeServer(connection);
diff --git a/extensions/markdown-language-features/server/src/browser/workerMain.ts b/extensions/markdown-language-features/server/src/browser/workerMain.ts
new file mode 100644
index 0000000000..6c19839050
--- /dev/null
+++ b/extensions/markdown-language-features/server/src/browser/workerMain.ts
@@ -0,0 +1,38 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import * as l10n from '@vscode/l10n';
+
+let initialized = false;
+const pendingMessages: any[] = [];
+
+const messageHandler = async (e: any) => {
+	if (!initialized) {
+		const l10nLog: string[] = [];
+		initialized = true;
+		const i10lLocation = e.data.i10lLocation;
+		if (i10lLocation) {
+			try {
+				await l10n.config({ uri: i10lLocation });
+				l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}.`);
+			} catch (e) {
+				l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}.`);
+			}
+		} else {
+			l10nLog.push(`l10n: No bundle configured.`);
+		}
+
+		await import('./main');
+
+		if (self.onmessage !== messageHandler) {
+			pendingMessages.forEach(msg => self.onmessage?.(msg));
+			pendingMessages.length = 0;
+		}
+
+		l10nLog.forEach(console.log);
+	} else {
+		pendingMessages.push(e);
+	}
+};
+self.onmessage = messageHandler;
diff --git a/extensions/markdown-language-features/server/src/config.ts b/extensions/markdown-language-features/server/src/config.ts
index 1da18dfe3a..95bc10b15f 100644
--- a/extensions/markdown-language-features/server/src/config.ts
+++ b/extensions/markdown-language-features/server/src/config.ts
@@ -3,17 +3,25 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-export interface LsConfiguration {
-	/**
-	 * List of file extensions should be considered as markdown.
-	 *
-	 * These should not include the leading `.`.
-	 */
-	readonly markdownFileExtensions: readonly string[];
-}
+import { LsConfiguration } from 'vscode-markdown-languageservice';
+
+export { LsConfiguration };
 
 const defaultConfig: LsConfiguration = {
 	markdownFileExtensions: ['md'],
+	knownLinkedToFileExtensions: [
+		'jpg',
+		'jpeg',
+		'png',
+		'gif',
+		'webp',
+		'bmp',
+		'tiff',
+	],
+	excludePaths: [
+		'**/.*',
+		'**/node_modules/**',
+	]
 };
 
 export function getLsConfiguration(overrides: Partial): LsConfiguration {
diff --git a/extensions/markdown-language-features/server/src/configuration.ts b/extensions/markdown-language-features/server/src/configuration.ts
index f63656a1ed..cd7e8e3d4e 100644
--- a/extensions/markdown-language-features/server/src/configuration.ts
+++ b/extensions/markdown-language-features/server/src/configuration.ts
@@ -6,30 +6,45 @@
 import { Connection, Emitter } from 'vscode-languageserver';
 import { Disposable } from './util/dispose';
 
-export type ValidateEnabled = 'ignore' | 'warning' | 'error';
+export type ValidateEnabled = 'ignore' | 'warning' | 'error' | 'hint';
 
-interface Settings {
+export interface Settings {
 	readonly markdown: {
+		readonly server: {
+			readonly log: 'off' | 'debug' | 'trace';
+		};
+
+		readonly preferredMdPathExtensionStyle: 'auto' | 'includeExtension' | 'removeExtension';
+
+		readonly occurrencesHighlight: {
+			readonly enabled: boolean;
+		};
+
 		readonly suggest: {
 			readonly paths: {
 				readonly enabled: boolean;
+				readonly includeWorkspaceHeaderCompletions: 'never' | 'onSingleOrDoubleHash' | 'onDoubleHash';
 			};
 		};
 
-		readonly experimental: {
-			readonly validate: {
-				readonly enabled: true;
-				readonly referenceLinks: {
-					readonly enabled: ValidateEnabled;
-				};
-				readonly fragmentLinks: {
-					readonly enabled: ValidateEnabled;
-				};
-				readonly fileLinks: {
-					readonly enabled: ValidateEnabled;
-					readonly markdownFragmentLinks: ValidateEnabled;
-				};
-				readonly ignoreLinks: readonly string[];
+		readonly validate: {
+			readonly enabled: true;
+			readonly referenceLinks: {
+				readonly enabled: ValidateEnabled;
+			};
+			readonly fragmentLinks: {
+				readonly enabled: ValidateEnabled;
+			};
+			readonly fileLinks: {
+				readonly enabled: ValidateEnabled;
+				readonly markdownFragmentLinks: ValidateEnabled | 'inherit';
+			};
+			readonly ignoredLinks: readonly string[];
+			readonly unusedLinkDefinitions: {
+				readonly enabled: ValidateEnabled;
+			};
+			readonly duplicateLinkDefinitions: {
+				readonly enabled: ValidateEnabled;
 			};
 		};
 	};
diff --git a/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts
index 11950bcc5e..686d163577 100644
--- a/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts
+++ b/extensions/markdown-language-features/server/src/languageFeatures/diagnostics.ts
@@ -3,19 +3,20 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { Connection, FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport } from 'vscode-languageserver';
+import { Connection, FullDocumentDiagnosticReport, TextDocuments, UnchangedDocumentDiagnosticReport } from 'vscode-languageserver';
 import * as md from 'vscode-markdown-languageservice';
-import { disposeAll } from 'vscode-markdown-languageservice/out/util/dispose';
 import { Disposable } from 'vscode-notebook-renderer/events';
 import { URI } from 'vscode-uri';
 import { ConfigurationManager, ValidateEnabled } from '../configuration';
-import { VsCodeClientWorkspace } from '../workspace';
+import { disposeAll } from '../util/dispose';
 
 const defaultDiagnosticOptions: md.DiagnosticOptions = {
 	validateFileLinks: md.DiagnosticLevel.ignore,
 	validateReferences: md.DiagnosticLevel.ignore,
 	validateFragmentLinks: md.DiagnosticLevel.ignore,
 	validateMarkdownFileLinkFragments: md.DiagnosticLevel.ignore,
+	validateUnusedLinkDefinitions: md.DiagnosticLevel.ignore,
+	validateDuplicateLinkDefinitions: md.DiagnosticLevel.ignore,
 	ignoreLinks: [],
 };
 
@@ -24,6 +25,7 @@ function convertDiagnosticLevel(enabled: ValidateEnabled): md.DiagnosticLevel |
 		case 'error': return md.DiagnosticLevel.error;
 		case 'warning': return md.DiagnosticLevel.warning;
 		case 'ignore': return md.DiagnosticLevel.ignore;
+		case 'hint': return md.DiagnosticLevel.hint;
 		default: return md.DiagnosticLevel.ignore;
 	}
 }
@@ -34,27 +36,31 @@ function getDiagnosticsOptions(config: ConfigurationManager): md.DiagnosticOptio
 		return defaultDiagnosticOptions;
 	}
 
+	const validateFragmentLinks = convertDiagnosticLevel(settings.markdown.validate.fragmentLinks.enabled);
 	return {
-		validateFileLinks: convertDiagnosticLevel(settings.markdown.experimental.validate.fileLinks.enabled),
-		validateReferences: convertDiagnosticLevel(settings.markdown.experimental.validate.referenceLinks.enabled),
-		validateFragmentLinks: convertDiagnosticLevel(settings.markdown.experimental.validate.fragmentLinks.enabled),
-		validateMarkdownFileLinkFragments: convertDiagnosticLevel(settings.markdown.experimental.validate.fileLinks.markdownFragmentLinks),
-		ignoreLinks: settings.markdown.experimental.validate.ignoreLinks,
+		validateFileLinks: convertDiagnosticLevel(settings.markdown.validate.fileLinks.enabled),
+		validateReferences: convertDiagnosticLevel(settings.markdown.validate.referenceLinks.enabled),
+		validateFragmentLinks: convertDiagnosticLevel(settings.markdown.validate.fragmentLinks.enabled),
+		validateMarkdownFileLinkFragments: settings.markdown.validate.fileLinks.markdownFragmentLinks === 'inherit' ? validateFragmentLinks : convertDiagnosticLevel(settings.markdown.validate.fileLinks.markdownFragmentLinks),
+		validateUnusedLinkDefinitions: convertDiagnosticLevel(settings.markdown.validate.unusedLinkDefinitions.enabled),
+		validateDuplicateLinkDefinitions: convertDiagnosticLevel(settings.markdown.validate.duplicateLinkDefinitions.enabled),
+		ignoreLinks: settings.markdown.validate.ignoredLinks,
 	};
 }
 
 export function registerValidateSupport(
 	connection: Connection,
-	workspace: VsCodeClientWorkspace,
+	workspace: md.IWorkspace,
+	documents: TextDocuments,
 	ls: md.IMdLanguageService,
 	config: ConfigurationManager,
+	logger: md.ILogger,
 ): Disposable {
 	let diagnosticOptions: md.DiagnosticOptions = defaultDiagnosticOptions;
 	function updateDiagnosticsSetting(): void {
 		diagnosticOptions = getDiagnosticsOptions(config);
 	}
 
-
 	const subs: Disposable[] = [];
 	const manager = ls.createPullDiagnosticsManager();
 	subs.push(manager);
@@ -64,14 +70,23 @@ export function registerValidateSupport(
 		connection.languages.diagnostics.refresh();
 	}));
 
+	const emptyDiagnosticsResponse = Object.freeze({ kind: 'full', items: [] });
+
 	connection.languages.diagnostics.on(async (params, token): Promise => {
-		if (!config.getSettings()?.markdown.experimental.validate.enabled) {
-			return { kind: 'full', items: [] };
+		logger.log(md.LogLevel.Debug, 'connection.languages.diagnostics.on', { document: params.textDocument.uri });
+
+		if (!config.getSettings()?.markdown.validate.enabled) {
+			return emptyDiagnosticsResponse;
 		}
 
-		const document = await workspace.openMarkdownDocument(URI.parse(params.textDocument.uri));
+		const uri = URI.parse(params.textDocument.uri);
+		if (!workspace.hasMarkdownDocument(uri)) {
+			return emptyDiagnosticsResponse;
+		}
+
+		const document = await workspace.openMarkdownDocument(uri);
 		if (!document) {
-			return { kind: 'full', items: [] };
+			return emptyDiagnosticsResponse;
 		}
 
 		const diagnostics = await manager.computeDiagnostics(document, diagnosticOptions, token);
@@ -87,6 +102,10 @@ export function registerValidateSupport(
 		connection.languages.diagnostics.refresh();
 	}));
 
+	subs.push(documents.onDidClose(e => {
+		manager.disposeDocumentResources(URI.parse(e.document.uri));
+	}));
+
 	return {
 		dispose: () => {
 			disposeAll(subs);
diff --git a/extensions/markdown-language-features/server/src/logging.ts b/extensions/markdown-language-features/server/src/logging.ts
index 3d90d90b40..56fb1baf5a 100644
--- a/extensions/markdown-language-features/server/src/logging.ts
+++ b/extensions/markdown-language-features/server/src/logging.ts
@@ -3,9 +3,11 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { ILogger, LogLevel } from 'vscode-markdown-languageservice';
+import * as md from 'vscode-markdown-languageservice';
+import { ConfigurationManager } from './configuration';
+import { Disposable } from './util/dispose';
 
-export class LogFunctionLogger implements ILogger {
+export class LogFunctionLogger extends Disposable implements md.ILogger {
 
 	private static now(): string {
 		const now = new Date();
@@ -27,21 +29,53 @@ export class LogFunctionLogger implements ILogger {
 		return JSON.stringify(data, undefined, 2);
 	}
 
+	private _logLevel: md.LogLevel;
+
 	constructor(
-		private readonly _logFn: typeof console.log
-	) { }
+		private readonly _logFn: typeof console.log,
+		private readonly _config: ConfigurationManager,
+	) {
+		super();
 
+		this._register(this._config.onDidChangeConfiguration(() => {
+			this._logLevel = LogFunctionLogger.readLogLevel(this._config);
+		}));
 
-	public log(level: LogLevel, title: string, message: string, data?: any): void {
-		this.appendLine(`[${level} ${LogFunctionLogger.now()}] ${title}: ${message}`);
+		this._logLevel = LogFunctionLogger.readLogLevel(this._config);
+	}
+
+	private static readLogLevel(config: ConfigurationManager): md.LogLevel {
+		switch (config.getSettings()?.markdown.server.log) {
+			case 'trace': return md.LogLevel.Trace;
+			case 'debug': return md.LogLevel.Debug;
+			case 'off':
+			default:
+				return md.LogLevel.Off;
+		}
+	}
+
+	get level(): md.LogLevel { return this._logLevel; }
+
+	public log(level: md.LogLevel, message: string, data?: any): void {
+		if (this.level < level) {
+			return;
+		}
+
+		this.appendLine(`[${this.toLevelLabel(level)} ${LogFunctionLogger.now()}] ${message}`);
 		if (data) {
 			this.appendLine(LogFunctionLogger.data2String(data));
 		}
 	}
 
+	private toLevelLabel(level: md.LogLevel): string {
+		switch (level) {
+			case md.LogLevel.Off: return 'Off';
+			case md.LogLevel.Debug: return 'Debug';
+			case md.LogLevel.Trace: return 'Trace';
+		}
+	}
+
 	private appendLine(value: string): void {
 		this._logFn(value);
 	}
 }
-
-export const consoleLogger = new LogFunctionLogger(console.log);
diff --git a/extensions/markdown-language-features/server/src/node/main.ts b/extensions/markdown-language-features/server/src/node/main.ts
index 64c0a006cd..f47c0ee6c7 100644
--- a/extensions/markdown-language-features/server/src/node/main.ts
+++ b/extensions/markdown-language-features/server/src/node/main.ts
@@ -4,7 +4,7 @@
  *--------------------------------------------------------------------------------------------*/
 
 import { Connection, createConnection } from 'vscode-languageserver/node';
-import { startServer } from '../server';
+import { startVsCodeServer } from '../server';
 
 // Create a connection for the server.
 const connection: Connection = createConnection();
@@ -16,4 +16,4 @@ process.on('unhandledRejection', (e: any) => {
 	connection.console.error(`Unhandled exception ${e}`);
 });
 
-startServer(connection);
+startVsCodeServer(connection);
diff --git a/extensions/markdown-language-features/server/src/node/workerMain.ts b/extensions/markdown-language-features/server/src/node/workerMain.ts
new file mode 100644
index 0000000000..1ed1bc7e84
--- /dev/null
+++ b/extensions/markdown-language-features/server/src/node/workerMain.ts
@@ -0,0 +1,23 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import * as l10n from '@vscode/l10n';
+
+async function setupMain() {
+	const l10nLog: string[] = [];
+
+	const i10lLocation = process.env['VSCODE_L10N_BUNDLE_LOCATION'];
+	if (i10lLocation) {
+		try {
+			await l10n.config({ uri: i10lLocation });
+			l10nLog.push(`l10n: Configured to ${i10lLocation.toString()}`);
+		} catch (e) {
+			l10nLog.push(`l10n: Problems loading ${i10lLocation.toString()} : ${e}`);
+		}
+	}
+	await import('./main');
+
+	l10nLog.forEach(console.log);
+}
+setupMain();
diff --git a/extensions/markdown-language-features/server/src/protocol.ts b/extensions/markdown-language-features/server/src/protocol.ts
index 77084d9266..2180f4f3a8 100644
--- a/extensions/markdown-language-features/server/src/protocol.ts
+++ b/extensions/markdown-language-features/server/src/protocol.ts
@@ -3,7 +3,7 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { RequestType } from 'vscode-languageserver';
+import { FileRename, RequestType } from 'vscode-languageserver';
 import type * as lsp from 'vscode-languageserver-types';
 import type * as md from 'vscode-markdown-languageservice';
 
@@ -14,7 +14,7 @@ export const fs_readFile = new RequestType<{ uri: string }, number[], any>('mark
 export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory');
 export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat');
 
-export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any>('markdown/fs/watcher/create');
+export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions; watchParentDirs: boolean }, void, any>('markdown/fs/watcher/create');
 export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete');
 
 export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace');
@@ -22,6 +22,9 @@ export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('
 
 //#region To server
 export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace');
+export const getEditForFileRenames = new RequestType('markdown/getEditForFileRenames');
 
 export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange');
+
+export const resolveLinkTarget = new RequestType<{ linkText: string; uri: string }, md.ResolvedDocumentLinkTarget, any>('markdown/resolveLinkTarget');
 //#endregion
diff --git a/extensions/markdown-language-features/server/src/server.ts b/extensions/markdown-language-features/server/src/server.ts
index 6ed6cc692c..4ec316072d 100644
--- a/extensions/markdown-language-features/server/src/server.ts
+++ b/extensions/markdown-language-features/server/src/server.ts
@@ -3,54 +3,92 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { CancellationToken, Connection, InitializeParams, InitializeResult, NotebookDocuments, TextDocuments } from 'vscode-languageserver';
+import * as l10n from '@vscode/l10n';
+import { CancellationToken, CompletionRegistrationOptions, CompletionRequest, Connection, Disposable, DocumentHighlightRegistrationOptions, DocumentHighlightRequest, InitializeParams, InitializeResult, NotebookDocuments, ResponseError, TextDocuments } from 'vscode-languageserver';
 import { TextDocument } from 'vscode-languageserver-textdocument';
 import * as lsp from 'vscode-languageserver-types';
 import * as md from 'vscode-markdown-languageservice';
-import { IDisposable } from 'vscode-markdown-languageservice/out/util/dispose';
 import { URI } from 'vscode-uri';
-import { getLsConfiguration } from './config';
-import { ConfigurationManager } from './configuration';
+import { LsConfiguration, getLsConfiguration } from './config';
+import { ConfigurationManager, Settings } from './configuration';
 import { registerValidateSupport } from './languageFeatures/diagnostics';
 import { LogFunctionLogger } from './logging';
 import * as protocol from './protocol';
+import { IDisposable } from './util/dispose';
 import { VsCodeClientWorkspace } from './workspace';
 
-export async function startServer(connection: Connection) {
+interface MdServerInitializationOptions extends LsConfiguration { }
+
+const organizeLinkDefKind = 'source.organizeLinkDefinitions';
+
+export async function startVsCodeServer(connection: Connection) {
+	const configurationManager = new ConfigurationManager(connection);
+	const logger = new LogFunctionLogger(connection.console.log.bind(connection.console), configurationManager);
+
+	const parser = new class implements md.IMdParser {
+		slugifier = md.githubSlugifier;
+
+		tokenize(document: md.ITextDocument): Promise {
+			return connection.sendRequest(protocol.parse, { uri: document.uri.toString() });
+		}
+	};
+
 	const documents = new TextDocuments(TextDocument);
 	const notebooks = new NotebookDocuments(documents);
 
-	const configurationManager = new ConfigurationManager(connection);
+	const workspaceFactory: WorkspaceFactory = ({ connection, config, workspaceFolders }) => {
+		const workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks, logger);
+		workspace.workspaceFolders = (workspaceFolders ?? []).map(x => URI.parse(x.uri));
+		return workspace;
+	};
 
-	let provider: md.IMdLanguageService | undefined;
-	let workspace: VsCodeClientWorkspace | undefined;
+	return startServer(connection, { documents, notebooks, configurationManager, logger, parser, workspaceFactory });
+}
+
+type WorkspaceFactory = (config: {
+	connection: Connection;
+	config: LsConfiguration;
+	workspaceFolders?: lsp.WorkspaceFolder[] | null;
+}) => md.IWorkspace;
+
+export async function startServer(connection: Connection, serverConfig: {
+	documents: TextDocuments;
+	notebooks?: NotebookDocuments;
+	configurationManager: ConfigurationManager;
+	logger: md.ILogger;
+	parser: md.IMdParser;
+	workspaceFactory: WorkspaceFactory;
+}) {
+	const { documents, notebooks } = serverConfig;
+
+	let mdLs: md.IMdLanguageService | undefined;
 
 	connection.onInitialize((params: InitializeParams): InitializeResult => {
-		const parser = new class implements md.IMdParser {
-			slugifier = md.githubSlugifier;
+		const initOptions = params.initializationOptions as MdServerInitializationOptions | undefined;
 
-			async tokenize(document: md.ITextDocument): Promise {
-				return await connection.sendRequest(protocol.parse, { uri: document.uri.toString() });
-			}
-		};
+		const mdConfig = getLsConfiguration(initOptions ?? {});
 
-		const config = getLsConfiguration({
-			markdownFileExtensions: params.initializationOptions.markdownFileExtensions,
-		});
-
-		const logger = new LogFunctionLogger(connection.console.log.bind(connection.console));
-		workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks, logger);
-		provider = md.createLanguageService({
+		const workspace = serverConfig.workspaceFactory({ connection, config: mdConfig, workspaceFolders: params.workspaceFolders });
+		mdLs = md.createLanguageService({
 			workspace,
-			parser,
-			logger,
-			markdownFileExtensions: config.markdownFileExtensions,
+			parser: serverConfig.parser,
+			logger: serverConfig.logger,
+			...mdConfig,
+			get preferredMdPathExtensionStyle() {
+				switch (serverConfig.configurationManager.getSettings()?.markdown.preferredMdPathExtensionStyle) {
+					case 'includeExtension': return md.PreferredMdPathExtensionStyle.includeExtension;
+					case 'removeExtension': return md.PreferredMdPathExtensionStyle.removeExtension;
+					case 'auto':
+					default:
+						return md.PreferredMdPathExtensionStyle.auto;
+				}
+			}
 		});
 
-		registerCompletionsSupport(connection, documents, provider, configurationManager);
-		registerValidateSupport(connection, workspace, provider, configurationManager);
+		registerCompletionsSupport(connection, documents, mdLs, serverConfig.configurationManager);
+		registerDocumentHighlightSupport(connection, documents, mdLs, serverConfig.configurationManager);
+		registerValidateSupport(connection, workspace, documents, mdLs, serverConfig.configurationManager, serverConfig.logger);
 
-		workspace.workspaceFolders = (params.workspaceFolders ?? []).map(x => URI.parse(x.uri));
 		return {
 			capabilities: {
 				diagnosticProvider: {
@@ -59,11 +97,19 @@ export async function startServer(connection: Connection) {
 					interFileDependencies: true,
 					workspaceDiagnostics: false,
 				},
-				completionProvider: { triggerCharacters: ['.', '/', '#'] },
+				codeActionProvider: {
+					resolveProvider: true,
+					codeActionKinds: [
+						organizeLinkDefKind,
+						'quickfix',
+						'refactor',
+					]
+				},
 				definitionProvider: true,
 				documentLinkProvider: { resolveProvider: true },
 				documentSymbolProvider: true,
 				foldingRangeProvider: true,
+				referencesProvider: true,
 				renameProvider: { prepareProvider: true, },
 				selectionRangeProvider: true,
 				workspaceSymbolProvider: true,
@@ -77,176 +123,241 @@ export async function startServer(connection: Connection) {
 		};
 	});
 
-
 	connection.onDocumentLinks(async (params, token): Promise => {
-		try {
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				return await provider!.getDocumentLinks(document, token);
-			}
-		} catch (e) {
-			console.error(e.stack);
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return [];
 		}
-		return [];
+		return mdLs!.getDocumentLinks(document, token);
 	});
 
 	connection.onDocumentLinkResolve(async (link, token): Promise => {
-		try {
-			return await provider!.resolveDocumentLink(link, token);
-		} catch (e) {
-			console.error(e.stack);
-		}
-		return undefined;
+		return mdLs!.resolveDocumentLink(link, token);
 	});
 
 	connection.onDocumentSymbol(async (params, token): Promise => {
-		try {
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				return await provider!.getDocumentSymbols(document, token);
-			}
-		} catch (e) {
-			console.error(e.stack);
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return [];
 		}
-		return [];
+		return mdLs!.getDocumentSymbols(document, { includeLinkDefinitions: true }, token);
 	});
 
 	connection.onFoldingRanges(async (params, token): Promise => {
-		try {
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				return await provider!.getFoldingRanges(document, token);
-			}
-		} catch (e) {
-			console.error(e.stack);
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return [];
 		}
-		return [];
+		return mdLs!.getFoldingRanges(document, token);
 	});
 
 	connection.onSelectionRanges(async (params, token): Promise => {
-		try {
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				return await provider!.getSelectionRanges(document, params.positions, token);
-			}
-		} catch (e) {
-			console.error(e.stack);
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return [];
 		}
-		return [];
+		return mdLs!.getSelectionRanges(document, params.positions, token);
 	});
 
 	connection.onWorkspaceSymbol(async (params, token): Promise => {
-		try {
-			return await provider!.getWorkspaceSymbols(params.query, token);
-		} catch (e) {
-			console.error(e.stack);
-		}
-		return [];
+		return mdLs!.getWorkspaceSymbols(params.query, token);
 	});
 
 	connection.onReferences(async (params, token): Promise => {
-		try {
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				return await provider!.getReferences(document, params.position, params.context, token);
-			}
-		} catch (e) {
-			console.error(e.stack);
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return [];
 		}
-		return [];
+		return mdLs!.getReferences(document, params.position, params.context, token);
 	});
 
 	connection.onDefinition(async (params, token): Promise => {
-		try {
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				return await provider!.getDefinition(document, params.position, token);
-			}
-		} catch (e) {
-			console.error(e.stack);
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return undefined;
 		}
-		return undefined;
+		return mdLs!.getDefinition(document, params.position, token);
 	});
 
 	connection.onPrepareRename(async (params, token) => {
-		try {
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				return await provider!.prepareRename(document, params.position, token);
-			}
-		} catch (e) {
-			console.error(e.stack);
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return undefined;
+		}
+
+		try {
+			return await mdLs!.prepareRename(document, params.position, token);
+		} catch (e) {
+			if (e instanceof md.RenameNotSupportedAtLocationError) {
+				throw new ResponseError(0, e.message);
+			} else {
+				throw e;
+			}
 		}
-		return undefined;
 	});
 
 	connection.onRenameRequest(async (params, token) => {
-		try {
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				const edit = await provider!.getRenameEdit(document, params.position, params.newName, token);
-				console.log(JSON.stringify(edit));
-				return edit;
-			}
-		} catch (e) {
-			console.error(e.stack);
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return undefined;
 		}
-		return undefined;
+		return mdLs!.getRenameEdit(document, params.position, params.newName, token);
+	});
+
+	interface OrganizeLinkActionData {
+		readonly uri: string;
+	}
+
+	connection.onCodeAction(async (params, token) => {
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return undefined;
+		}
+
+		if (params.context.only?.some(kind => kind === 'source' || kind.startsWith('source.'))) {
+			const action: lsp.CodeAction = {
+				title: l10n.t("Organize link definitions"),
+				kind: organizeLinkDefKind,
+				data: { uri: document.uri }
+			};
+			return [action];
+		}
+
+		return mdLs!.getCodeActions(document, params.range, params.context, token);
+	});
+
+	connection.onCodeActionResolve(async (codeAction, token) => {
+		if (codeAction.kind === organizeLinkDefKind) {
+			const data = codeAction.data as OrganizeLinkActionData;
+			const document = documents.get(data.uri);
+			if (!document) {
+				return codeAction;
+			}
+
+			const edits = (await mdLs?.organizeLinkDefinitions(document, { removeUnused: true }, token)) || [];
+			codeAction.edit = {
+				changes: {
+					[data.uri]: edits
+				}
+			};
+			return codeAction;
+		}
+
+		return codeAction;
 	});
 
 	connection.onRequest(protocol.getReferencesToFileInWorkspace, (async (params: { uri: string }, token: CancellationToken) => {
-		try {
-			return await provider!.getFileReferences(URI.parse(params.uri), token);
-		} catch (e) {
-			console.error(e.stack);
+		return mdLs!.getFileReferences(URI.parse(params.uri), token);
+	}));
+
+	connection.onRequest(protocol.getEditForFileRenames, (async (params, token: CancellationToken) => {
+		const result = await mdLs!.getRenameFilesInWorkspaceEdit(params.map(x => ({ oldUri: URI.parse(x.oldUri), newUri: URI.parse(x.newUri) })), token);
+		if (!result) {
+			return result;
 		}
-		return undefined;
+
+		return {
+			edit: result.edit,
+			participatingRenames: result.participatingRenames.map(rename => ({ oldUri: rename.oldUri.toString(), newUri: rename.newUri.toString() }))
+		};
+	}));
+
+	connection.onRequest(protocol.resolveLinkTarget, (async (params, token: CancellationToken) => {
+		return mdLs!.resolveLinkTarget(params.linkText, URI.parse(params.uri), token);
 	}));
 
 	documents.listen(connection);
-	notebooks.listen(connection);
+	notebooks?.listen(connection);
 	connection.listen();
 }
 
-
-function registerCompletionsSupport(
-	connection: Connection,
-	documents: TextDocuments,
-	ls: md.IMdLanguageService,
+function registerDynamicClientFeature(
 	config: ConfigurationManager,
-): IDisposable {
-	// let registration: Promise | undefined;
+	isEnabled: (settings: Settings | undefined) => boolean,
+	register: () => Promise,
+) {
+	let registration: Promise | undefined;
 	function update() {
-		// TODO: client still makes the request in this case. Figure our how to properly unregister.
-		return;
-		// const settings = config.getSettings();
-		// if (settings?.markdown.suggest.paths.enabled) {
-		// 	if (!registration) {
-		// 		registration = connection.client.register(CompletionRequest.type);
-		// 	}
-		// } else {
-		// 	registration?.then(x => x.dispose());
-		// 	registration = undefined;
-		// }
-	}
-
-	connection.onCompletion(async (params, token): Promise => {
-		try {
-			const settings = config.getSettings();
-			if (!settings?.markdown.suggest.paths.enabled) {
-				return [];
+		const settings = config.getSettings();
+		if (isEnabled(settings)) {
+			if (!registration) {
+				registration = register();
 			}
-
-			const document = documents.get(params.textDocument.uri);
-			if (document) {
-				return await ls.getCompletionItems(document, params.position, params.context!, token);
-			}
-		} catch (e) {
-			console.error(e.stack);
+		} else {
+			registration?.then(x => x.dispose());
+			registration = undefined;
 		}
-		return [];
-	});
+	}
 
 	update();
 	return config.onDidChangeConfiguration(() => update());
 }
+
+function registerCompletionsSupport(
+	connection: Connection,
+	documents: TextDocuments,
+	ls: md.IMdLanguageService,
+	config: ConfigurationManager,
+): IDisposable {
+	function getIncludeWorkspaceHeaderCompletions(): md.IncludeWorkspaceHeaderCompletions {
+		switch (config.getSettings()?.markdown.suggest.paths.includeWorkspaceHeaderCompletions) {
+			case 'onSingleOrDoubleHash': return md.IncludeWorkspaceHeaderCompletions.onSingleOrDoubleHash;
+			case 'onDoubleHash': return md.IncludeWorkspaceHeaderCompletions.onDoubleHash;
+			case 'never':
+			default: return md.IncludeWorkspaceHeaderCompletions.never;
+		}
+	}
+
+	connection.onCompletion(async (params, token): Promise => {
+		const settings = config.getSettings();
+		if (!settings?.markdown.suggest.paths.enabled) {
+			return [];
+		}
+
+		const document = documents.get(params.textDocument.uri);
+		if (document) {
+			// TODO: remove any type after picking up new release with correct types
+			return ls.getCompletionItems(document, params.position, {
+				...(params.context || {}),
+				includeWorkspaceHeaderCompletions: getIncludeWorkspaceHeaderCompletions(),
+			} as any, token);
+		}
+		return [];
+	});
+
+	return registerDynamicClientFeature(config, (settings) => !!settings?.markdown.suggest.paths.enabled, () => {
+		const registrationOptions: CompletionRegistrationOptions = {
+			documentSelector: null,
+			triggerCharacters: ['.', '/', '#'],
+		};
+		return connection.client.register(CompletionRequest.type, registrationOptions);
+	});
+}
+
+function registerDocumentHighlightSupport(
+	connection: Connection,
+	documents: TextDocuments,
+	mdLs: md.IMdLanguageService,
+	configurationManager: ConfigurationManager
+) {
+	connection.onDocumentHighlight(async (params, token) => {
+		const settings = configurationManager.getSettings();
+		if (!settings?.markdown.occurrencesHighlight.enabled) {
+			return undefined;
+		}
+
+		const document = documents.get(params.textDocument.uri);
+		if (!document) {
+			return undefined;
+		}
+
+		return mdLs!.getDocumentHighlights(document, params.position, token);
+	});
+
+	return registerDynamicClientFeature(configurationManager, (settings) => !!settings?.markdown.occurrencesHighlight.enabled, () => {
+		const registrationOptions: DocumentHighlightRegistrationOptions = {
+			documentSelector: null,
+		};
+		return connection.client.register(DocumentHighlightRequest.type, registrationOptions);
+	});
+}
diff --git a/extensions/markdown-language-features/server/src/util/dispose.ts b/extensions/markdown-language-features/server/src/util/dispose.ts
index 5b2ece486d..ead7309dfd 100644
--- a/extensions/markdown-language-features/server/src/util/dispose.ts
+++ b/extensions/markdown-language-features/server/src/util/dispose.ts
@@ -3,14 +3,6 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-export class MultiDisposeError extends Error {
-	constructor(
-		public readonly errors: any[]
-	) {
-		super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`);
-	}
-}
-
 export function disposeAll(disposables: Iterable) {
 	const errors: any[] = [];
 
@@ -25,7 +17,7 @@ export function disposeAll(disposables: Iterable) {
 	if (errors.length === 1) {
 		throw errors[0];
 	} else if (errors.length > 1) {
-		throw new MultiDisposeError(errors);
+		throw new AggregateError(errors, 'Encountered errors while disposing of store');
 	}
 }
 
@@ -60,21 +52,3 @@ export abstract class Disposable {
 	}
 }
 
-export class DisposableStore extends Disposable {
-	private readonly items = new Set();
-
-	public override dispose() {
-		super.dispose();
-		disposeAll(this.items);
-		this.items.clear();
-	}
-
-	public add(item: T): T {
-		if (this.isDisposed) {
-			console.warn('Adding to disposed store. Item will be leaked');
-		}
-
-		this.items.add(item);
-		return item;
-	}
-}
diff --git a/extensions/markdown-language-features/server/src/util/file.ts b/extensions/markdown-language-features/server/src/util/file.ts
index aa42c39470..0967418049 100644
--- a/extensions/markdown-language-features/server/src/util/file.ts
+++ b/extensions/markdown-language-features/server/src/util/file.ts
@@ -8,7 +8,7 @@ import { URI, Utils } from 'vscode-uri';
 import { LsConfiguration } from '../config';
 
 export function looksLikeMarkdownPath(config: LsConfiguration, resolvedHrefPath: URI) {
-	return config.markdownFileExtensions.includes(Utils.extname(URI.from(resolvedHrefPath)).toLowerCase().replace('.', ''));
+	return config.markdownFileExtensions.includes(Utils.extname(resolvedHrefPath).toLowerCase().replace('.', ''));
 }
 
 export function isMarkdownFile(document: TextDocument) {
diff --git a/extensions/markdown-language-features/server/src/workspace.ts b/extensions/markdown-language-features/server/src/workspace.ts
index 83fa402e48..37abdececc 100644
--- a/extensions/markdown-language-features/server/src/workspace.ts
+++ b/extensions/markdown-language-features/server/src/workspace.ts
@@ -3,14 +3,12 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import { Connection, Emitter, FileChangeType, NotebookDocuments, TextDocuments } from 'vscode-languageserver';
+import { Connection, Emitter, FileChangeType, NotebookDocuments, Position, Range, TextDocuments } from 'vscode-languageserver';
 import { TextDocument } from 'vscode-languageserver-textdocument';
 import * as md from 'vscode-markdown-languageservice';
-import { ContainingDocumentContext, FileWatcherOptions, IFileSystemWatcher } from 'vscode-markdown-languageservice/out/workspace';
 import { URI } from 'vscode-uri';
 import { LsConfiguration } from './config';
 import * as protocol from './protocol';
-import { coalesce } from './util/arrays';
 import { isMarkdownFile, looksLikeMarkdownPath } from './util/file';
 import { Limiter } from './util/limiter';
 import { ResourceMap } from './util/resourceMap';
@@ -18,6 +16,70 @@ import { Schemes } from './util/schemes';
 
 declare const TextDecoder: any;
 
+class VsCodeDocument implements md.ITextDocument {
+
+	private inMemoryDoc?: TextDocument;
+	private onDiskDoc?: TextDocument;
+
+	readonly uri: string;
+
+	constructor(uri: string, init: { inMemoryDoc: TextDocument });
+	constructor(uri: string, init: { onDiskDoc: TextDocument });
+	constructor(uri: string, init: { inMemoryDoc?: TextDocument; onDiskDoc?: TextDocument }) {
+		this.uri = uri;
+		this.inMemoryDoc = init?.inMemoryDoc;
+		this.onDiskDoc = init?.onDiskDoc;
+	}
+
+	get version(): number {
+		return this.inMemoryDoc?.version ?? this.onDiskDoc?.version ?? 0;
+	}
+
+	get lineCount(): number {
+		return this.inMemoryDoc?.lineCount ?? this.onDiskDoc?.lineCount ?? 0;
+	}
+
+	getText(range?: Range): string {
+		if (this.inMemoryDoc) {
+			return this.inMemoryDoc.getText(range);
+		}
+
+		if (this.onDiskDoc) {
+			return this.onDiskDoc.getText(range);
+		}
+
+		throw new Error('Document has been closed');
+	}
+
+	positionAt(offset: number): Position {
+		if (this.inMemoryDoc) {
+			return this.inMemoryDoc.positionAt(offset);
+		}
+
+		if (this.onDiskDoc) {
+			return this.onDiskDoc.positionAt(offset);
+		}
+
+		throw new Error('Document has been closed');
+	}
+
+	hasInMemoryDoc(): boolean {
+		return !!this.inMemoryDoc;
+	}
+
+	isDetached(): boolean {
+		return !this.onDiskDoc && !this.inMemoryDoc;
+	}
+
+	setInMemoryDoc(doc: TextDocument | undefined) {
+		this.inMemoryDoc = doc;
+	}
+
+	setOnDiskDoc(doc: TextDocument | undefined) {
+		this.onDiskDoc = doc;
+	}
+}
+
 export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 
 	private readonly _onDidCreateMarkdownDocument = new Emitter();
@@ -29,14 +91,14 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 	private readonly _onDidDeleteMarkdownDocument = new Emitter();
 	public readonly onDidDeleteMarkdownDocument = this._onDidDeleteMarkdownDocument.event;
 
-	private readonly _documentCache = new ResourceMap();
+	private readonly _documentCache = new ResourceMap();
 
 	private readonly _utf8Decoder = new TextDecoder('utf-8');
 
 	private _watcherPool = 0;
 	private readonly _watchers = new Map;
 		readonly onDidCreate: Emitter;
 		readonly onDidDelete: Emitter;
@@ -50,45 +112,117 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 		private readonly logger: md.ILogger,
 	) {
 		documents.onDidOpen(e => {
-			this._documentCache.delete(URI.parse(e.document.uri));
-			if (this.isRelevantMarkdownDocument(e.document)) {
-				this._onDidCreateMarkdownDocument.fire(e.document);
+			if (!this.isRelevantMarkdownDocument(e.document)) {
+				return;
+			}
+
+			this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.TextDocument.onDidOpen', { document: e.document.uri });
+
+			const uri = URI.parse(e.document.uri);
+			const doc = this._documentCache.get(uri);
+
+			if (doc) {
+				// File already existed on disk
+				doc.setInMemoryDoc(e.document);
+
+				// The content visible to the language service may have changed since the in-memory doc
+				// may differ from the one on-disk. To be safe we always fire a change event.
+				this._onDidChangeMarkdownDocument.fire(doc);
+			} else {
+				// We're creating the file for the first time
+				const doc = new VsCodeDocument(e.document.uri, { inMemoryDoc: e.document });
+				this._documentCache.set(uri, doc);
+				this._onDidCreateMarkdownDocument.fire(doc);
 			}
 		});
 
 		documents.onDidChangeContent(e => {
-			if (this.isRelevantMarkdownDocument(e.document)) {
-				this._onDidChangeMarkdownDocument.fire(e.document);
+			if (!this.isRelevantMarkdownDocument(e.document)) {
+				return;
+			}
+
+			this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.TextDocument.onDidChanceContent', { document: e.document.uri });
+
+			const uri = URI.parse(e.document.uri);
+			const entry = this._documentCache.get(uri);
+			if (entry) {
+				entry.setInMemoryDoc(e.document);
+				this._onDidChangeMarkdownDocument.fire(entry);
 			}
 		});
 
-		documents.onDidClose(e => {
-			this._documentCache.delete(URI.parse(e.document.uri));
+		documents.onDidClose(async e => {
+			if (!this.isRelevantMarkdownDocument(e.document)) {
+				return;
+			}
+
+			this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.TextDocument.onDidClose', { document: e.document.uri });
+
+			const uri = URI.parse(e.document.uri);
+			const doc = this._documentCache.get(uri);
+			if (!doc) {
+				// Document was never opened
+				return;
+			}
+
+			doc.setInMemoryDoc(undefined);
+			if (doc.isDetached()) {
+				// The document has been fully closed
+				this.doDeleteDocument(uri);
+				return;
+			}
+
+			// Check that if file has been deleted on disk.
+			// This can happen when directories are renamed / moved. VS Code's file system watcher does not
+			// notify us when this happens.
+			if (!(await this.statBypassingCache(uri))) {
+				if (this._documentCache.get(uri) === doc && !doc.hasInMemoryDoc()) {
+					this.doDeleteDocument(uri);
+					return;
+				}
+			}
+
+			// The document still exists on disk
+			// To be safe, tell the service that the document has changed because the
+			// in-memory doc contents may be different than the disk doc contents.
+			this._onDidChangeMarkdownDocument.fire(doc);
 		});
 
 		connection.onDidChangeWatchedFiles(async ({ changes }) => {
 			for (const change of changes) {
 				const resource = URI.parse(change.uri);
-				this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: onDidChangeWatchedFiles', `${change.type}: ${resource}`);
+				this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.onDidChangeWatchedFiles', { type: change.type, resource: resource.toString() });
 				switch (change.type) {
 					case FileChangeType.Changed: {
-						this._documentCache.delete(resource);
-						const document = await this.openMarkdownDocument(resource);
-						if (document) {
-							this._onDidChangeMarkdownDocument.fire(document);
+						const entry = this._documentCache.get(resource);
+						if (entry) {
+							// Refresh the on-disk state
+							const document = await this.openMarkdownDocumentFromFs(resource);
+							if (document) {
+								this._onDidChangeMarkdownDocument.fire(document);
+							}
 						}
 						break;
 					}
 					case FileChangeType.Created: {
-						const document = await this.openMarkdownDocument(resource);
-						if (document) {
-							this._onDidCreateMarkdownDocument.fire(document);
+						const entry = this._documentCache.get(resource);
+						if (entry) {
+							// Create or update the on-disk state
+							const document = await this.openMarkdownDocumentFromFs(resource);
+							if (document) {
+								this._onDidCreateMarkdownDocument.fire(document);
+							}
 						}
 						break;
 					}
 					case FileChangeType.Deleted: {
-						this._documentCache.delete(resource);
-						this._onDidDeleteMarkdownDocument.fire(resource);
+						const entry = this._documentCache.get(resource);
+						if (entry) {
+							entry.setOnDiskDoc(undefined);
+							if (entry.isDetached()) {
+								this.doDeleteDocument(resource);
+							}
+						}
 						break;
 					}
 				}
@@ -96,7 +230,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 		});
 
 		connection.onRequest(protocol.fs_watcher_onChange, params => {
-			this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: fs_watcher_onChange', `${params.kind}: ${params.uri}`);
+			this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.fs_watcher_onChange', { kind: params.kind, uri: params.uri });
 
 			const watcher = this._watchers.get(params.id);
 			if (!watcher) {
@@ -128,29 +262,35 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 	}
 
 	async getAllMarkdownDocuments(): Promise> {
+		// Add opened files (such as untitled files)
+		const openTextDocumentResults = this.documents.all()
+			.filter(doc => this.isRelevantMarkdownDocument(doc));
+
+		const allDocs = new ResourceMap();
+		for (const doc of openTextDocumentResults) {
+			allDocs.set(URI.parse(doc.uri), doc);
+		}
+
+		// And then add files on disk
 		const maxConcurrent = 20;
-
-		const foundFiles = new ResourceMap();
 		const limiter = new Limiter(maxConcurrent);
-
-		// Add files on disk
 		const resources = await this.connection.sendRequest(protocol.findMarkdownFilesInWorkspace, {});
-		const onDiskResults = await Promise.all(resources.map(strResource => {
+		await Promise.all(resources.map(strResource => {
 			return limiter.queue(async () => {
 				const resource = URI.parse(strResource);
+				if (allDocs.has(resource)) {
+					return;
+				}
+
 				const doc = await this.openMarkdownDocument(resource);
 				if (doc) {
-					foundFiles.set(resource);
+					allDocs.set(resource, doc);
 				}
 				return doc;
 			});
 		}));
 
-		// Add opened files (such as untitled files)
-		const openTextDocumentResults = await Promise.all(this.documents.all()
-			.filter(doc => !foundFiles.has(URI.parse(doc.uri)) && this.isRelevantMarkdownDocument(doc)));
-
-		return coalesce([...onDiskResults, ...openTextDocumentResults]);
+		return allDocs.values();
 	}
 
 	hasMarkdownDocument(resource: URI): boolean {
@@ -165,10 +305,21 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 
 		const matchingDocument = this.documents.get(resource.toString());
 		if (matchingDocument) {
-			this._documentCache.set(resource, matchingDocument);
-			return matchingDocument;
+			let entry = this._documentCache.get(resource);
+			if (entry) {
+				entry.setInMemoryDoc(matchingDocument);
+			} else {
+				entry = new VsCodeDocument(resource.toString(), { inMemoryDoc: matchingDocument });
+				this._documentCache.set(resource, entry);
+			}
+
+			return entry;
 		}
 
+		return this.openMarkdownDocumentFromFs(resource);
+	}
+
+	private async openMarkdownDocumentFromFs(resource: URI): Promise {
 		if (!looksLikeMarkdownPath(this.config, resource)) {
 			return undefined;
 		}
@@ -180,7 +331,9 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 
 			// We assume that markdown is in UTF-8
 			const text = this._utf8Decoder.decode(bytes);
-			const doc = TextDocument.create(resource.toString(), 'markdown', 0, text);
+			const doc = new VsCodeDocument(resource.toString(), {
+				onDiskDoc: TextDocument.create(resource.toString(), 'markdown', 0, text)
+			});
 			this._documentCache.set(resource, doc);
 			return doc;
 		} catch (e) {
@@ -189,19 +342,28 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 	}
 
 	async stat(resource: URI): Promise {
-		this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: stat', `${resource}`);
-		if (this._documentCache.has(resource) || this.documents.get(resource.toString())) {
+		this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.stat', { resource: resource.toString() });
+		if (this._documentCache.has(resource)) {
 			return { isDirectory: false };
 		}
-		return this.connection.sendRequest(protocol.fs_stat, { uri: resource.toString() });
+		return this.statBypassingCache(resource);
+	}
+
+	private async statBypassingCache(resource: URI): Promise {
+		const uri = resource.toString();
+		if (this.documents.get(uri)) {
+			return { isDirectory: false };
+		}
+		const fsResult = await this.connection.sendRequest(protocol.fs_stat, { uri });
+		return fsResult ?? undefined; // Force convert null to undefined
 	}
 
 	async readDirectory(resource: URI): Promise<[string, md.FileStat][]> {
-		this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: readDir', `${resource}`);
+		this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.readDir', { resource: resource.toString() });
 		return this.connection.sendRequest(protocol.fs_readDirectory, { uri: resource.toString() });
 	}
 
-	getContainingDocument(resource: URI): ContainingDocumentContext | undefined {
+	getContainingDocument(resource: URI): md.ContainingDocumentContext | undefined {
 		if (resource.scheme === Schemes.notebookCell) {
 			const nb = this.notebooks.findNotebookDocumentForCell(resource.toString());
 			if (nb) {
@@ -214,9 +376,9 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 		return undefined;
 	}
 
-	watchFile(resource: URI, options: FileWatcherOptions): IFileSystemWatcher {
+	watchFile(resource: URI, options: md.FileWatcherOptions): md.IFileSystemWatcher {
 		const id = this._watcherPool++;
-		this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: watchFile', `(${id}) ${resource}`);
+		this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.watchFile', { id, resource: resource.toString() });
 
 		const entry = {
 			resource,
@@ -231,6 +393,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 			id,
 			uri: resource.toString(),
 			options,
+			watchParentDirs: true,
 		});
 
 		return {
@@ -238,7 +401,7 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 			onDidChange: entry.onDidChange.event,
 			onDidDelete: entry.onDidDelete.event,
 			dispose: () => {
-				this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: disposeWatcher', `(${id}) ${resource}`);
+				this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.disposeWatcher', { id, resource: resource.toString() });
 				this.connection.sendRequest(protocol.fs_watcher_delete, { id });
 				this._watchers.delete(id);
 			}
@@ -248,4 +411,11 @@ export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
 	private isRelevantMarkdownDocument(doc: TextDocument) {
 		return isMarkdownFile(doc) && URI.parse(doc.uri).scheme !== 'vscode-bulkeditpreview';
 	}
+
+	private doDeleteDocument(uri: URI) {
+		this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace.deleteDocument', { document: uri.toString() });
+
+		this._documentCache.delete(uri);
+		this._onDidDeleteMarkdownDocument.fire(uri);
+	}
 }
diff --git a/extensions/markdown-language-features/server/tsconfig.json b/extensions/markdown-language-features/server/tsconfig.json
index 8b4aedde27..0a73af08ed 100644
--- a/extensions/markdown-language-features/server/tsconfig.json
+++ b/extensions/markdown-language-features/server/tsconfig.json
@@ -1,7 +1,12 @@
 {
 	"extends": "../../tsconfig.base.json",
 	"compilerOptions": {
-		"outDir": "./out"
+		"outDir": "./out",
+		"lib": [
+			"ES2020",
+			"ES2021.Promise",
+			"WebWorker"
+		]
 	},
 	"include": [
 		"src/**/*"
diff --git a/extensions/markdown-language-features/server/yarn.lock b/extensions/markdown-language-features/server/yarn.lock
index d0d31f5998..b801eb22eb 100644
--- a/extensions/markdown-language-features/server/yarn.lock
+++ b/extensions/markdown-language-features/server/yarn.lock
@@ -3,67 +3,144 @@
 
 
 "@types/node@16.x":
-  version "16.11.43"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.43.tgz#555e5a743f76b6b897d47f945305b618525ddbe6"
-  integrity sha512-GqWykok+3uocgfAJM8imbozrqLnPyTrpFlrryURQlw1EesPUCx5XxTiucWDSFF9/NUEXDuD4bnvHm8xfVGWTpQ==
+  version "16.18.3"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
+  integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==
+
+"@vscode/l10n@^0.0.10":
+  version "0.0.10"
+  resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.10.tgz#9c513107c690c0dd16e3ec61e453743de15ebdb0"
+  integrity sha512-E1OCmDcDWa0Ya7vtSjp/XfHFGqYJfh+YPC1RkATU71fTac+j1JjCcB3qwSzmlKAighx2WxhLlfhS0RwAN++PFQ==
+
+"@vscode/l10n@^0.0.11":
+  version "0.0.11"
+  resolved "https://registry.yarnpkg.com/@vscode/l10n/-/l10n-0.0.11.tgz#325d7beb2cfb87162bc624d16c4d546de6a73b72"
+  integrity sha512-ukOMWnCg1tCvT7WnDfsUKQOFDQGsyR5tNgRpwmqi+5/vzU3ghdDXzvIM4IOPdSb3OeSsBNvmSL8nxIVOqi2WXA==
+
+boolbase@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
+  integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
+
+css-select@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6"
+  integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==
+  dependencies:
+    boolbase "^1.0.0"
+    css-what "^6.1.0"
+    domhandler "^5.0.2"
+    domutils "^3.0.1"
+    nth-check "^2.0.1"
+
+css-what@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
+  integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
+
+dom-serializer@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53"
+  integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==
+  dependencies:
+    domelementtype "^2.3.0"
+    domhandler "^5.0.2"
+    entities "^4.2.0"
+
+domelementtype@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
+  integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
+
+domhandler@^5.0.2, domhandler@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31"
+  integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==
+  dependencies:
+    domelementtype "^2.3.0"
+
+domutils@^3.0.1:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e"
+  integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==
+  dependencies:
+    dom-serializer "^2.0.0"
+    domelementtype "^2.3.0"
+    domhandler "^5.0.3"
+
+entities@^4.2.0:
+  version "4.5.0"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
+  integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+he@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+node-html-parser@^6.1.5:
+  version "6.1.5"
+  resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.5.tgz#c819dceb13a10a7642ff92f94f870b4f77968097"
+  integrity sha512-fAaM511feX++/Chnhe475a0NHD8M7AxDInsqQpz6x63GRF7xYNdS8Vo5dKsIVPgsOvG7eioRRTZQnWBrhDHBSg==
+  dependencies:
+    css-select "^5.1.0"
+    he "1.2.0"
+
+nth-check@^2.0.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
+  integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
+  dependencies:
+    boolbase "^1.0.0"
 
 picomatch@^2.3.1:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
 
-vscode-jsonrpc@8.0.2-next.1:
-  version "8.0.2-next.1"
-  resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6"
-  integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA==
+vscode-jsonrpc@8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.1.0.tgz#cb9989c65e219e18533cc38e767611272d274c94"
+  integrity sha512-6TDy/abTQk+zDGYazgbIPc+4JoXdwC8NHU9Pbn4UJP1fehUyZmM4RHp5IthX7A6L5KS30PRui+j+tbbMMMafdw==
 
-vscode-languageserver-protocol@3.17.2-next.6:
-  version "3.17.2-next.6"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc"
-  integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA==
+vscode-languageserver-protocol@3.17.3:
+  version "3.17.3"
+  resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.3.tgz#6d0d54da093f0c0ee3060b81612cce0f11060d57"
+  integrity sha512-924/h0AqsMtA5yK22GgMtCYiMdCOtWTSGgUOkgEDX+wk2b0x4sAfLiO4NxBxqbiVtz7K7/1/RgVrVI0NClZwqA==
   dependencies:
-    vscode-jsonrpc "8.0.2-next.1"
-    vscode-languageserver-types "3.17.2-next.2"
+    vscode-jsonrpc "8.1.0"
+    vscode-languageserver-types "3.17.3"
 
-vscode-languageserver-textdocument@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.5.tgz#838769940ece626176ec5d5a2aa2d0aa69f5095c"
-  integrity sha512-1ah7zyQjKBudnMiHbZmxz5bYNM9KKZYz+5VQLj+yr8l+9w3g+WAhCkUkWbhMEdC5u0ub4Ndiye/fDyS8ghIKQg==
+vscode-languageserver-textdocument@^1.0.8:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz#9eae94509cbd945ea44bca8dcfe4bb0c15bb3ac0"
+  integrity sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==
 
-vscode-languageserver-types@3.17.2-next.2:
-  version "3.17.2-next.2"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596"
-  integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w==
+vscode-languageserver-types@3.17.3, vscode-languageserver-types@^3.17.3:
+  version "3.17.3"
+  resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz#72d05e47b73be93acb84d6e311b5786390f13f64"
+  integrity sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==
 
-vscode-languageserver-types@^3.17.1:
-  version "3.17.1"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16"
-  integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ==
-
-vscode-languageserver@^8.0.2-next.5`:
-  version "8.0.2-next.5"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2-next.5.tgz#39a2dd4c504fb88042375e7ac706a714bdaab4e5"
-  integrity sha512-2ZDb7O/4atS9mJKufPPz637z+51kCyZfgnobFW5eSrUdS3c0UB/nMS4Ng1EavYTX84GVaVMKCrmP0f2ceLmR0A==
+vscode-languageserver@^8.1.0:
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.1.0.tgz#5024253718915d84576ce6662dd46a791498d827"
+  integrity sha512-eUt8f1z2N2IEUDBsKaNapkz7jl5QpskN2Y0G01T/ItMxBxw1fJwvtySGB9QMecatne8jFIWJGWI61dWjyTLQsw==
   dependencies:
-    vscode-languageserver-protocol "3.17.2-next.6"
+    vscode-languageserver-protocol "3.17.3"
 
-vscode-markdown-languageservice@^0.0.0-alpha.12:
-  version "0.0.0-alpha.12"
-  resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.12.tgz#5a3c7559969c3cb455d508d48129c8e221589630"
-  integrity sha512-9dJ/GL6A9UUOcB1TpvgsbcwqsYjnxHx4jxDaqeZZEMWFSUySfp0PAn1ge2S2Qj00zypvsu0eCTGUNd56G1/BNQ==
+vscode-markdown-languageservice@^0.4.0-alpha.2:
+  version "0.4.0-alpha.2"
+  resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.4.0-alpha.2.tgz#2edbd157ada35922ec762a7d6550d87a0b78959a"
+  integrity sha512-m2x+3dezndpDqfviCzsfUgAySVhoN8266OnasPpPlIZIho3a/JcUmFo6GZDlWBtOQXd9FT+TSAC2BPDCtWlhPQ==
   dependencies:
+    "@vscode/l10n" "^0.0.10"
+    node-html-parser "^6.1.5"
     picomatch "^2.3.1"
-    vscode-languageserver-textdocument "^1.0.5"
-    vscode-languageserver-types "^3.17.1"
-    vscode-nls "^5.0.1"
-    vscode-uri "^3.0.3"
+    vscode-languageserver-textdocument "^1.0.8"
+    vscode-languageserver-types "^3.17.3"
+    vscode-uri "^3.0.7"
 
-vscode-nls@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2"
-  integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A==
-
-vscode-uri@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84"
-  integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==
+vscode-uri@^3.0.7:
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.7.tgz#6d19fef387ee6b46c479e5fb00870e15e58c1eb8"
+  integrity sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==
diff --git a/extensions/markdown-language-features/src/client.ts b/extensions/markdown-language-features/src/client/client.ts
similarity index 57%
rename from extensions/markdown-language-features/src/client.ts
rename to extensions/markdown-language-features/src/client/client.ts
index 4d86fc36d9..be0409ad07 100644
--- a/extensions/markdown-language-features/src/client.ts
+++ b/extensions/markdown-language-features/src/client/client.ts
@@ -5,18 +5,42 @@
 
 import * as vscode from 'vscode';
 import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType } from 'vscode-languageclient';
-import * as nls from 'vscode-nls';
-import { IMdParser } from './markdownEngine';
+import { IMdParser } from '../markdownEngine';
 import * as proto from './protocol';
-import { looksLikeMarkdownPath, markdownFileExtensions } from './util/file';
-import { IMdWorkspace } from './workspace';
+import { looksLikeMarkdownPath, markdownFileExtensions } from '../util/file';
+import { VsCodeMdWorkspace } from './workspace';
+import { FileWatcherManager } from './fileWatchingManager';
+import { IDisposable } from '../util/dispose';
 
-const localize = nls.loadMessageBundle();
 
 export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient;
 
+export class MdLanguageClient implements IDisposable {
 
-export async function startClient(factory: LanguageClientConstructor, workspace: IMdWorkspace, parser: IMdParser): Promise {
+	constructor(
+		private readonly _client: BaseLanguageClient,
+		private readonly _workspace: VsCodeMdWorkspace,
+	) { }
+
+	dispose(): void {
+		this._client.stop();
+		this._workspace.dispose();
+	}
+
+	resolveLinkTarget(linkText: string, uri: vscode.Uri): Promise {
+		return this._client.sendRequest(proto.resolveLinkTarget, { linkText, uri: uri.toString() });
+	}
+
+	getEditForFileRenames(files: ReadonlyArray<{ oldUri: string; newUri: string }>, token: vscode.CancellationToken) {
+		return this._client.sendRequest(proto.getEditForFileRenames, files, token);
+	}
+
+	getReferencesToFileInWorkspace(resource: vscode.Uri, token: vscode.CancellationToken) {
+		return this._client.sendRequest(proto.getReferencesToFileInWorkspace, { uri: resource.toString() }, token);
+	}
+}
+
+export async function startClient(factory: LanguageClientConstructor, parser: IMdParser): Promise {
 
 	const mdFileGlob = `**/*.{${markdownFileExtensions.join(',')}}`;
 
@@ -28,19 +52,18 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
 		},
 		initializationOptions: {
 			markdownFileExtensions,
+			i10lLocation: vscode.l10n.uri?.toJSON(),
 		},
 		diagnosticPullOptions: {
 			onChange: true,
-			onSave: true,
 			onTabs: true,
 			match(_documentSelector, resource) {
 				return looksLikeMarkdownPath(resource);
 			},
 		},
-
 	};
 
-	const client = factory('markdown', localize('markdownServer.name', 'Markdown Language Server'), clientOptions);
+	const client = factory('markdown', vscode.l10n.t("Markdown Language Server"), clientOptions);
 
 	client.registerProposedFeatures();
 
@@ -57,6 +80,8 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
 		});
 	}
 
+	const workspace = new VsCodeMdWorkspace();
+
 	client.onRequest(proto.parse, async (e) => {
 		const uri = vscode.Uri.parse(e.uri);
 		const doc = await workspace.getOrLoadMarkdownDocument(uri);
@@ -92,23 +117,36 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
 		return (await vscode.workspace.findFiles(mdFileGlob, '**/node_modules/**')).map(x => x.toString());
 	});
 
-	const watchers = new Map();
+	const watchers = new FileWatcherManager();
 
 	client.onRequest(proto.fs_watcher_create, async (params): Promise => {
 		const id = params.id;
-		const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(vscode.Uri.parse(params.uri), '*'), params.options.ignoreCreate, params.options.ignoreChange, params.options.ignoreDelete);
-		watchers.set(id, watcher);
-		watcher.onDidCreate(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'create' }); });
-		watcher.onDidChange(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'change' }); });
-		watcher.onDidDelete(() => { client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind: 'delete' }); });
+		const uri = vscode.Uri.parse(params.uri);
+
+		const sendWatcherChange = (kind: 'create' | 'change' | 'delete') => {
+			client.sendRequest(proto.fs_watcher_onChange, { id, uri: params.uri, kind });
+		};
+
+		watchers.create(id, uri, params.watchParentDirs, {
+			create: params.options.ignoreCreate ? undefined : () => sendWatcherChange('create'),
+			change: params.options.ignoreChange ? undefined : () => sendWatcherChange('change'),
+			delete: params.options.ignoreDelete ? undefined : () => sendWatcherChange('delete'),
+		});
 	});
 
 	client.onRequest(proto.fs_watcher_delete, async (params): Promise => {
-		watchers.get(params.id)?.dispose();
 		watchers.delete(params.id);
 	});
 
+	vscode.commands.registerCommand('vscodeMarkdownLanguageservice.open', (uri, args) => {
+		return vscode.commands.executeCommand('vscode.open', uri, args);
+	});
+
+	vscode.commands.registerCommand('vscodeMarkdownLanguageservice.rename', (uri, pos) => {
+		return vscode.commands.executeCommand('editor.action.rename', [vscode.Uri.from(uri), new vscode.Position(pos.line, pos.character)]);
+	});
+
 	await client.start();
 
-	return client;
+	return new MdLanguageClient(client, workspace);
 }
diff --git a/extensions/markdown-language-features/src/client/fileWatchingManager.ts b/extensions/markdown-language-features/src/client/fileWatchingManager.ts
new file mode 100644
index 0000000000..f6e81fa9da
--- /dev/null
+++ b/extensions/markdown-language-features/src/client/fileWatchingManager.ts
@@ -0,0 +1,98 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { Utils } from 'vscode-uri';
+import { disposeAll, IDisposable } from '../util/dispose';
+import { ResourceMap } from '../util/resourceMap';
+import { Schemes } from '../util/schemes';
+
+type DirWatcherEntry = {
+	readonly uri: vscode.Uri;
+	readonly listeners: IDisposable[];
+};
+
+
+export class FileWatcherManager {
+
+	private readonly _fileWatchers = new Map();
+
+	private readonly _dirWatchers = new ResourceMap<{
+		readonly watcher: vscode.FileSystemWatcher;
+		refCount: number;
+	}>();
+
+	create(id: number, uri: vscode.Uri, watchParentDirs: boolean, listeners: { create?: () => void; change?: () => void; delete?: () => void }): void {
+		const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(uri, '*'), !listeners.create, !listeners.change, !listeners.delete);
+		const parentDirWatchers: DirWatcherEntry[] = [];
+		this._fileWatchers.set(id, { watcher, dirWatchers: parentDirWatchers });
+
+		if (listeners.create) { watcher.onDidCreate(listeners.create); }
+		if (listeners.change) { watcher.onDidChange(listeners.change); }
+		if (listeners.delete) { watcher.onDidDelete(listeners.delete); }
+
+		if (watchParentDirs && uri.scheme !== Schemes.untitled) {
+			// We need to watch the parent directories too for when these are deleted / created
+			for (let dirUri = Utils.dirname(uri); dirUri.path.length > 1; dirUri = Utils.dirname(dirUri)) {
+				const dirWatcher: DirWatcherEntry = { uri: dirUri, listeners: [] };
+
+				let parentDirWatcher = this._dirWatchers.get(dirUri);
+				if (!parentDirWatcher) {
+					const glob = new vscode.RelativePattern(Utils.dirname(dirUri), Utils.basename(dirUri));
+					const parentWatcher = vscode.workspace.createFileSystemWatcher(glob, !listeners.create, true, !listeners.delete);
+					parentDirWatcher = { refCount: 0, watcher: parentWatcher };
+					this._dirWatchers.set(dirUri, parentDirWatcher);
+				}
+				parentDirWatcher.refCount++;
+
+				if (listeners.create) {
+					dirWatcher.listeners.push(parentDirWatcher.watcher.onDidCreate(async () => {
+						// Just because the parent dir was created doesn't mean our file was created
+						try {
+							const stat = await vscode.workspace.fs.stat(uri);
+							if (stat.type === vscode.FileType.File) {
+								listeners.create!();
+							}
+						} catch {
+							// Noop
+						}
+					}));
+				}
+
+				if (listeners.delete) {
+					// When the parent dir is deleted, consider our file deleted too
+					// TODO: this fires if the file previously did not exist and then the parent is deleted
+					dirWatcher.listeners.push(parentDirWatcher.watcher.onDidDelete(listeners.delete));
+				}
+
+				parentDirWatchers.push(dirWatcher);
+			}
+		}
+	}
+
+	delete(id: number): void {
+		const entry = this._fileWatchers.get(id);
+		if (entry) {
+			for (const dirWatcher of entry.dirWatchers) {
+				disposeAll(dirWatcher.listeners);
+
+				const dirWatcherEntry = this._dirWatchers.get(dirWatcher.uri);
+				if (dirWatcherEntry) {
+					if (--dirWatcherEntry.refCount <= 0) {
+						dirWatcherEntry.watcher.dispose();
+						this._dirWatchers.delete(dirWatcher.uri);
+					}
+				}
+			}
+
+			entry.watcher.dispose();
+		}
+
+		this._fileWatchers.delete(id);
+	}
+}
diff --git a/extensions/markdown-language-features/src/client/inMemoryDocument.ts b/extensions/markdown-language-features/src/client/inMemoryDocument.ts
new file mode 100644
index 0000000000..f200ae6116
--- /dev/null
+++ b/extensions/markdown-language-features/src/client/inMemoryDocument.ts
@@ -0,0 +1,20 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { ITextDocument } from '../types/textDocument';
+
+export class InMemoryDocument implements ITextDocument {
+
+	constructor(
+		public readonly uri: vscode.Uri,
+		private readonly _contents: string,
+		public readonly version = 0,
+	) { }
+
+	getText(): string {
+		return this._contents;
+	}
+}
diff --git a/extensions/markdown-language-features/src/protocol.ts b/extensions/markdown-language-features/src/client/protocol.ts
similarity index 64%
rename from extensions/markdown-language-features/src/protocol.ts
rename to extensions/markdown-language-features/src/client/protocol.ts
index 0eec124bd5..b406aefe80 100644
--- a/extensions/markdown-language-features/src/protocol.ts
+++ b/extensions/markdown-language-features/src/client/protocol.ts
@@ -3,11 +3,18 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-import Token = require('markdown-it/lib/token');
-import { RequestType } from 'vscode-languageclient';
+import type Token = require('markdown-it/lib/token');
+import * as vscode from 'vscode';
+import { FileRename, RequestType } from 'vscode-languageclient';
 import type * as lsp from 'vscode-languageserver-types';
 import type * as md from 'vscode-markdown-languageservice';
 
+
+export type ResolvedDocumentLinkTarget =
+	| { readonly kind: 'file'; readonly uri: vscode.Uri; position?: lsp.Position; fragment?: string }
+	| { readonly kind: 'folder'; readonly uri: vscode.Uri }
+	| { readonly kind: 'external'; readonly uri: vscode.Uri };
+
 //#region From server
 export const parse = new RequestType<{ uri: string }, Token[], any>('markdown/parse');
 
@@ -15,7 +22,7 @@ export const fs_readFile = new RequestType<{ uri: string }, number[], any>('mark
 export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory');
 export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat');
 
-export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any>('markdown/fs/watcher/create');
+export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions; watchParentDirs: boolean }, void, any>('markdown/fs/watcher/create');
 export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete');
 
 export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace');
@@ -23,6 +30,9 @@ export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('
 
 //#region To server
 export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace');
+export const getEditForFileRenames = new RequestType, { participatingRenames: readonly FileRename[]; edit: lsp.WorkspaceEdit }, any>('markdown/getEditForFileRenames');
 
 export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange');
+
+export const resolveLinkTarget = new RequestType<{ linkText: string; uri: string }, ResolvedDocumentLinkTarget, any>('markdown/resolveLinkTarget');
 //#endregion
diff --git a/extensions/markdown-language-features/src/client/workspace.ts b/extensions/markdown-language-features/src/client/workspace.ts
new file mode 100644
index 0000000000..4878b4d511
--- /dev/null
+++ b/extensions/markdown-language-features/src/client/workspace.ts
@@ -0,0 +1,80 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { ITextDocument } from '../types/textDocument';
+import { Disposable } from '../util/dispose';
+import { isMarkdownFile, looksLikeMarkdownPath } from '../util/file';
+import { InMemoryDocument } from './inMemoryDocument';
+import { ResourceMap } from '../util/resourceMap';
+
+/**
+ * Provides set of markdown files known to VS Code.
+ *
+ * This includes both opened text documents and markdown files in the workspace.
+ */
+export class VsCodeMdWorkspace extends Disposable {
+
+	private _watcher: vscode.FileSystemWatcher | undefined;
+
+	private readonly _documentCache = new ResourceMap();
+
+	private readonly _utf8Decoder = new TextDecoder('utf-8');
+
+	constructor() {
+		super();
+
+		this._watcher = this._register(vscode.workspace.createFileSystemWatcher('**/*.md'));
+
+		this._register(this._watcher.onDidChange(async resource => {
+			this._documentCache.delete(resource);
+		}));
+
+		this._register(this._watcher.onDidDelete(resource => {
+			this._documentCache.delete(resource);
+		}));
+
+		this._register(vscode.workspace.onDidOpenTextDocument(e => {
+			this._documentCache.delete(e.uri);
+		}));
+
+		this._register(vscode.workspace.onDidCloseTextDocument(e => {
+			this._documentCache.delete(e.uri);
+		}));
+	}
+
+	private _isRelevantMarkdownDocument(doc: vscode.TextDocument) {
+		return isMarkdownFile(doc) && doc.uri.scheme !== 'vscode-bulkeditpreview';
+	}
+
+	public async getOrLoadMarkdownDocument(resource: vscode.Uri): Promise {
+		const existing = this._documentCache.get(resource);
+		if (existing) {
+			return existing;
+		}
+
+		const matchingDocument = vscode.workspace.textDocuments.find((doc) => this._isRelevantMarkdownDocument(doc) && doc.uri.toString() === resource.toString());
+		if (matchingDocument) {
+			this._documentCache.set(resource, matchingDocument);
+			return matchingDocument;
+		}
+
+		if (!looksLikeMarkdownPath(resource)) {
+			return undefined;
+		}
+
+		try {
+			const bytes = await vscode.workspace.fs.readFile(resource);
+
+			// We assume that markdown is in UTF-8
+			const text = this._utf8Decoder.decode(bytes);
+			const doc = new InMemoryDocument(resource, text, 0);
+			this._documentCache.set(resource, doc);
+			return doc;
+		} catch {
+			return undefined;
+		}
+	}
+}
diff --git a/extensions/markdown-language-features/src/commandManager.ts b/extensions/markdown-language-features/src/commandManager.ts
index 64c6713a0c..8488136317 100644
--- a/extensions/markdown-language-features/src/commandManager.ts
+++ b/extensions/markdown-language-features/src/commandManager.ts
@@ -13,28 +13,28 @@ export interface Command {
 }
 
 export class CommandManager {
-	private readonly commands = new Map();
+	private readonly _commands = new Map();
 
 	public dispose() {
-		for (const registration of this.commands.values()) {
+		for (const registration of this._commands.values()) {
 			registration.dispose();
 		}
-		this.commands.clear();
+		this._commands.clear();
 	}
 
 	public register(command: T): vscode.Disposable {
-		this.registerCommand(command.id, command.execute, command);
+		this._registerCommand(command.id, command.execute, command);
 		return new vscode.Disposable(() => {
-			this.commands.delete(command.id);
+			this._commands.delete(command.id);
 		});
 	}
 
 	// {{SQL CARBON EDIT}}
-	private registerCommand(id: string, impl: (...args: any[]) => any, thisArg?: any) {
-		if (this.commands.has(id)) {
+	private _registerCommand(id: string, impl: (...args: any[]) => any, thisArg?: any) {
+		if (this._commands.has(id)) {
 			return;
 		}
 
-		this.commands.set(id, vscode.commands.registerCommand(id, impl, thisArg));
+		this._commands.set(id, vscode.commands.registerCommand(id, impl, thisArg));
 	}
 }
diff --git a/extensions/markdown-language-features/src/commands/index.ts b/extensions/markdown-language-features/src/commands/index.ts
index b5dc2c136d..330f620abf 100644
--- a/extensions/markdown-language-features/src/commands/index.ts
+++ b/extensions/markdown-language-features/src/commands/index.ts
@@ -3,13 +3,41 @@
  *  Licensed under the Source EULA. See License.txt in the project root for license information.
  *--------------------------------------------------------------------------------------------*/
 
-export { MoveCursorToPositionCommand } from './moveCursorToPosition';
-export { OpenDocumentLinkCommand } from './openDocumentLink';
-export { RefreshPreviewCommand } from './refreshPreview';
-export { ReloadPlugins } from './reloadPlugins';
-export { RenderDocument } from './renderDocument';
-export { ShowLockedPreviewToSideCommand, ShowPreviewCommand, ShowPreviewToSideCommand } from './showPreview';
-export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector';
-export { ShowSourceCommand } from './showSource';
-export { ToggleLockCommand } from './toggleLock';
+import * as vscode from 'vscode';
+import { CommandManager } from '../commandManager';
+import { MarkdownItEngine } from '../markdownEngine';
+import { MarkdownPreviewManager } from '../preview/previewManager';
+import { ContentSecurityPolicyArbiter, PreviewSecuritySelector } from '../preview/security';
+import { TelemetryReporter } from '../telemetryReporter';
+import { InsertLinkFromWorkspace, InsertImageFromWorkspace } from './insertResource';
+import { RefreshPreviewCommand } from './refreshPreview';
+import { ReloadPlugins } from './reloadPlugins';
+import { RenderDocument } from './renderDocument';
+import { ShowLockedPreviewToSideCommand, ShowPreviewCommand, ShowPreviewToSideCommand } from './showPreview';
+import { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector';
+import { ShowSourceCommand } from './showSource';
+import { ToggleLockCommand } from './toggleLock';
 
+export function registerMarkdownCommands(
+	commandManager: CommandManager,
+	previewManager: MarkdownPreviewManager,
+	telemetryReporter: TelemetryReporter,
+	cspArbiter: ContentSecurityPolicyArbiter,
+	engine: MarkdownItEngine,
+): vscode.Disposable {
+	const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager);
+
+	commandManager.register(new ShowPreviewCommand(previewManager, telemetryReporter));
+	commandManager.register(new ShowPreviewToSideCommand(previewManager, telemetryReporter));
+	commandManager.register(new ShowLockedPreviewToSideCommand(previewManager, telemetryReporter));
+	commandManager.register(new ShowSourceCommand(previewManager));
+	commandManager.register(new RefreshPreviewCommand(previewManager, engine));
+	commandManager.register(new ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager));
+	commandManager.register(new ToggleLockCommand(previewManager));
+	commandManager.register(new RenderDocument(engine));
+	commandManager.register(new ReloadPlugins(previewManager, engine));
+	commandManager.register(new InsertLinkFromWorkspace());
+	commandManager.register(new InsertImageFromWorkspace());
+
+	return commandManager;
+}
diff --git a/extensions/markdown-language-features/src/commands/insertResource.ts b/extensions/markdown-language-features/src/commands/insertResource.ts
new file mode 100644
index 0000000000..bb8e2f25f9
--- /dev/null
+++ b/extensions/markdown-language-features/src/commands/insertResource.ts
@@ -0,0 +1,95 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { Utils } from 'vscode-uri';
+import { Command } from '../commandManager';
+import { createUriListSnippet, mediaFileExtensions } from '../languageFeatures/copyFiles/shared';
+import { coalesce } from '../util/arrays';
+import { getParentDocumentUri } from '../util/document';
+import { Schemes } from '../util/schemes';
+
+
+export class InsertLinkFromWorkspace implements Command {
+	public readonly id = 'markdown.editor.insertLinkFromWorkspace';
+
+	public async execute(resources?: vscode.Uri[]) {
+		const activeEditor = vscode.window.activeTextEditor;
+		if (!activeEditor) {
+			return;
+		}
+
+		resources ??= await vscode.window.showOpenDialog({
+			canSelectFiles: true,
+			canSelectFolders: false,
+			canSelectMany: true,
+			openLabel: vscode.l10n.t("Insert link"),
+			title: vscode.l10n.t("Insert link"),
+			defaultUri: getDefaultUri(activeEditor.document),
+		});
+
+		return insertLink(activeEditor, resources ?? [], false);
+	}
+}
+
+export class InsertImageFromWorkspace implements Command {
+	public readonly id = 'markdown.editor.insertImageFromWorkspace';
+
+	public async execute(resources?: vscode.Uri[]) {
+		const activeEditor = vscode.window.activeTextEditor;
+		if (!activeEditor) {
+			return;
+		}
+
+		resources ??= await vscode.window.showOpenDialog({
+			canSelectFiles: true,
+			canSelectFolders: false,
+			canSelectMany: true,
+			filters: {
+				[vscode.l10n.t("Media")]: Array.from(mediaFileExtensions.keys())
+			},
+			openLabel: vscode.l10n.t("Insert image"),
+			title: vscode.l10n.t("Insert image"),
+			defaultUri: getDefaultUri(activeEditor.document),
+		});
+
+		return insertLink(activeEditor, resources ?? [], true);
+	}
+}
+
+function getDefaultUri(document: vscode.TextDocument) {
+	const docUri = getParentDocumentUri(document);
+	if (docUri.scheme === Schemes.untitled) {
+		return vscode.workspace.workspaceFolders?.[0]?.uri;
+	}
+	return Utils.dirname(docUri);
+}
+
+async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsImage: boolean): Promise {
+	if (!selectedFiles.length) {
+		return;
+	}
+
+	const edit = createInsertLinkEdit(activeEditor, selectedFiles, insertAsImage);
+	await vscode.workspace.applyEdit(edit);
+}
+
+function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean) {
+	const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => {
+		const selectionText = activeEditor.document.getText(selection);
+		const snippet = createUriListSnippet(activeEditor.document, selectedFiles, {
+			insertAsMedia,
+			placeholderText: selectionText,
+			placeholderStartIndex: (i + 1) * selectedFiles.length,
+			separator: insertAsMedia ? '\n' : ' ',
+		});
+
+		return snippet ? new vscode.SnippetTextEdit(selection, snippet.snippet) : undefined;
+	}));
+
+	const edit = new vscode.WorkspaceEdit();
+	edit.set(activeEditor.document.uri, snippetEdits);
+	return edit;
+}
diff --git a/extensions/markdown-language-features/src/commands/moveCursorToPosition.ts b/extensions/markdown-language-features/src/commands/moveCursorToPosition.ts
deleted file mode 100644
index 2db2944baf..0000000000
--- a/extensions/markdown-language-features/src/commands/moveCursorToPosition.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import * as vscode from 'vscode';
-import { Command } from '../commandManager';
-
-export class MoveCursorToPositionCommand implements Command {
-	public readonly id = '_markdown.moveCursorToPosition';
-
-	public execute(line: number, character: number) {
-		if (!vscode.window.activeTextEditor) {
-			return;
-		}
-		const position = new vscode.Position(line, character);
-		const selection = new vscode.Selection(position, position);
-		vscode.window.activeTextEditor.revealRange(selection);
-		vscode.window.activeTextEditor.selection = selection;
-	}
-}
diff --git a/extensions/markdown-language-features/src/commands/openDocumentLink.ts b/extensions/markdown-language-features/src/commands/openDocumentLink.ts
deleted file mode 100644
index dff7b05fb0..0000000000
--- a/extensions/markdown-language-features/src/commands/openDocumentLink.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import * as vscode from 'vscode';
-import { Command } from '../commandManager';
-import { MdTableOfContentsProvider } from '../tableOfContents';
-import { openDocumentLink } from '../util/openDocumentLink';
-import { Schemes } from '../util/schemes';
-
-type UriComponents = {
-	readonly scheme?: string;
-	readonly path: string;
-	readonly fragment?: string;
-	readonly authority?: string;
-	readonly query?: string;
-};
-
-export interface OpenDocumentLinkArgs {
-	readonly parts: UriComponents;
-	readonly fragment: string;
-	readonly fromResource: UriComponents;
-}
-
-export class OpenDocumentLinkCommand implements Command {
-	private static readonly id = '_markdown.openDocumentLink';
-	public readonly id = OpenDocumentLinkCommand.id;
-
-	public static createCommandUri(
-		fromResource: vscode.Uri,
-		path: vscode.Uri,
-		fragment: string,
-	): vscode.Uri {
-		const toJson = (uri: vscode.Uri): UriComponents => {
-			return {
-				scheme: uri.scheme,
-				authority: uri.authority,
-				path: uri.path,
-				fragment: uri.fragment,
-				query: uri.query,
-			};
-		};
-		return vscode.Uri.parse(`command:${OpenDocumentLinkCommand.id}?${encodeURIComponent(JSON.stringify({
-			parts: toJson(path),
-			fragment,
-			fromResource: toJson(fromResource),
-		}))}`);
-	}
-
-	public constructor(
-		private readonly tocProvider: MdTableOfContentsProvider,
-	) { }
-
-	public async execute(args: OpenDocumentLinkArgs) {
-		const fromResource = vscode.Uri.parse('').with(args.fromResource);
-		const targetResource = reviveUri(args.parts).with({ fragment: args.fragment });
-		return openDocumentLink(this.tocProvider, targetResource, fromResource);
-	}
-}
-
-function reviveUri(parts: any) {
-	if (parts.scheme === Schemes.file) {
-		return vscode.Uri.file(parts.path);
-	}
-	return vscode.Uri.parse('').with(parts);
-}
diff --git a/extensions/markdown-language-features/src/commands/refreshPreview.ts b/extensions/markdown-language-features/src/commands/refreshPreview.ts
index 1d983d2f55..7f63d38054 100644
--- a/extensions/markdown-language-features/src/commands/refreshPreview.ts
+++ b/extensions/markdown-language-features/src/commands/refreshPreview.ts
@@ -11,12 +11,12 @@ export class RefreshPreviewCommand implements Command {
 	public readonly id = 'markdown.preview.refresh';
 
 	public constructor(
-		private readonly webviewManager: MarkdownPreviewManager,
-		private readonly engine: MarkdownItEngine
+		private readonly _webviewManager: MarkdownPreviewManager,
+		private readonly _engine: MarkdownItEngine
 	) { }
 
 	public execute() {
-		this.engine.cleanCache();
-		this.webviewManager.refresh();
+		this._engine.cleanCache();
+		this._webviewManager.refresh();
 	}
 }
diff --git a/extensions/markdown-language-features/src/commands/reloadPlugins.ts b/extensions/markdown-language-features/src/commands/reloadPlugins.ts
index 826697d545..4e579b21de 100644
--- a/extensions/markdown-language-features/src/commands/reloadPlugins.ts
+++ b/extensions/markdown-language-features/src/commands/reloadPlugins.ts
@@ -11,13 +11,13 @@ export class ReloadPlugins implements Command {
 	public readonly id = 'markdown.api.reloadPlugins';
 
 	public constructor(
-		private readonly webviewManager: MarkdownPreviewManager,
-		private readonly engine: MarkdownItEngine,
+		private readonly _webviewManager: MarkdownPreviewManager,
+		private readonly _engine: MarkdownItEngine,
 	) { }
 
 	public execute(): void {
-		this.engine.reloadPlugins();
-		this.engine.cleanCache();
-		this.webviewManager.refresh();
+		this._engine.reloadPlugins();
+		this._engine.cleanCache();
+		this._webviewManager.refresh();
 	}
 }
diff --git a/extensions/markdown-language-features/src/commands/renderDocument.ts b/extensions/markdown-language-features/src/commands/renderDocument.ts
index 7da7e52d72..8d59a1bd85 100644
--- a/extensions/markdown-language-features/src/commands/renderDocument.ts
+++ b/extensions/markdown-language-features/src/commands/renderDocument.ts
@@ -11,10 +11,10 @@ export class RenderDocument implements Command {
 	public readonly id = 'markdown.api.render';
 
 	public constructor(
-		private readonly engine: MarkdownItEngine
+		private readonly _engine: MarkdownItEngine
 	) { }
 
 	public async execute(document: ITextDocument | string): Promise {
-		return (await (this.engine.render(document))).html;
+		return (await (this._engine.render(document))).html;
 	}
 }
diff --git a/extensions/markdown-language-features/src/commands/showPreview.ts b/extensions/markdown-language-features/src/commands/showPreview.ts
index f7687e1f2a..7e2e02b674 100644
--- a/extensions/markdown-language-features/src/commands/showPreview.ts
+++ b/extensions/markdown-language-features/src/commands/showPreview.ts
@@ -54,13 +54,13 @@ export class ShowPreviewCommand implements Command {
 	public readonly id = 'markdown.showPreview';
 
 	public constructor(
-		private readonly webviewManager: MarkdownPreviewManager,
-		private readonly telemetryReporter: TelemetryReporter
+		private readonly _webviewManager: MarkdownPreviewManager,
+		private readonly _telemetryReporter: TelemetryReporter
 	) { }
 
 	public execute(mainUri?: vscode.Uri, allUris?: vscode.Uri[], previewSettings?: DynamicPreviewSettings) {
 		for (const uri of Array.isArray(allUris) ? allUris : [mainUri]) {
-			showPreview(this.webviewManager, this.telemetryReporter, uri, {
+			showPreview(this._webviewManager, this._telemetryReporter, uri, {
 				sideBySide: false,
 				locked: previewSettings && previewSettings.locked
 			});
@@ -72,12 +72,12 @@ export class ShowPreviewToSideCommand implements Command {
 	public readonly id = 'markdown.showPreviewToSide';
 
 	public constructor(
-		private readonly webviewManager: MarkdownPreviewManager,
-		private readonly telemetryReporter: TelemetryReporter
+		private readonly _webviewManager: MarkdownPreviewManager,
+		private readonly _telemetryReporter: TelemetryReporter
 	) { }
 
 	public execute(uri?: vscode.Uri, previewSettings?: DynamicPreviewSettings) {
-		showPreview(this.webviewManager, this.telemetryReporter, uri, {
+		showPreview(this._webviewManager, this._telemetryReporter, uri, {
 			sideBySide: true,
 			locked: previewSettings && previewSettings.locked
 		});
@@ -89,12 +89,12 @@ export class ShowLockedPreviewToSideCommand implements Command {
 	public readonly id = 'markdown.showLockedPreviewToSide';
 
 	public constructor(
-		private readonly webviewManager: MarkdownPreviewManager,
-		private readonly telemetryReporter: TelemetryReporter
+		private readonly _webviewManager: MarkdownPreviewManager,
+		private readonly _telemetryReporter: TelemetryReporter
 	) { }
 
 	public execute(uri?: vscode.Uri) {
-		showPreview(this.webviewManager, this.telemetryReporter, uri, {
+		showPreview(this._webviewManager, this._telemetryReporter, uri, {
 			sideBySide: true,
 			locked: true
 		});
diff --git a/extensions/markdown-language-features/src/commands/showPreviewSecuritySelector.ts b/extensions/markdown-language-features/src/commands/showPreviewSecuritySelector.ts
index 8516978c02..26caae531f 100644
--- a/extensions/markdown-language-features/src/commands/showPreviewSecuritySelector.ts
+++ b/extensions/markdown-language-features/src/commands/showPreviewSecuritySelector.ts
@@ -13,18 +13,18 @@ export class ShowPreviewSecuritySelectorCommand implements Command {
 	public readonly id = 'markdown.showPreviewSecuritySelector';
 
 	public constructor(
-		private readonly previewSecuritySelector: PreviewSecuritySelector,
-		private readonly previewManager: MarkdownPreviewManager
+		private readonly _previewSecuritySelector: PreviewSecuritySelector,
+		private readonly _previewManager: MarkdownPreviewManager
 	) { }
 
 	public execute(resource: string | undefined) {
-		if (this.previewManager.activePreviewResource) {
-			this.previewSecuritySelector.showSecuritySelectorForResource(this.previewManager.activePreviewResource);
+		if (this._previewManager.activePreviewResource) {
+			this._previewSecuritySelector.showSecuritySelectorForResource(this._previewManager.activePreviewResource);
 		} else if (resource) {
 			const source = vscode.Uri.parse(resource);
-			this.previewSecuritySelector.showSecuritySelectorForResource(source.query ? vscode.Uri.parse(source.query) : source);
+			this._previewSecuritySelector.showSecuritySelectorForResource(source.query ? vscode.Uri.parse(source.query) : source);
 		} else if (vscode.window.activeTextEditor && isMarkdownFile(vscode.window.activeTextEditor.document)) {
-			this.previewSecuritySelector.showSecuritySelectorForResource(vscode.window.activeTextEditor.document.uri);
+			this._previewSecuritySelector.showSecuritySelectorForResource(vscode.window.activeTextEditor.document.uri);
 		}
 	}
 }
diff --git a/extensions/markdown-language-features/src/commands/showSource.ts b/extensions/markdown-language-features/src/commands/showSource.ts
index d6377e7f7f..28b6499183 100644
--- a/extensions/markdown-language-features/src/commands/showSource.ts
+++ b/extensions/markdown-language-features/src/commands/showSource.ts
@@ -11,11 +11,11 @@ export class ShowSourceCommand implements Command {
 	public readonly id = 'markdown.showSource';
 
 	public constructor(
-		private readonly previewManager: MarkdownPreviewManager
+		private readonly _previewManager: MarkdownPreviewManager
 	) { }
 
 	public execute() {
-		const { activePreviewResource, activePreviewResourceColumn } = this.previewManager;
+		const { activePreviewResource, activePreviewResourceColumn } = this._previewManager;
 		if (activePreviewResource && activePreviewResourceColumn) {
 			return vscode.workspace.openTextDocument(activePreviewResource).then(document => {
 				return vscode.window.showTextDocument(document, activePreviewResourceColumn);
diff --git a/extensions/markdown-language-features/src/commands/toggleLock.ts b/extensions/markdown-language-features/src/commands/toggleLock.ts
index 0caeac949a..9175033a1d 100644
--- a/extensions/markdown-language-features/src/commands/toggleLock.ts
+++ b/extensions/markdown-language-features/src/commands/toggleLock.ts
@@ -10,10 +10,10 @@ export class ToggleLockCommand implements Command {
 	public readonly id = 'markdown.preview.toggleLock';
 
 	public constructor(
-		private readonly previewManager: MarkdownPreviewManager
+		private readonly _previewManager: MarkdownPreviewManager
 	) { }
 
 	public execute() {
-		this.previewManager.toggleLock();
+		this._previewManager.toggleLock();
 	}
 }
diff --git a/extensions/markdown-language-features/src/extension.browser.ts b/extensions/markdown-language-features/src/extension.browser.ts
index 65d1ad6bbb..ba492c1695 100644
--- a/extensions/markdown-language-features/src/extension.browser.ts
+++ b/extensions/markdown-language-features/src/extension.browser.ts
@@ -4,14 +4,13 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as vscode from 'vscode';
-import { BaseLanguageClient, LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser';
-import { startClient } from './client';
+import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/browser';
+import { MdLanguageClient, startClient } from './client/client';
 import { activateShared } from './extension.shared';
 import { VsCodeOutputLogger } from './logging';
 import { IMdParser, MarkdownItEngine } from './markdownEngine';
 import { getMarkdownExtensionContributions } from './markdownExtensions';
 import { githubSlugifier } from './slugify';
-import { IMdWorkspace, VsCodeMdWorkspace } from './workspace';
 
 export async function activate(context: vscode.ExtensionContext) {
 	const contributions = getMarkdownExtensionContributions(context);
@@ -22,21 +21,18 @@ export async function activate(context: vscode.ExtensionContext) {
 
 	const engine = new MarkdownItEngine(contributions, githubSlugifier, logger);
 
-	const workspace = new VsCodeMdWorkspace();
-	context.subscriptions.push(workspace);
-
-	const client = await startServer(context, workspace, engine);
-	context.subscriptions.push({
-		dispose: () => client.stop()
-	});
-	activateShared(context, client, workspace, engine, logger, contributions);
+	const client = await startServer(context, engine);
+	context.subscriptions.push(client);
+	activateShared(context, client, engine, logger, contributions);
 }
 
-function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise {
-	const serverMain = vscode.Uri.joinPath(context.extensionUri, 'server/dist/browser/main.js');
+function startServer(context: vscode.ExtensionContext, parser: IMdParser): Promise {
+	const serverMain = vscode.Uri.joinPath(context.extensionUri, 'server/dist/browser/workerMain.js');
+
 	const worker = new Worker(serverMain.toString());
+	worker.postMessage({ i10lLocation: vscode.l10n.uri?.toString() ?? '' });
 
 	return startClient((id: string, name: string, clientOptions: LanguageClientOptions) => {
 		return new LanguageClient(id, name, clientOptions, worker);
-	}, workspace, parser);
+	}, parser);
 }
diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts
index 6c5ee45e6d..aa379a9ee6 100644
--- a/extensions/markdown-language-features/src/extension.shared.ts
+++ b/extensions/markdown-language-features/src/extension.shared.ts
@@ -4,27 +4,26 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as vscode from 'vscode';
-import { BaseLanguageClient } from 'vscode-languageclient';
+import { MdLanguageClient } from './client/client';
 import { CommandManager } from './commandManager';
-import * as commands from './commands/index';
-import { registerPasteSupport } from './languageFeatures/copyPaste';
+import { registerMarkdownCommands } from './commands/index';
+import { registerPasteSupport } from './languageFeatures/copyFiles/copyPaste';
 import { registerDiagnosticSupport } from './languageFeatures/diagnostics';
-import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor';
+import { registerDropIntoEditorSupport } from './languageFeatures/copyFiles/dropIntoEditor';
 import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences';
+import { registerUpdateLinksOnRename } from './languageFeatures/linkUpdater';
 import { ILogger } from './logging';
-import { MarkdownItEngine, MdParsingProvider } from './markdownEngine';
+import { MarkdownItEngine } from './markdownEngine';
 import { MarkdownContributionProvider } from './markdownExtensions';
 import { MdDocumentRenderer } from './preview/documentRenderer';
 import { MarkdownPreviewManager } from './preview/previewManager';
-import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './preview/security';
-import { MdTableOfContentsProvider } from './tableOfContents';
-import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter';
-import { IMdWorkspace } from './workspace';
+import { ExtensionContentSecurityPolicyArbiter } from './preview/security';
+import { loadDefaultTelemetryReporter } from './telemetryReporter';
+import { MdLinkOpener } from './util/openDocumentLink';
 
 export function activateShared(
 	context: vscode.ExtensionContext,
-	client: BaseLanguageClient,
-	workspace: IMdWorkspace,
+	client: MdLanguageClient,
 	engine: MarkdownItEngine,
 	logger: ILogger,
 	contributions: MarkdownContributionProvider,
@@ -35,16 +34,14 @@ export function activateShared(
 	const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState);
 	const commandManager = new CommandManager();
 
-	const parser = new MdParsingProvider(engine, workspace);
-	const tocProvider = new MdTableOfContentsProvider(parser, workspace, logger);
-	context.subscriptions.push(parser, tocProvider);
+	const opener = new MdLinkOpener(client);
 
 	const contentProvider = new MdDocumentRenderer(engine, context, cspArbiter, contributions, logger);
-	const previewManager = new MarkdownPreviewManager(contentProvider, workspace, logger, contributions, tocProvider);
+	const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, opener);
 	context.subscriptions.push(previewManager);
 
 	context.subscriptions.push(registerMarkdownLanguageFeatures(client, commandManager));
-	context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine, tocProvider));
+	context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine));
 
 	context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
 		previewManager.updateConfiguration();
@@ -52,7 +49,7 @@ export function activateShared(
 }
 
 function registerMarkdownLanguageFeatures(
-	client: BaseLanguageClient,
+	client: MdLanguageClient,
 	commandManager: CommandManager,
 ): vscode.Disposable {
 	const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' };
@@ -62,29 +59,7 @@ function registerMarkdownLanguageFeatures(
 		registerDropIntoEditorSupport(selector),
 		registerFindFileReferenceSupport(commandManager, client),
 		registerPasteSupport(selector),
+		registerUpdateLinksOnRename(client),
 	);
 }
 
-function registerMarkdownCommands(
-	commandManager: CommandManager,
-	previewManager: MarkdownPreviewManager,
-	telemetryReporter: TelemetryReporter,
-	cspArbiter: ContentSecurityPolicyArbiter,
-	engine: MarkdownItEngine,
-	tocProvider: MdTableOfContentsProvider,
-): vscode.Disposable {
-	const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager);
-
-	commandManager.register(new commands.ShowPreviewCommand(previewManager, telemetryReporter));
-	commandManager.register(new commands.ShowPreviewToSideCommand(previewManager, telemetryReporter));
-	commandManager.register(new commands.ShowLockedPreviewToSideCommand(previewManager, telemetryReporter));
-	commandManager.register(new commands.ShowSourceCommand(previewManager));
-	commandManager.register(new commands.RefreshPreviewCommand(previewManager, engine));
-	commandManager.register(new commands.MoveCursorToPositionCommand());
-	commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager));
-	commandManager.register(new commands.OpenDocumentLinkCommand(tocProvider));
-	commandManager.register(new commands.ToggleLockCommand(previewManager));
-	commandManager.register(new commands.RenderDocument(engine));
-	commandManager.register(new commands.ReloadPlugins(previewManager, engine));
-	return commandManager;
-}
diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts
index 797dfa620e..66bff9ccfb 100644
--- a/extensions/markdown-language-features/src/extension.ts
+++ b/extensions/markdown-language-features/src/extension.ts
@@ -4,14 +4,13 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as vscode from 'vscode';
-import { BaseLanguageClient, LanguageClient, ServerOptions, TransportKind } from 'vscode-languageclient/node';
-import { startClient } from './client';
+import { LanguageClient, ServerOptions, TransportKind } from 'vscode-languageclient/node';
+import { MdLanguageClient, startClient } from './client/client';
 import { activateShared } from './extension.shared';
 import { VsCodeOutputLogger } from './logging';
 import { IMdParser, MarkdownItEngine } from './markdownEngine';
 import { getMarkdownExtensionContributions } from './markdownExtensions';
 import { githubSlugifier } from './slugify';
-import { IMdWorkspace, VsCodeMdWorkspace } from './workspace';
 
 export async function activate(context: vscode.ExtensionContext) {
 	const contributions = getMarkdownExtensionContributions(context);
@@ -22,20 +21,15 @@ export async function activate(context: vscode.ExtensionContext) {
 
 	const engine = new MarkdownItEngine(contributions, githubSlugifier, logger);
 
-	const workspace = new VsCodeMdWorkspace();
-	context.subscriptions.push(workspace);
-
-	const client = await startServer(context, workspace, engine);
-	context.subscriptions.push({
-		dispose: () => client.stop()
-	});
-	activateShared(context, client, workspace, engine, logger, contributions);
+	const client = await startServer(context, engine);
+	context.subscriptions.push(client);
+	activateShared(context, client, engine, logger, contributions);
 }
 
-function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace, parser: IMdParser): Promise {
+function startServer(context: vscode.ExtensionContext, parser: IMdParser): Promise {
 	const clientMain = vscode.extensions.getExtension('vscode.markdown-language-features')?.packageJSON?.main || '';
 
-	const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/main`;
+	const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/workerMain`;
 	const serverModule = context.asAbsolutePath(serverMain);
 
 	// The debug options for the server
@@ -47,7 +41,11 @@ function startServer(context: vscode.ExtensionContext, workspace: IMdWorkspace,
 		run: { module: serverModule, transport: TransportKind.ipc },
 		debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
 	};
+
+	// pass the location of the localization bundle to the server
+	process.env['VSCODE_L10N_BUNDLE_LOCATION'] = vscode.l10n.uri?.toString() ?? '';
+
 	return startClient((id, name, clientOptions) => {
 		return new LanguageClient(id, name, serverOptions, clientOptions);
-	}, workspace, parser);
+	}, parser);
 }
diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts
new file mode 100644
index 0000000000..b3111ea530
--- /dev/null
+++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts
@@ -0,0 +1,174 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import * as picomatch from 'picomatch';
+import * as vscode from 'vscode';
+import { Utils } from 'vscode-uri';
+import { getParentDocumentUri } from '../../util/document';
+
+type OverwriteBehavior = 'overwrite' | 'nameIncrementally';
+
+interface CopyFileConfiguration {
+	readonly destination: Record;
+	readonly overwriteBehavior: OverwriteBehavior;
+}
+
+function getCopyFileConfiguration(document: vscode.TextDocument): CopyFileConfiguration {
+	const config = vscode.workspace.getConfiguration('markdown', document);
+	return {
+		destination: config.get>('copyFiles.destination') ?? {},
+		overwriteBehavior: readOverwriteBehavior(config),
+	};
+}
+
+function readOverwriteBehavior(config: vscode.WorkspaceConfiguration): OverwriteBehavior {
+	switch (config.get('copyFiles.overwriteBehavior')) {
+		case 'overwrite': return 'overwrite';
+		default: return 'nameIncrementally';
+	}
+}
+
+export class NewFilePathGenerator {
+
+	private readonly _usedPaths = new Set();
+
+	async getNewFilePath(
+		document: vscode.TextDocument,
+		file: vscode.DataTransferFile,
+		token: vscode.CancellationToken,
+	): Promise<{ readonly uri: vscode.Uri; readonly overwrite: boolean } | undefined> {
+		const config = getCopyFileConfiguration(document);
+		const desiredPath = getDesiredNewFilePath(config, document, file);
+
+		const root = Utils.dirname(desiredPath);
+		const ext = Utils.extname(desiredPath);
+		let baseName = Utils.basename(desiredPath);
+		baseName = baseName.slice(0, baseName.length - ext.length);
+		for (let i = 0; ; ++i) {
+			if (token.isCancellationRequested) {
+				return undefined;
+			}
+
+			const name = i === 0 ? baseName : `${baseName}-${i}`;
+			const uri = vscode.Uri.joinPath(root, name + ext);
+			if (this._wasPathAlreadyUsed(uri)) {
+				continue;
+			}
+
+			// Try overwriting if it already exists
+			if (config.overwriteBehavior === 'overwrite') {
+				this._usedPaths.add(uri.toString());
+				return { uri, overwrite: true };
+			}
+
+			// Otherwise we need to check the fs to see if it exists
+			try {
+				await vscode.workspace.fs.stat(uri);
+			} catch {
+				if (!this._wasPathAlreadyUsed(uri)) {
+					// Does not exist
+					this._usedPaths.add(uri.toString());
+					return { uri, overwrite: false };
+				}
+			}
+		}
+	}
+
+	private _wasPathAlreadyUsed(uri: vscode.Uri) {
+		return this._usedPaths.has(uri.toString());
+	}
+}
+
+function getDesiredNewFilePath(config: CopyFileConfiguration, document: vscode.TextDocument, file: vscode.DataTransferFile): vscode.Uri {
+	const docUri = getParentDocumentUri(document);
+	for (const [rawGlob, rawDest] of Object.entries(config.destination)) {
+		for (const glob of parseGlob(rawGlob)) {
+			if (picomatch.isMatch(docUri.path, glob)) {
+				return resolveCopyDestination(docUri, file.name, rawDest, uri => vscode.workspace.getWorkspaceFolder(uri)?.uri);
+			}
+		}
+	}
+
+	// Default to next to current file
+	return vscode.Uri.joinPath(Utils.dirname(docUri), file.name);
+}
+
+function parseGlob(rawGlob: string): Iterable {
+	if (rawGlob.startsWith('/')) {
+		// Anchor to workspace folders
+		return (vscode.workspace.workspaceFolders ?? []).map(folder => vscode.Uri.joinPath(folder.uri, rawGlob).path);
+	}
+
+	// Relative path, so implicitly track on ** to match everything
+	if (!rawGlob.startsWith('**')) {
+		return ['**/' + rawGlob];
+	}
+
+	return [rawGlob];
+}
+
+type GetWorkspaceFolder = (documentUri: vscode.Uri) => vscode.Uri | undefined;
+
+export function resolveCopyDestination(documentUri: vscode.Uri, fileName: string, dest: string, getWorkspaceFolder: GetWorkspaceFolder): vscode.Uri {
+	const resolvedDest = resolveCopyDestinationSetting(documentUri, fileName, dest, getWorkspaceFolder);
+
+	if (resolvedDest.startsWith('/')) {
+		// Absolute path
+		return Utils.resolvePath(documentUri, resolvedDest);
+	}
+
+	// Relative to document
+	const dirName = Utils.dirname(documentUri);
+	return Utils.resolvePath(dirName, resolvedDest);
+}
+
+
+function resolveCopyDestinationSetting(documentUri: vscode.Uri, fileName: string, dest: string, getWorkspaceFolder: GetWorkspaceFolder): string {
+	let outDest = dest.trim();
+	if (!outDest) {
+		outDest = '${fileName}';
+	}
+
+	// Destination that start with `/` implicitly means go to workspace root
+	if (outDest.startsWith('/')) {
+		outDest = '${documentWorkspaceFolder}/' + outDest.slice(1);
+	}
+
+	// Destination that ends with `/` implicitly needs a fileName
+	if (outDest.endsWith('/')) {
+		outDest += '${fileName}';
+	}
+
+	const documentDirName = Utils.dirname(documentUri);
+	const documentBaseName = Utils.basename(documentUri);
+	const documentExtName = Utils.extname(documentUri);
+
+	const workspaceFolder = getWorkspaceFolder(documentUri);
+
+	const vars = new Map([
+		['documentDirName', documentDirName.path], //  Parent directory path
+		['documentFileName', documentBaseName], // Full filename: file.md
+		['documentBaseName', documentBaseName.slice(0, documentBaseName.length - documentExtName.length)], // Just the name: file
+		['documentExtName', documentExtName.replace('.', '')], // Just the file ext: md
+
+		// Workspace
+		['documentWorkspaceFolder', (workspaceFolder ?? documentDirName).path],
+
+		// File
+		['fileName', fileName],// Full file name
+	]);
+
+	return outDest.replaceAll(/\$\{(\w+)(?:\/([^\}]+?)\/([^\}]+?)\/)?\}/g, (_, name, pattern, replacement) => {
+		const entry = vars.get(name);
+		if (!entry) {
+			return '';
+		}
+
+		if (pattern && replacement) {
+			return entry.replace(new RegExp(pattern), replacement);
+		}
+
+		return entry;
+	});
+}
diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts
new file mode 100644
index 0000000000..582572309f
--- /dev/null
+++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts
@@ -0,0 +1,77 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { Schemes } from '../../util/schemes';
+import { createEditForMediaFiles, mediaMimes, tryGetUriListSnippet } from './shared';
+
+class PasteEditProvider implements vscode.DocumentPasteEditProvider {
+
+	private readonly _id = 'insertLink';
+
+	async provideDocumentPasteEdits(
+		document: vscode.TextDocument,
+		_ranges: readonly vscode.Range[],
+		dataTransfer: vscode.DataTransfer,
+		token: vscode.CancellationToken,
+	): Promise {
+		const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.filePaste.enabled', true);
+		if (!enabled) {
+			return;
+		}
+
+		const createEdit = await this._getMediaFilesEdit(document, dataTransfer, token);
+		if (createEdit) {
+			return createEdit;
+		}
+
+		const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
+		if (!snippet) {
+			return;
+		}
+
+		const uriEdit = new vscode.DocumentPasteEdit(snippet.snippet, this._id, snippet.label);
+		uriEdit.priority = this._getPriority(dataTransfer);
+		return uriEdit;
+	}
+
+	private async _getMediaFilesEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise {
+		if (document.uri.scheme === Schemes.untitled) {
+			return;
+		}
+
+		const copyFilesIntoWorkspace = vscode.workspace.getConfiguration('markdown', document).get<'mediaFiles' | 'never'>('editor.filePaste.copyIntoWorkspace', 'mediaFiles');
+		if (copyFilesIntoWorkspace === 'never') {
+			return;
+		}
+
+		const edit = await createEditForMediaFiles(document, dataTransfer, token);
+		if (!edit) {
+			return;
+		}
+
+		const pasteEdit = new vscode.DocumentPasteEdit(edit.snippet, this._id, edit.label);
+		pasteEdit.additionalEdit = edit.additionalEdits;
+		pasteEdit.priority = this._getPriority(dataTransfer);
+		return pasteEdit;
+	}
+
+	private _getPriority(dataTransfer: vscode.DataTransfer): number {
+		if (dataTransfer.get('text/plain')) {
+			// Deprioritize in favor of normal text content
+			return -10;
+		}
+		return 0;
+	}
+}
+
+export function registerPasteSupport(selector: vscode.DocumentSelector,) {
+	return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteEditProvider(), {
+		pasteMimeTypes: [
+			'text/uri-list',
+			...mediaMimes,
+		]
+	});
+}
diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts
new file mode 100644
index 0000000000..1de84d292e
--- /dev/null
+++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/dropIntoEditor.ts
@@ -0,0 +1,73 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import { createEditForMediaFiles as createEditForMediaFiles, tryGetUriListSnippet } from './shared';
+import { Schemes } from '../../util/schemes';
+
+
+class MarkdownImageDropProvider implements vscode.DocumentDropEditProvider {
+	private readonly _id = 'insertLink';
+
+	async provideDocumentDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise {
+		const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true);
+		if (!enabled) {
+			return;
+		}
+
+		const filesEdit = await this._getMediaFilesEdit(document, dataTransfer, token);
+		if (filesEdit) {
+			return filesEdit;
+		}
+
+		if (token.isCancellationRequested) {
+			return;
+		}
+
+		return this._getUriListEdit(document, dataTransfer, token);
+	}
+
+	private async _getUriListEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise {
+		const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
+		if (!snippet) {
+			return undefined;
+		}
+
+		const edit = new vscode.DocumentDropEdit(snippet.snippet);
+		edit.id = this._id;
+		edit.label = snippet.label;
+		return edit;
+	}
+
+	private async _getMediaFilesEdit(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise {
+		if (document.uri.scheme === Schemes.untitled) {
+			return;
+		}
+
+		const copyIntoWorkspace = vscode.workspace.getConfiguration('markdown', document).get<'mediaFiles' | 'never'>('editor.drop.copyIntoWorkspace', 'mediaFiles');
+		if (copyIntoWorkspace !== 'mediaFiles') {
+			return;
+		}
+
+		const filesEdit = await createEditForMediaFiles(document, dataTransfer, token);
+		if (!filesEdit) {
+			return;
+		}
+
+		const edit = new vscode.DocumentDropEdit(filesEdit.snippet);
+		edit.id = this._id;
+		edit.label = filesEdit.label;
+		edit.additionalEdit = filesEdit.additionalEdits;
+		return edit;
+	}
+}
+
+export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector) {
+	return vscode.languages.registerDocumentDropEditProvider(selector, new MarkdownImageDropProvider(), {
+		dropMimeTypes: [
+			'text/uri-list'
+		]
+	});
+}
diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts
new file mode 100644
index 0000000000..c8392f72eb
--- /dev/null
+++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts
@@ -0,0 +1,298 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as path from 'path';
+import * as vscode from 'vscode';
+import * as URI from 'vscode-uri';
+import { Schemes } from '../../util/schemes';
+import { NewFilePathGenerator } from './copyFiles';
+import { coalesce } from '../../util/arrays';
+import { getDocumentDir } from '../../util/document';
+
+enum MediaKind {
+	Image,
+	Video,
+	Audio,
+}
+
+export const mediaFileExtensions = new Map([
+	// Images
+	['bmp', MediaKind.Image],
+	['gif', MediaKind.Image],
+	['ico', MediaKind.Image],
+	['jpe', MediaKind.Image],
+	['jpeg', MediaKind.Image],
+	['jpg', MediaKind.Image],
+	['png', MediaKind.Image],
+	['psd', MediaKind.Image],
+	['svg', MediaKind.Image],
+	['tga', MediaKind.Image],
+	['tif', MediaKind.Image],
+	['tiff', MediaKind.Image],
+	['webp', MediaKind.Image],
+
+	// Videos
+	['ogg', MediaKind.Video],
+	['mp4', MediaKind.Video],
+
+	// Audio Files
+	['mp3', MediaKind.Audio],
+	['aac', MediaKind.Audio],
+	['wav', MediaKind.Audio],
+]);
+
+export const mediaMimes = new Set([
+	'image/bmp',
+	'image/gif',
+	'image/jpeg',
+	'image/png',
+	'image/webp',
+	'video/mp4',
+	'video/ogg',
+	'audio/mpeg',
+	'audio/aac',
+	'audio/x-wav',
+]);
+
+
+export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> {
+	const urlList = await dataTransfer.get('text/uri-list')?.asString();
+	if (!urlList || token.isCancellationRequested) {
+		return undefined;
+	}
+
+	const uris: vscode.Uri[] = [];
+	for (const resource of urlList.split(/\r?\n/g)) {
+		try {
+			uris.push(vscode.Uri.parse(resource));
+		} catch {
+			// noop
+		}
+	}
+
+	return createUriListSnippet(document, uris);
+}
+
+interface UriListSnippetOptions {
+	readonly placeholderText?: string;
+
+	readonly placeholderStartIndex?: number;
+
+	/**
+	 * Should the snippet be for an image link or video?
+	 *
+	 * If `undefined`, tries to infer this from the uri.
+	 */
+	readonly insertAsMedia?: boolean;
+
+	readonly separator?: string;
+}
+
+
+export function createUriListSnippet(
+	document: vscode.TextDocument,
+	uris: readonly vscode.Uri[],
+	options?: UriListSnippetOptions
+): { snippet: vscode.SnippetString; label: string } | undefined {
+	if (!uris.length) {
+		return;
+	}
+
+	const dir = getDocumentDir(document);
+
+	const snippet = new vscode.SnippetString();
+
+	let insertedLinkCount = 0;
+	let insertedImageCount = 0;
+	let insertedAudioVideoCount = 0;
+
+	uris.forEach((uri, i) => {
+		const mdPath = getMdPath(dir, uri);
+
+		const ext = URI.Utils.extname(uri).toLowerCase().replace('.', '');
+		const insertAsMedia = typeof options?.insertAsMedia === 'undefined' ? mediaFileExtensions.has(ext) : !!options.insertAsMedia;
+		const insertAsVideo = mediaFileExtensions.get(ext) === MediaKind.Video;
+		const insertAsAudio = mediaFileExtensions.get(ext) === MediaKind.Audio;
+
+		if (insertAsVideo) {
+			insertedAudioVideoCount++;
+			snippet.appendText(`');
+		} else if (insertAsAudio) {
+			insertedAudioVideoCount++;
+			snippet.appendText(`');
+		} else {
+			if (insertAsMedia) {
+				insertedImageCount++;
+			} else {
+				insertedLinkCount++;
+			}
+
+			snippet.appendText(insertAsMedia ? '![' : '[');
+
+			const placeholderText = options?.placeholderText ?? (insertAsMedia ? 'Alt text' : 'label');
+			const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : undefined;
+			snippet.appendPlaceholder(placeholderText, placeholderIndex);
+
+			snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`);
+		}
+
+		if (i < uris.length - 1 && uris.length > 1) {
+			snippet.appendText(options?.separator ?? ' ');
+		}
+	});
+
+	let label: string;
+	if (insertedAudioVideoCount > 0) {
+		if (insertedLinkCount > 0) {
+			label = vscode.l10n.t('Insert Markdown Media and Links');
+		} else {
+			label = vscode.l10n.t('Insert Markdown Media');
+		}
+	} else if (insertedImageCount > 0 && insertedLinkCount > 0) {
+		label = vscode.l10n.t('Insert Markdown Images and Links');
+	} else if (insertedImageCount > 0) {
+		label = insertedImageCount > 1
+			? vscode.l10n.t('Insert Markdown Images')
+			: vscode.l10n.t('Insert Markdown Image');
+	} else {
+		label = insertedLinkCount > 1
+			? vscode.l10n.t('Insert Markdown Links')
+			: vscode.l10n.t('Insert Markdown Link');
+	}
+
+	return { snippet, label };
+}
+
+/**
+ * Create a new edit from the image files in a data transfer.
+ *
+ * This tries copying files outside of the workspace into the workspace.
+ */
+export async function createEditForMediaFiles(
+	document: vscode.TextDocument,
+	dataTransfer: vscode.DataTransfer,
+	token: vscode.CancellationToken
+): Promise<{ snippet: vscode.SnippetString; label: string; additionalEdits: vscode.WorkspaceEdit } | undefined> {
+	if (document.uri.scheme === Schemes.untitled) {
+		return;
+	}
+
+	interface FileEntry {
+		readonly uri: vscode.Uri;
+		readonly newFile?: { readonly contents: vscode.DataTransferFile; readonly overwrite: boolean };
+	}
+
+	const pathGenerator = new NewFilePathGenerator();
+	const fileEntries = coalesce(await Promise.all(Array.from(dataTransfer, async ([mime, item]): Promise => {
+		if (!mediaMimes.has(mime)) {
+			return;
+		}
+
+		const file = item?.asFile();
+		if (!file) {
+			return;
+		}
+
+		if (file.uri) {
+			// If the file is already in a workspace, we don't want to create a copy of it
+			const workspaceFolder = vscode.workspace.getWorkspaceFolder(file.uri);
+			if (workspaceFolder) {
+				return { uri: file.uri };
+			}
+		}
+
+		const newFile = await pathGenerator.getNewFilePath(document, file, token);
+		if (!newFile) {
+			return;
+		}
+		return { uri: newFile.uri, newFile: { contents: file, overwrite: newFile.overwrite } };
+	})));
+	if (!fileEntries.length) {
+		return;
+	}
+
+	const workspaceEdit = new vscode.WorkspaceEdit();
+	for (const entry of fileEntries) {
+		if (entry.newFile) {
+			workspaceEdit.createFile(entry.uri, {
+				contents: entry.newFile.contents,
+				overwrite: entry.newFile.overwrite,
+			});
+		}
+	}
+
+	const snippet = createUriListSnippet(document, fileEntries.map(entry => entry.uri));
+	if (!snippet) {
+		return;
+	}
+
+	return {
+		snippet: snippet.snippet,
+		label: snippet.label,
+		additionalEdits: workspaceEdit,
+	};
+}
+
+function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
+	if (dir && dir.scheme === file.scheme && dir.authority === file.authority) {
+		if (file.scheme === Schemes.file) {
+			// On windows, we must use the native `path.relative` to generate the relative path
+			// so that drive-letters are resolved cast insensitively. However we then want to
+			// convert back to a posix path to insert in to the document.
+			const relativePath = path.relative(dir.fsPath, file.fsPath);
+			return path.posix.normalize(relativePath.split(path.sep).join(path.posix.sep));
+		}
+
+		return path.posix.relative(dir.path, file.path);
+	}
+
+	return file.toString(false);
+}
+
+function escapeHtmlAttribute(attr: string): string {
+	return encodeURI(attr).replaceAll('"', '"');
+}
+
+function escapeMarkdownLinkPath(mdPath: string): string {
+	if (needsBracketLink(mdPath)) {
+		return '<' + mdPath.replace('<', '\\<').replace('>', '\\>') + '>';
+	}
+
+	return encodeURI(mdPath);
+}
+
+function needsBracketLink(mdPath: string) {
+	// Links with whitespace or control characters must be enclosed in brackets
+	if (mdPath.startsWith('<') || /\s|[\u007F\u0000-\u001f]/.test(mdPath)) {
+		return true;
+	}
+
+	// Check if the link has mis-matched parens
+	if (!/[\(\)]/.test(mdPath)) {
+		return false;
+	}
+
+	let previousChar = '';
+	let nestingCount = 0;
+	for (const char of mdPath) {
+		if (char === '(' && previousChar !== '\\') {
+			nestingCount++;
+		} else if (char === ')' && previousChar !== '\\') {
+			nestingCount--;
+		}
+
+		if (nestingCount < 0) {
+			return true;
+		}
+		previousChar = char;
+	}
+
+	return nestingCount > 0;
+}
+
diff --git a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts
deleted file mode 100644
index d392970339..0000000000
--- a/extensions/markdown-language-features/src/languageFeatures/copyPaste.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import * as vscode from 'vscode';
-import { tryGetUriListSnippet } from './dropIntoEditor';
-
-export function registerPasteSupport(selector: vscode.DocumentSelector) {
-	return vscode.languages.registerDocumentPasteEditProvider(selector, new class implements vscode.DocumentPasteEditProvider {
-
-		async provideDocumentPasteEdits(
-			document: vscode.TextDocument,
-			_ranges: readonly vscode.Range[],
-			dataTransfer: vscode.DataTransfer,
-			token: vscode.CancellationToken,
-		): Promise {
-			const enabled = vscode.workspace.getConfiguration('markdown', document).get('experimental.editor.pasteLinks.enabled', true);
-			if (!enabled) {
-				return;
-			}
-
-			const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
-			return snippet ? new vscode.DocumentPasteEdit(snippet) : undefined;
-		}
-	}, {
-		pasteMimeTypes: ['text/uri-list']
-	});
-}
diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts
index 1e76b78f62..a991b26323 100644
--- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts
+++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts
@@ -4,10 +4,8 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as vscode from 'vscode';
-import * as nls from 'vscode-nls';
 import { CommandManager } from '../commandManager';
 
-const localize = nls.loadMessageBundle();
 
 // Copied from markdown language service
 export enum DiagnosticCode {
@@ -22,18 +20,18 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider {
 
 	private static readonly _addToIgnoreLinksCommandId = '_markdown.addToIgnoreLinks';
 
-	private static readonly metadata: vscode.CodeActionProviderMetadata = {
+	private static readonly _metadata: vscode.CodeActionProviderMetadata = {
 		providedCodeActionKinds: [
 			vscode.CodeActionKind.QuickFix
 		],
 	};
 
 	public static register(selector: vscode.DocumentSelector, commandManager: CommandManager): vscode.Disposable {
-		const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToIgnoreLinksQuickFixProvider(), AddToIgnoreLinksQuickFixProvider.metadata);
+		const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToIgnoreLinksQuickFixProvider(), AddToIgnoreLinksQuickFixProvider._metadata);
 		const commandReg = commandManager.register({
 			id: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId,
 			execute(resource: vscode.Uri, path: string) {
-				const settingId = 'experimental.validate.ignoreLinks';
+				const settingId = 'validate.ignoredLinks';
 				const config = vscode.workspace.getConfiguration('markdown', resource);
 				const paths = new Set(config.get(settingId, []));
 				paths.add(path);
@@ -55,7 +53,7 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider {
 					const hrefText = (diagnostic as any).data?.hrefText;
 					if (hrefText) {
 						const fix = new vscode.CodeAction(
-							localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", hrefText),
+							vscode.l10n.t("Exclude '{0}' from link validation.", hrefText),
 							vscode.CodeActionKind.QuickFix);
 
 						fix.command = {
diff --git a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts
deleted file mode 100644
index 5c2e49e78b..0000000000
--- a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- *  Copyright (c) Microsoft Corporation. All rights reserved.
- *  Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import * as path from 'path';
-import * as vscode from 'vscode';
-import * as URI from 'vscode-uri';
-
-const imageFileExtensions = new Set([
-	'.bmp',
-	'.gif',
-	'.ico',
-	'.jpe',
-	'.jpeg',
-	'.jpg',
-	'.png',
-	'.psd',
-	'.svg',
-	'.tga',
-	'.tif',
-	'.tiff',
-	'.webp',
-]);
-
-export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector) {
-	return vscode.languages.registerDocumentDropEditProvider(selector, new class implements vscode.DocumentDropEditProvider {
-		async provideDocumentDropEdits(document: vscode.TextDocument, _position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise {
-			const enabled = vscode.workspace.getConfiguration('markdown', document).get('editor.drop.enabled', true);
-			if (!enabled) {
-				return undefined;
-			}
-
-			const snippet = await tryGetUriListSnippet(document, dataTransfer, token);
-			return snippet ? new vscode.DocumentDropEdit(snippet) : undefined;
-		}
-	});
-}
-
-export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise {
-	const urlList = await dataTransfer.get('text/uri-list')?.asString();
-	if (!urlList || token.isCancellationRequested) {
-		return undefined;
-	}
-
-	const uris: vscode.Uri[] = [];
-	for (const resource of urlList.split('\n')) {
-		try {
-			uris.push(vscode.Uri.parse(resource));
-		} catch {
-			// noop
-		}
-	}
-
-	if (!uris.length) {
-		return;
-	}
-
-	const snippet = new vscode.SnippetString();
-	uris.forEach((uri, i) => {
-		const mdPath = document.uri.scheme === uri.scheme
-			? encodeURI(path.relative(URI.Utils.dirname(document.uri).fsPath, uri.fsPath).replace(/\\/g, '/'))
-			: uri.toString(false);
-
-		const ext = URI.Utils.extname(uri).toLowerCase();
-		snippet.appendText(imageFileExtensions.has(ext) ? '![' : '[');
-		snippet.appendTabstop();
-		snippet.appendText(`](${mdPath})`);
-
-		if (i <= uris.length - 1 && uris.length > 1) {
-			snippet.appendText(' ');
-		}
-	});
-
-	return snippet;
-}
diff --git a/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts b/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts
index 6ca2e06180..89de3afa90 100644
--- a/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts
+++ b/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts
@@ -4,12 +4,9 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as vscode from 'vscode';
-import { BaseLanguageClient } from 'vscode-languageclient';
-import * as nls from 'vscode-nls';
+import type * as lsp from 'vscode-languageserver-types';
+import { MdLanguageClient } from '../client/client';
 import { Command, CommandManager } from '../commandManager';
-import { getReferencesToFileInWorkspace } from '../protocol';
-
-const localize = nls.loadMessageBundle();
 
 
 export class FindFileReferencesCommand implements Command {
@@ -17,25 +14,22 @@ export class FindFileReferencesCommand implements Command {
 	public readonly id = 'markdown.findAllFileReferences';
 
 	constructor(
-		private readonly client: BaseLanguageClient,
+		private readonly _client: MdLanguageClient,
 	) { }
 
 	public async execute(resource?: vscode.Uri) {
+		resource ??= vscode.window.activeTextEditor?.document.uri;
 		if (!resource) {
-			resource = vscode.window.activeTextEditor?.document.uri;
-		}
-
-		if (!resource) {
-			vscode.window.showErrorMessage(localize('error.noResource', "Find file references failed. No resource provided."));
+			vscode.window.showErrorMessage(vscode.l10n.t("Find file references failed. No resource provided."));
 			return;
 		}
 
 		await vscode.window.withProgress({
 			location: vscode.ProgressLocation.Window,
-			title: localize('progress.title', "Finding file references")
+			title: vscode.l10n.t("Finding file references")
 		}, async (_progress, token) => {
-			const locations = (await this.client.sendRequest(getReferencesToFileInWorkspace, { uri: resource!.toString() }, token)).map(loc => {
-				return new vscode.Location(vscode.Uri.parse(loc.uri), new vscode.Range(loc.range.start.line, loc.range.start.character, loc.range.end.line, loc.range.end.character));
+			const locations = (await this._client.getReferencesToFileInWorkspace(resource!, token)).map(loc => {
+				return new vscode.Location(vscode.Uri.parse(loc.uri), convertRange(loc.range));
 			});
 
 			const config = vscode.workspace.getConfiguration('references');
@@ -51,9 +45,13 @@ export class FindFileReferencesCommand implements Command {
 	}
 }
 
+export function convertRange(range: lsp.Range): vscode.Range {
+	return new vscode.Range(range.start.line, range.start.character, range.end.line, range.end.character);
+}
+
 export function registerFindFileReferenceSupport(
 	commandManager: CommandManager,
-	client: BaseLanguageClient,
+	client: MdLanguageClient,
 ): vscode.Disposable {
 	return commandManager.register(new FindFileReferencesCommand(client));
 }
diff --git a/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts b/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts
new file mode 100644
index 0000000000..03c4f22758
--- /dev/null
+++ b/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts
@@ -0,0 +1,230 @@
+/*---------------------------------------------------------------------------------------------
+ *  Copyright (c) Microsoft Corporation. All rights reserved.
+ *  Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as path from 'path';
+import * as picomatch from 'picomatch';
+import * as vscode from 'vscode';
+import { TextDocumentEdit } from 'vscode-languageclient';
+import { MdLanguageClient } from '../client/client';
+import { Delayer } from '../util/async';
+import { noopToken } from '../util/cancellation';
+import { Disposable } from '../util/dispose';
+import { convertRange } from './fileReferences';
+
+
+const settingNames = Object.freeze({
+	enabled: 'updateLinksOnFileMove.enabled',
+	include: 'updateLinksOnFileMove.include',
+	enableForDirectories: 'updateLinksOnFileMove.enableForDirectories',
+});
+
+const enum UpdateLinksOnFileMoveSetting {
+	Prompt = 'prompt',
+	Always = 'always',
+	Never = 'never',
+}
+
+interface RenameAction {
+	readonly oldUri: vscode.Uri;
+	readonly newUri: vscode.Uri;
+}
+
+class UpdateLinksOnFileRenameHandler extends Disposable {
+
+	private readonly _delayer = new Delayer(50);
+	private readonly _pendingRenames = new Set();
+
+	public constructor(
+		private readonly _client: MdLanguageClient,
+	) {
+		super();
+
+		this._register(vscode.workspace.onDidRenameFiles(async (e) => {
+			await Promise.all(e.files.map(async (rename) => {
+				if (await this._shouldParticipateInLinkUpdate(rename.newUri)) {
+					this._pendingRenames.add(rename);
+				}
+			}));
+
+			if (this._pendingRenames.size) {
+				this._delayer.trigger(() => {
+					vscode.window.withProgress({
+						location: vscode.ProgressLocation.Window,
+						title: vscode.l10n.t("Checking for Markdown links to update")
+					}, () => this._flushRenames());
+				});
+			}
+		}));
+	}
+
+	private async _flushRenames(): Promise {
+		const renames = Array.from(this._pendingRenames);
+		this._pendingRenames.clear();
+
+		const result = await this._getEditsForFileRename(renames, noopToken);
+
+		if (result && result.edit.size) {
+			if (await this._confirmActionWithUser(result.resourcesBeingRenamed)) {
+				await vscode.workspace.applyEdit(result.edit);
+			}
+		}
+	}
+
+	private async _confirmActionWithUser(newResources: readonly vscode.Uri[]): Promise {
+		if (!newResources.length) {
+			return false;
+		}
+
+		const config = vscode.workspace.getConfiguration('markdown', newResources[0]);
+		const setting = config.get(settingNames.enabled);
+		switch (setting) {
+			case UpdateLinksOnFileMoveSetting.Prompt:
+				return this._promptUser(newResources);
+			case UpdateLinksOnFileMoveSetting.Always:
+				return true;
+			case UpdateLinksOnFileMoveSetting.Never:
+			default:
+				return false;
+		}
+	}
+	private async _shouldParticipateInLinkUpdate(newUri: vscode.Uri): Promise {
+		const config = vscode.workspace.getConfiguration('markdown', newUri);
+		const setting = config.get(settingNames.enabled);
+		if (setting === UpdateLinksOnFileMoveSetting.Never) {
+			return false;
+		}
+
+		const externalGlob = config.get(settingNames.include);
+		if (externalGlob) {
+			for (const glob of externalGlob) {
+				if (picomatch.isMatch(newUri.fsPath, glob)) {
+					return true;
+				}
+			}
+		}
+
+		const stat = await vscode.workspace.fs.stat(newUri);
+		if (stat.type === vscode.FileType.Directory) {
+			return config.get(settingNames.enableForDirectories, true);
+		}
+
+		return false;
+	}
+
+	private async _promptUser(newResources: readonly vscode.Uri[]): Promise {
+		if (!newResources.length) {
+			return false;
+		}
+
+		const rejectItem: vscode.MessageItem = {
+			title: vscode.l10n.t("No"),
+			isCloseAffordance: true,
+		};
+
+		const acceptItem: vscode.MessageItem = {
+			title: vscode.l10n.t("Yes"),
+		};
+
+		const alwaysItem: vscode.MessageItem = {
+			title: vscode.l10n.t("Always"),
+		};
+
+		const neverItem: vscode.MessageItem = {
+			title: vscode.l10n.t("Never"),
+		};
+
+		const choice = await vscode.window.showInformationMessage(
+			newResources.length === 1
+				? vscode.l10n.t("Update Markdown links for '{0}'?", path.basename(newResources[0].fsPath))
+				: this._getConfirmMessage(vscode.l10n.t("Update Markdown links for the following {0} files?", newResources.length), newResources), {
+			modal: true,
+		}, rejectItem, acceptItem, alwaysItem, neverItem);
+
+		switch (choice) {
+			case acceptItem: {
+				return true;
+			}
+			case rejectItem: {
+				return false;
+			}
+			case alwaysItem: {
+				const config = vscode.workspace.getConfiguration('markdown', newResources[0]);
+				config.update(
+					settingNames.enabled,
+					UpdateLinksOnFileMoveSetting.Always,
+					this._getConfigTargetScope(config, settingNames.enabled));
+				return true;
+			}
+			case neverItem: {
+				const config = vscode.workspace.getConfiguration('markdown', newResources[0]);
+				config.update(
+					settingNames.enabled,
+					UpdateLinksOnFileMoveSetting.Never,
+					this._getConfigTargetScope(config, settingNames.enabled));
+				return false;
+			}
+			default: {
+				return false;
+			}
+		}
+	}
+
+	private async _getEditsForFileRename(renames: readonly RenameAction[], token: vscode.CancellationToken): Promise<{ edit: vscode.WorkspaceEdit; resourcesBeingRenamed: vscode.Uri[] } | undefined> {
+		const result = await this._client.getEditForFileRenames(renames.map(rename => ({ oldUri: rename.oldUri.toString(), newUri: rename.newUri.toString() })), token);
+		if (!result?.edit.documentChanges?.length) {
+			return undefined;
+		}
+
+		const workspaceEdit = new vscode.WorkspaceEdit();
+
+		for (const change of result.edit.documentChanges as TextDocumentEdit[]) {
+			const uri = vscode.Uri.parse(change.textDocument.uri);
+			for (const edit of change.edits) {
+				workspaceEdit.replace(uri, convertRange(edit.range), edit.newText);
+			}
+		}
+
+		return {
+			edit: workspaceEdit,
+			resourcesBeingRenamed: result.participatingRenames.map(x => vscode.Uri.parse(x.newUri)),
+		};
+	}
+
+	private _getConfirmMessage(start: string, resourcesToConfirm: readonly vscode.Uri[]): string {
+		const MAX_CONFIRM_FILES = 10;
+
+		const paths = [start];
+		paths.push('');
+		paths.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => path.basename(r.fsPath)));
+
+		if (resourcesToConfirm.length > MAX_CONFIRM_FILES) {
+			if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) {
+				paths.push(vscode.l10n.t("...1 additional file not shown"));
+			} else {
+				paths.push(vscode.l10n.t("...{0} additional files not shown", resourcesToConfirm.length - MAX_CONFIRM_FILES));
+			}
+		}
+
+		paths.push('');
+		return paths.join('\n');
+	}
+
+	private _getConfigTargetScope(config: vscode.WorkspaceConfiguration, settingsName: string): vscode.ConfigurationTarget {
+		const inspected = config.inspect(settingsName);
+		if (inspected?.workspaceFolderValue) {
+			return vscode.ConfigurationTarget.WorkspaceFolder;
+		}
+
+		if (inspected?.workspaceValue) {
+			return vscode.ConfigurationTarget.Workspace;
+		}
+
+		return vscode.ConfigurationTarget.Global;
+	}
+}
+
+export function registerUpdateLinksOnRename(client: MdLanguageClient): vscode.Disposable {
+	return new UpdateLinksOnFileRenameHandler(client);
+}
diff --git a/extensions/markdown-language-features/src/logging.ts b/extensions/markdown-language-features/src/logging.ts
index 4273b4bba6..37a2f1ecbf 100644
--- a/extensions/markdown-language-features/src/logging.ts
+++ b/extensions/markdown-language-features/src/logging.ts
@@ -5,7 +5,6 @@
 
 import * as vscode from 'vscode';
 import { Disposable } from './util/dispose';
-import { lazy } from './util/lazy';
 
 enum Trace {
 	Off,
@@ -31,49 +30,54 @@ export interface ILogger {
 }
 
 export class VsCodeOutputLogger extends Disposable implements ILogger {
-	private trace?: Trace;
+	private _trace?: Trace;
 
-	private readonly outputChannel = lazy(() => this._register(vscode.window.createOutputChannel('Markdown')));
+	private _outputChannelValue?: vscode.OutputChannel;
+
+	private get _outputChannel() {
+		this._outputChannelValue ??= this._register(vscode.window.createOutputChannel('Markdown'));
+		return this._outputChannelValue;
+	}
 
 	constructor() {
 		super();
 
 		this._register(vscode.workspace.onDidChangeConfiguration(() => {
-			this.updateConfiguration();
+			this._updateConfiguration();
 		}));
 
-		this.updateConfiguration();
+		this._updateConfiguration();
 	}
 
 	public verbose(title: string, message: string, data?: any): void {
-		if (this.trace === Trace.Verbose) {
-			this.appendLine(`[Verbose ${this.now()}] ${title}: ${message}`);
+		if (this._trace === Trace.Verbose) {
+			this._appendLine(`[Verbose ${this._now()}] ${title}: ${message}`);
 			if (data) {
-				this.appendLine(VsCodeOutputLogger.data2String(data));
+				this._appendLine(VsCodeOutputLogger._data2String(data));
 			}
 		}
 	}
 
-	private now(): string {
+	private _now(): string {
 		const now = new Date();
 		return String(now.getUTCHours()).padStart(2, '0')
 			+ ':' + String(now.getMinutes()).padStart(2, '0')
 			+ ':' + String(now.getUTCSeconds()).padStart(2, '0') + '.' + String(now.getMilliseconds()).padStart(3, '0');
 	}
 
-	private updateConfiguration(): void {
-		this.trace = this.readTrace();
+	private _updateConfiguration(): void {
+		this._trace = this._readTrace();
 	}
 
-	private appendLine(value: string): void {
-		this.outputChannel.value.appendLine(value);
+	private _appendLine(value: string): void {
+		this._outputChannel.appendLine(value);
 	}
 
-	private readTrace(): Trace {
+	private _readTrace(): Trace {
 		return Trace.fromString(vscode.workspace.getConfiguration().get('markdown.trace.extension', 'off'));
 	}
 
-	private static data2String(data: any): string {
+	private static _data2String(data: any): string {
 		if (data instanceof Error) {
 			if (typeof data.stack === 'string') {
 				return data.stack;
diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts
index 0a521d5046..3a0287d878 100644
--- a/extensions/markdown-language-features/src/markdownEngine.ts
+++ b/extensions/markdown-language-features/src/markdownEngine.ts
@@ -10,14 +10,8 @@ import { ILogger } from './logging';
 import { MarkdownContributionProvider } from './markdownExtensions';
 import { Slugifier } from './slugify';
 import { ITextDocument } from './types/textDocument';
-import { Disposable } from './util/dispose';
-import { stringHash } from './util/hash';
 import { WebviewResourceProvider } from './util/resources';
 import { isOfScheme, Schemes } from './util/schemes';
-import { MdDocumentInfoCache } from './util/workspaceCache';
-import { IMdWorkspace } from './workspace';
-
-const UNICODE_NEWLINE_REGEX = /\u2028|\u2029/g;
 
 /**
  * Adds begin line index to the output via the 'data-line' data attribute.
@@ -50,47 +44,47 @@ const pluginSourceMap: MarkdownIt.PluginSimple = (md): void => {
 type MarkdownItConfig = Readonly>>;
 
 class TokenCache {
-	private cachedDocument?: {
+	private _cachedDocument?: {
 		readonly uri: vscode.Uri;
 		readonly version: number;
 		readonly config: MarkdownItConfig;
 	};
-	private tokens?: Token[];
+	private _tokens?: Token[];
 
 	public tryGetCached(document: ITextDocument, config: MarkdownItConfig): Token[] | undefined {
-		if (this.cachedDocument
-			&& this.cachedDocument.uri.toString() === document.uri.toString()
-			&& this.cachedDocument.version === document.version
-			&& this.cachedDocument.config.breaks === config.breaks
-			&& this.cachedDocument.config.linkify === config.linkify
+		if (this._cachedDocument
+			&& this._cachedDocument.uri.toString() === document.uri.toString()
+			&& this._cachedDocument.version === document.version
+			&& this._cachedDocument.config.breaks === config.breaks
+			&& this._cachedDocument.config.linkify === config.linkify
 		) {
-			return this.tokens;
+			return this._tokens;
 		}
 		return undefined;
 	}
 
 	public update(document: ITextDocument, config: MarkdownItConfig, tokens: Token[]) {
-		this.cachedDocument = {
+		this._cachedDocument = {
 			uri: document.uri,
 			version: document.version,
 			config,
 		};
-		this.tokens = tokens;
+		this._tokens = tokens;
 	}
 
 	public clean(): void {
-		this.cachedDocument = undefined;
-		this.tokens = undefined;
+		this._cachedDocument = undefined;
+		this._tokens = undefined;
 	}
 }
 
 export interface RenderOutput {
 	html: string;
-	containingImages: { src: string }[];
+	containingImages: Set;
 }
 
 interface RenderEnv {
-	containingImages: { src: string }[];
+	containingImages: Set;
 	currentDocument: vscode.Uri | undefined;
 	resourceProvider: WebviewResourceProvider | undefined;
 }
@@ -103,7 +97,7 @@ export interface IMdParser {
 
 export class MarkdownItEngine implements IMdParser {
 
-	private md?: Promise;
+	private _md?: Promise;
 
 	private _slugCount = new Map();
 	private _tokenCache = new TokenCache();
@@ -111,26 +105,26 @@ export class MarkdownItEngine implements IMdParser {
 	public readonly slugifier: Slugifier;
 
 	public constructor(
-		private readonly contributionProvider: MarkdownContributionProvider,
+		private readonly _contributionProvider: MarkdownContributionProvider,
 		slugifier: Slugifier,
-		private readonly logger: ILogger,
+		private readonly _logger: ILogger,
 	) {
 		this.slugifier = slugifier;
 
-		contributionProvider.onContributionsChanged(() => {
+		_contributionProvider.onContributionsChanged(() => {
 			// Markdown plugin contributions may have changed
-			this.md = undefined;
+			this._md = undefined;
 		});
 	}
 
-	private async getEngine(config: MarkdownItConfig): Promise {
-		if (!this.md) {
-			this.md = (async () => {
+	private async _getEngine(config: MarkdownItConfig): Promise {
+		if (!this._md) {
+			this._md = (async () => {
 				const markdownIt = await import('markdown-it');
 				let md: MarkdownIt = markdownIt(await getMarkdownOptions(() => md));
 				md.linkify.set({ fuzzyLink: false });
 
-				for (const plugin of this.contributionProvider.contributions.markdownItPlugins.values()) {
+				for (const plugin of this._contributionProvider.contributions.markdownItPlugins.values()) {
 					try {
 						md = (await plugin)(md);
 					} catch (e) {
@@ -153,63 +147,63 @@ export class MarkdownItEngine implements IMdParser {
 					alt: ['paragraph', 'reference', 'blockquote', 'list']
 				});
 
-				this.addImageRenderer(md);
-				this.addFencedRenderer(md);
-				this.addLinkNormalizer(md);
-				this.addLinkValidator(md);
-				this.addNamedHeaders(md);
-				this.addLinkRenderer(md);
+				this._addImageRenderer(md);
+				this._addFencedRenderer(md);
+				this._addLinkNormalizer(md);
+				this._addLinkValidator(md);
+				this._addNamedHeaders(md);
+				this._addLinkRenderer(md);
 				md.use(pluginSourceMap);
 				return md;
 			})();
 		}
 
-		const md = await this.md!;
+		const md = await this._md!;
 		md.set(config);
 		return md;
 	}
 
 	public reloadPlugins() {
-		this.md = undefined;
+		this._md = undefined;
 	}
 
-	private tokenizeDocument(
+	private _tokenizeDocument(
 		document: ITextDocument,
 		config: MarkdownItConfig,
 		engine: MarkdownIt
 	): Token[] {
 		const cached = this._tokenCache.tryGetCached(document, config);
 		if (cached) {
-			this.resetSlugCount();
+			this._resetSlugCount();
 			return cached;
 		}
 
-		this.logger.verbose('MarkdownItEngine', `tokenizeDocument - ${document.uri}`);
-		const tokens = this.tokenizeString(document.getText(), engine);
+		this._logger.verbose('MarkdownItEngine', `tokenizeDocument - ${document.uri}`);
+		const tokens = this._tokenizeString(document.getText(), engine);
 		this._tokenCache.update(document, config, tokens);
 		return tokens;
 	}
 
-	private tokenizeString(text: string, engine: MarkdownIt) {
-		this.resetSlugCount();
+	private _tokenizeString(text: string, engine: MarkdownIt) {
+		this._resetSlugCount();
 
-		return engine.parse(text.replace(UNICODE_NEWLINE_REGEX, ''), {});
+		return engine.parse(text, {});
 	}
 
-	private resetSlugCount(): void {
+	private _resetSlugCount(): void {
 		this._slugCount = new Map();
 	}
 
 	public async render(input: ITextDocument | string, resourceProvider?: WebviewResourceProvider): Promise {
-		const config = this.getConfig(typeof input === 'string' ? undefined : input.uri);
-		const engine = await this.getEngine(config);
+		const config = this._getConfig(typeof input === 'string' ? undefined : input.uri);
+		const engine = await this._getEngine(config);
 
 		const tokens = typeof input === 'string'
-			? this.tokenizeString(input, engine)
-			: this.tokenizeDocument(input, config, engine);
+			? this._tokenizeString(input, engine)
+			: this._tokenizeDocument(input, config, engine);
 
 		const env: RenderEnv = {
-			containingImages: [],
+			containingImages: new Set(),
 			currentDocument: typeof input === 'string' ? undefined : input.uri,
 			resourceProvider,
 		};
@@ -226,16 +220,16 @@ export class MarkdownItEngine implements IMdParser {
 	}
 
 	public async tokenize(document: ITextDocument): Promise {
-		const config = this.getConfig(document.uri);
-		const engine = await this.getEngine(config);
-		return this.tokenizeDocument(document, config, engine);
+		const config = this._getConfig(document.uri);
+		const engine = await this._getEngine(config);
+		return this._tokenizeDocument(document, config, engine);
 	}
 
 	public cleanCache(): void {
 		this._tokenCache.clean();
 	}
 
-	private getConfig(resource?: vscode.Uri): MarkdownItConfig {
+	private _getConfig(resource?: vscode.Uri): MarkdownItConfig {
 		const config = vscode.workspace.getConfiguration('markdown', resource ?? null);
 		return {
 			breaks: config.get('preview.breaks', false),
@@ -244,20 +238,16 @@ export class MarkdownItEngine implements IMdParser {
 		};
 	}
 
-	private addImageRenderer(md: MarkdownIt): void {
+	private _addImageRenderer(md: MarkdownIt): void {
 		const original = md.renderer.rules.image;
 		md.renderer.rules.image = (tokens: Token[], idx: number, options, env: RenderEnv, self) => {
 			const token = tokens[idx];
-			token.attrJoin('class', 'loading');
-
 			const src = token.attrGet('src');
 			if (src) {
-				env.containingImages?.push({ src });
-				const imgHash = stringHash(src);
-				token.attrSet('id', `image-hash-${imgHash}`);
+				env.containingImages?.add(src);
 
 				if (!token.attrGet('data-src')) {
-					token.attrSet('src', this.toResourceUri(src, env.currentDocument, env.resourceProvider));
+					token.attrSet('src', this._toResourceUri(src, env.currentDocument, env.resourceProvider));
 					token.attrSet('data-src', src);
 				}
 			}
@@ -270,11 +260,11 @@ export class MarkdownItEngine implements IMdParser {
 		};
 	}
 
-	private addFencedRenderer(md: MarkdownIt): void {
+	private _addFencedRenderer(md: MarkdownIt): void {
 		const original = md.renderer.rules['fenced'];
 		md.renderer.rules['fenced'] = (tokens: Token[], idx: number, options, env, self) => {
 			const token = tokens[idx];
-			if (token.map && token.map.length) {
+			if (token.map?.length) {
 				token.attrJoin('class', 'hljs');
 			}
 
@@ -286,7 +276,7 @@ export class MarkdownItEngine implements IMdParser {
 		};
 	}
 
-	private addLinkNormalizer(md: MarkdownIt): void {
+	private _addLinkNormalizer(md: MarkdownIt): void {
 		const normalizeLink = md.normalizeLink;
 		md.normalizeLink = (link: string) => {
 			try {
@@ -302,7 +292,7 @@ export class MarkdownItEngine implements IMdParser {
 		};
 	}
 
-	private addLinkValidator(md: MarkdownIt): void {
+	private _addLinkValidator(md: MarkdownIt): void {
 		const validateLink = md.validateLink;
 		md.validateLink = (link: string) => {
 			return validateLink(link)
@@ -312,7 +302,7 @@ export class MarkdownItEngine implements IMdParser {
 		};
 	}
 
-	private addNamedHeaders(md: MarkdownIt): void {
+	private _addNamedHeaders(md: MarkdownIt): void {
 		const original = md.renderer.rules.heading_open;
 		md.renderer.rules.heading_open = (tokens: Token[], idx: number, options, env, self) => {
 			const title = tokens[idx + 1].children!.reduce((acc, t) => acc + t.content, '');
@@ -336,7 +326,7 @@ export class MarkdownItEngine implements IMdParser {
 		};
 	}
 
-	private addLinkRenderer(md: MarkdownIt): void {
+	private _addLinkRenderer(md: MarkdownIt): void {
 		const original = md.renderer.rules.link_open;
 
 		md.renderer.rules.link_open = (tokens: Token[], idx: number, options, env, self) => {
@@ -354,7 +344,7 @@ export class MarkdownItEngine implements IMdParser {
 		};
 	}
 
-	private toResourceUri(href: string, currentDocument: vscode.Uri | undefined, resourceProvider: WebviewResourceProvider | undefined): string {
+	private _toResourceUri(href: string, currentDocument: vscode.Uri | undefined, resourceProvider: WebviewResourceProvider | undefined): string {
 		try {
 			// Support file:// links
 			if (isOfScheme(Schemes.file, href)) {
@@ -422,6 +412,12 @@ async function getMarkdownOptions(md: () => MarkdownIt): Promise;
-
-	public readonly slugifier: Slugifier;
-
-	constructor(
-		engine: MarkdownItEngine,
-		workspace: IMdWorkspace,
-	) {
-		super();
-
-		this.slugifier = engine.slugifier;
-
-		this._cache = this._register(new MdDocumentInfoCache(workspace, doc => {
-			return engine.tokenize(doc);
-		}));
-	}
-
-	public tokenize(document: ITextDocument): Promise {
-		return this._cache.getForDocument(document);
-	}
-}
diff --git a/extensions/markdown-language-features/src/markdownExtensions.ts b/extensions/markdown-language-features/src/markdownExtensions.ts
index 9801c08af5..eff6447611 100644
--- a/extensions/markdown-language-features/src/markdownExtensions.ts
+++ b/extensions/markdown-language-features/src/markdownExtensions.ts
@@ -127,7 +127,7 @@ class VSCodeExtensionMarkdownContributionProvider extends Disposable implements
 		super();
 
 		this._register(vscode.extensions.onDidChange(() => {
-			const currentContributions = this.getCurrentContributions();
+			const currentContributions = this._getCurrentContributions();
 			const existingContributions = this._contributions || MarkdownContributions.Empty;
 			if (!MarkdownContributions.equal(existingContributions, currentContributions)) {
 				this._contributions = currentContributions;
@@ -144,13 +144,11 @@ class VSCodeExtensionMarkdownContributionProvider extends Disposable implements
 	public readonly onContributionsChanged = this._onContributionsChanged.event;
 
 	public get contributions(): MarkdownContributions {
-		if (!this._contributions) {
-			this._contributions = this.getCurrentContributions();
-		}
+		this._contributions ??= this._getCurrentContributions();
 		return this._contributions;
 	}
 
-	private getCurrentContributions(): MarkdownContributions {
+	private _getCurrentContributions(): MarkdownContributions {
 		return vscode.extensions.all
 			.map(MarkdownContributions.fromExtension)
 			.reduce(MarkdownContributions.merge, MarkdownContributions.Empty);
diff --git a/extensions/markdown-language-features/src/preview/documentRenderer.ts b/extensions/markdown-language-features/src/preview/documentRenderer.ts
index 3c8ca0afdf..3434dda925 100644
--- a/extensions/markdown-language-features/src/preview/documentRenderer.ts
+++ b/extensions/markdown-language-features/src/preview/documentRenderer.ts
@@ -4,7 +4,6 @@
  *--------------------------------------------------------------------------------------------*/
 
 import * as vscode from 'vscode';
-import * as nls from 'vscode-nls';
 import * as uri from 'vscode-uri';
 import { ILogger } from '../logging';
 import { MarkdownItEngine } from '../markdownEngine';
@@ -14,7 +13,6 @@ import { WebviewResourceProvider } from '../util/resources';
 import { MarkdownPreviewConfiguration, MarkdownPreviewConfigurationManager } from './previewConfig';
 import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from './security';
 
-const localize = nls.loadMessageBundle();
 
 /**
  * Strings used inside the markdown preview.
@@ -23,36 +21,35 @@ const localize = nls.loadMessageBundle();
  * can be localized using our normal localization process.
  */
 const previewStrings = {
-	cspAlertMessageText: localize(
-		'preview.securityMessage.text',
-		'Some content has been disabled in this document'),
+	cspAlertMessageText: vscode.l10n.t("Some content has been disabled in this document"),
 
-	cspAlertMessageTitle: localize(
-		'preview.securityMessage.title',
-		'Potentially unsafe or insecure content has been disabled in the Markdown preview. Change the Markdown preview security setting to allow insecure content or enable scripts'),
+	cspAlertMessageTitle: vscode.l10n.t("Potentially unsafe or insecure content has been disabled in the Markdown preview. Change the Markdown preview security setting to allow insecure content or enable scripts"),
 
-	cspAlertMessageLabel: localize(
-		'preview.securityMessage.label',
-		'Content Disabled Security Warning')
+	cspAlertMessageLabel: vscode.l10n.t("Content Disabled Security Warning")
 };
 
 export interface MarkdownContentProviderOutput {
 	html: string;
-	containingImages: { src: string }[];
+	containingImages: Set;
 }
 
+export interface ImageInfo {
+	readonly id: string;
+	readonly width: number;
+	readonly height: number;
+}
 
 export class MdDocumentRenderer {
 	constructor(
-		private readonly engine: MarkdownItEngine,
-		private readonly context: vscode.ExtensionContext,
-		private readonly cspArbiter: ContentSecurityPolicyArbiter,
-		private readonly contributionProvider: MarkdownContributionProvider,
-		private readonly logger: ILogger
+		private readonly _engine: MarkdownItEngine,
+		private readonly _context: vscode.ExtensionContext,
+		private readonly _cspArbiter: ContentSecurityPolicyArbiter,
+		private readonly _contributionProvider: MarkdownContributionProvider,
+		private readonly _logger: ILogger
 	) {
 		this.iconPath = {
-			dark: vscode.Uri.joinPath(this.context.extensionUri, 'media', 'preview-dark.svg'),
-			light: vscode.Uri.joinPath(this.context.extensionUri, 'media', 'preview-light.svg'),
+			dark: vscode.Uri.joinPath(this._context.extensionUri, 'media', 'preview-dark.svg'),
+			light: vscode.Uri.joinPath(this._context.extensionUri, 'media', 'preview-light.svg'),
 		};
 	}
 
@@ -62,8 +59,10 @@ export class MdDocumentRenderer {
 		markdownDocument: vscode.TextDocument,
 		resourceProvider: WebviewResourceProvider,
 		previewConfigurations: MarkdownPreviewConfigurationManager,
-		initialLine: number | undefined = undefined,
+		initialLine: number | undefined,
+		selectedLine: number | undefined,
 		state: any | undefined,
+		imageInfo: readonly ImageInfo[],
 		token: vscode.CancellationToken
 	): Promise {
 		const sourceUri = markdownDocument.uri;
@@ -72,27 +71,27 @@ export class MdDocumentRenderer {
 			source: sourceUri.toString(),
 			fragment: state?.fragment || markdownDocument.uri.fragment || undefined,
 			line: initialLine,
-			lineCount: markdownDocument.lineCount,
+			selectedLine,
 			scrollPreviewWithEditor: config.scrollPreviewWithEditor,
 			scrollEditorWithPreview: config.scrollEditorWithPreview,
 			doubleClickToSwitchToEditor: config.doubleClickToSwitchToEditor,
-			disableSecurityWarnings: this.cspArbiter.shouldDisableSecurityWarnings(),
+			disableSecurityWarnings: this._cspArbiter.shouldDisableSecurityWarnings(),
 			webviewResourceRoot: resourceProvider.asWebviewUri(markdownDocument.uri).toString(),
 		};
 
-		this.logger.verbose('DocumentRenderer', `provideTextDocumentContent - ${markdownDocument.uri}`, initialData);
+		this._logger.verbose('DocumentRenderer', `provideTextDocumentContent - ${markdownDocument.uri}`, initialData);
 
 		// Content Security Policy
 		const nonce = getNonce();
-		const csp = this.getCsp(resourceProvider, sourceUri, nonce);
+		const csp = this._getCsp(resourceProvider, sourceUri, nonce);
 
 		const body = await this.renderBody(markdownDocument, resourceProvider);
 		if (token.isCancellationRequested) {
-			return { html: '', containingImages: [] };
+			return { html: '', containingImages: new Set() };
 		}
 
 		const html = `
-			
+			
 			
 				
 				${csp}
@@ -100,13 +99,13 @@ export class MdDocumentRenderer {
 					data-settings="${escapeAttribute(JSON.stringify(initialData))}"
 					data-strings="${escapeAttribute(JSON.stringify(previewStrings))}"
 					data-state="${escapeAttribute(JSON.stringify(state || {}))}">
-				
-				${this.getStyles(resourceProvider, sourceUri, config, state)}
+				
+				${this._getStyles(resourceProvider, sourceUri, config, imageInfo)}
 				
 			
 			
 				${body.html}
-				${this.getScripts(resourceProvider, nonce)}
+				${this._getScripts(resourceProvider, nonce)}
 			
 			`;
 		return {
@@ -119,7 +118,7 @@ export class MdDocumentRenderer {
 		markdownDocument: vscode.TextDocument,
 		resourceProvider: WebviewResourceProvider,
 	): Promise {
-		const rendered = await this.engine.render(markdownDocument, resourceProvider);
+		const rendered = await this._engine.render(markdownDocument, resourceProvider);
 		const html = `
${rendered.html}
`; return { html, @@ -129,7 +128,7 @@ export class MdDocumentRenderer { public renderFileNotFoundDocument(resource: vscode.Uri): string { const resourcePath = uri.Utils.basename(resource); - const body = localize('preview.notFound', '{0} cannot be found', resourcePath); + const body = vscode.l10n.t('{0} cannot be found', resourcePath); return ` @@ -138,13 +137,13 @@ export class MdDocumentRenderer { `; } - private extensionResourcePath(resourceProvider: WebviewResourceProvider, mediaFile: string): string { + private _extensionResourcePath(resourceProvider: WebviewResourceProvider, mediaFile: string): string { const webviewResource = resourceProvider.asWebviewUri( - vscode.Uri.joinPath(this.context.extensionUri, 'media', mediaFile)); + vscode.Uri.joinPath(this._context.extensionUri, 'media', mediaFile)); return webviewResource.toString(); } - private fixHref(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, href: string): string { + private _fixHref(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, href: string): string { if (!href) { return href; } @@ -168,18 +167,18 @@ export class MdDocumentRenderer { return resourceProvider.asWebviewUri(vscode.Uri.joinPath(uri.Utils.dirname(resource), href)).toString(); } - private computeCustomStyleSheetIncludes(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string { + private _computeCustomStyleSheetIncludes(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration): string { if (!Array.isArray(config.styles)) { return ''; } const out: string[] = []; for (const style of config.styles) { - out.push(``); + out.push(``); } return out.join('\n'); } - private getSettingsOverrideStyles(config: MarkdownPreviewConfiguration): string { + private _getSettingsOverrideStyles(config: MarkdownPreviewConfiguration): string { return [ config.fontFamily ? `--markdown-font-family: ${config.fontFamily};` : '', isNaN(config.fontSize) ? '' : `--markdown-font-size: ${config.fontSize}px;`, @@ -187,35 +186,37 @@ export class MdDocumentRenderer { ].join(' '); } - private getImageStabilizerStyles(state?: any) { + private _getImageStabilizerStyles(imageInfo: readonly ImageInfo[]): string { + if (!imageInfo.length) { + return ''; + } + let ret = '\n'; return ret; } - private getStyles(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration, state?: any): string { + private _getStyles(resourceProvider: WebviewResourceProvider, resource: vscode.Uri, config: MarkdownPreviewConfiguration, imageInfo: readonly ImageInfo[]): string { const baseStyles: string[] = []; - for (const resource of this.contributionProvider.contributions.previewStyles) { + for (const resource of this._contributionProvider.contributions.previewStyles) { baseStyles.push(``); } return `${baseStyles.join('\n')} - ${this.computeCustomStyleSheetIncludes(resourceProvider, resource, config)} - ${this.getImageStabilizerStyles(state)}`; + ${this._computeCustomStyleSheetIncludes(resourceProvider, resource, config)} + ${this._getImageStabilizerStyles(imageInfo)}`; } - private getScripts(resourceProvider: WebviewResourceProvider, nonce: string): string { + private _getScripts(resourceProvider: WebviewResourceProvider, nonce: string): string { const out: string[] = []; - for (const resource of this.contributionProvider.contributions.previewScripts) { + for (const resource of this._contributionProvider.contributions.previewScripts) { out.push(` + +`; + } + + private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise { + if (resource.scheme === 'git') { + const stat = await vscode.workspace.fs.stat(resource); + if (stat.size === 0) { + // The file is stored on git lfs + return null; + } + } + + // Avoid adding cache busting if there is already a query string + if (resource.query) { + return webviewEditor.webview.asWebviewUri(resource).toString(); + } + return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(); + } + + private extensionResource(...parts: string[]) { + return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts)); + } +} + +export function registerAudioPreviewSupport(context: vscode.ExtensionContext, binarySizeStatusBarEntry: BinarySizeStatusBarEntry): vscode.Disposable { + const provider = new AudioPreviewProvider(context.extensionUri, binarySizeStatusBarEntry); + return vscode.window.registerCustomEditorProvider(AudioPreviewProvider.viewType, provider, { + supportsMultipleEditorsPerDocument: true, + webviewOptions: { + retainContextWhenHidden: true, + } + }); +} diff --git a/extensions/image-preview/src/binarySizeStatusBarEntry.ts b/extensions/media-preview/src/binarySizeStatusBarEntry.ts similarity index 63% rename from extensions/image-preview/src/binarySizeStatusBarEntry.ts rename to extensions/media-preview/src/binarySizeStatusBarEntry.ts index 0e23ca16f8..25b4c96cd4 100644 --- a/extensions/image-preview/src/binarySizeStatusBarEntry.ts +++ b/extensions/media-preview/src/binarySizeStatusBarEntry.ts @@ -4,10 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; import { PreviewStatusBarEntry } from './ownedStatusBarEntry'; -const localize = nls.loadMessageBundle(); class BinarySize { static readonly KB = 1024; @@ -17,32 +15,32 @@ class BinarySize { static formatSize(size: number): string { if (size < BinarySize.KB) { - return localize('sizeB', "{0}B", size); + return vscode.l10n.t("{0}B", size); } if (size < BinarySize.MB) { - return localize('sizeKB', "{0}KB", (size / BinarySize.KB).toFixed(2)); + return vscode.l10n.t("{0}KB", (size / BinarySize.KB).toFixed(2)); } if (size < BinarySize.GB) { - return localize('sizeMB', "{0}MB", (size / BinarySize.MB).toFixed(2)); + return vscode.l10n.t("{0}MB", (size / BinarySize.MB).toFixed(2)); } if (size < BinarySize.TB) { - return localize('sizeGB', "{0}GB", (size / BinarySize.GB).toFixed(2)); + return vscode.l10n.t("{0}GB", (size / BinarySize.GB).toFixed(2)); } - return localize('sizeTB', "{0}TB", (size / BinarySize.TB).toFixed(2)); + return vscode.l10n.t("{0}TB", (size / BinarySize.TB).toFixed(2)); } } export class BinarySizeStatusBarEntry extends PreviewStatusBarEntry { constructor() { - super('status.imagePreview.binarySize', localize('sizeStatusBar.name', "Image Binary Size"), vscode.StatusBarAlignment.Right, 100); + super('status.imagePreview.binarySize', vscode.l10n.t("Image Binary Size"), vscode.StatusBarAlignment.Right, 100); } - public show(owner: string, size: number | undefined) { + public show(owner: unknown, size: number | undefined) { if (typeof size === 'number') { super.showItem(owner, BinarySize.formatSize(size)); } else { diff --git a/extensions/media-preview/src/extension.ts b/extensions/media-preview/src/extension.ts new file mode 100644 index 0000000000..8b41d82995 --- /dev/null +++ b/extensions/media-preview/src/extension.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { registerAudioPreviewSupport } from './audioPreview'; +import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; +import { registerImagePreviewSupport } from './imagePreview'; +import { registerVideoPreviewSupport } from './videoPreview'; + +export function activate(context: vscode.ExtensionContext) { + const binarySizeStatusBarEntry = new BinarySizeStatusBarEntry(); + context.subscriptions.push(binarySizeStatusBarEntry); + + context.subscriptions.push(registerImagePreviewSupport(context, binarySizeStatusBarEntry)); + context.subscriptions.push(registerAudioPreviewSupport(context, binarySizeStatusBarEntry)); + context.subscriptions.push(registerVideoPreviewSupport(context, binarySizeStatusBarEntry)); +} diff --git a/extensions/media-preview/src/imagePreview/index.ts b/extensions/media-preview/src/imagePreview/index.ts new file mode 100644 index 0000000000..764fe23ea1 --- /dev/null +++ b/extensions/media-preview/src/imagePreview/index.ts @@ -0,0 +1,246 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { BinarySizeStatusBarEntry } from '../binarySizeStatusBarEntry'; +import { MediaPreview, PreviewState, reopenAsText } from '../mediaPreview'; +import { escapeAttribute, getNonce } from '../util/dom'; +import { SizeStatusBarEntry } from './sizeStatusBarEntry'; +import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry'; + + +export class PreviewManager implements vscode.CustomReadonlyEditorProvider { + + public static readonly viewType = 'imagePreview.previewEditor'; + + private readonly _previews = new Set(); + private _activePreview: ImagePreview | undefined; + + constructor( + private readonly extensionRoot: vscode.Uri, + private readonly sizeStatusBarEntry: SizeStatusBarEntry, + private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, + private readonly zoomStatusBarEntry: ZoomStatusBarEntry, + ) { } + + public async openCustomDocument(uri: vscode.Uri) { + return { uri, dispose: () => { } }; + } + + public async resolveCustomEditor( + document: vscode.CustomDocument, + webviewEditor: vscode.WebviewPanel, + ): Promise { + const preview = new ImagePreview(this.extensionRoot, document.uri, webviewEditor, this.sizeStatusBarEntry, this.binarySizeStatusBarEntry, this.zoomStatusBarEntry); + this._previews.add(preview); + this.setActivePreview(preview); + + webviewEditor.onDidDispose(() => { this._previews.delete(preview); }); + + webviewEditor.onDidChangeViewState(() => { + if (webviewEditor.active) { + this.setActivePreview(preview); + } else if (this._activePreview === preview && !webviewEditor.active) { + this.setActivePreview(undefined); + } + }); + } + + public get activePreview() { return this._activePreview; } + + private setActivePreview(value: ImagePreview | undefined): void { + this._activePreview = value; + } +} + + +class ImagePreview extends MediaPreview { + + private _imageSize: string | undefined; + private _imageZoom: Scale | undefined; + + private readonly emptyPngDataUri = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42gEFAPr/AP///wAI/AL+Sr4t6gAAAABJRU5ErkJggg=='; + + constructor( + private readonly extensionRoot: vscode.Uri, + resource: vscode.Uri, + webviewEditor: vscode.WebviewPanel, + private readonly sizeStatusBarEntry: SizeStatusBarEntry, + binarySizeStatusBarEntry: BinarySizeStatusBarEntry, + private readonly zoomStatusBarEntry: ZoomStatusBarEntry, + ) { + super(extensionRoot, resource, webviewEditor, binarySizeStatusBarEntry); + + this._register(webviewEditor.webview.onDidReceiveMessage(message => { + switch (message.type) { + case 'size': { + this._imageSize = message.value; + this.updateState(); + break; + } + case 'zoom': { + this._imageZoom = message.value; + this.updateState(); + break; + } + case 'reopen-as-text': { + reopenAsText(resource, webviewEditor.viewColumn); + break; + } + } + })); + + this._register(zoomStatusBarEntry.onDidChangeScale(e => { + if (this.previewState === PreviewState.Active) { + this.webviewEditor.webview.postMessage({ type: 'setScale', scale: e.scale }); + } + })); + + this._register(webviewEditor.onDidChangeViewState(() => { + this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); + })); + + this._register(webviewEditor.onDidDispose(() => { + if (this.previewState === PreviewState.Active) { + this.sizeStatusBarEntry.hide(this); + this.zoomStatusBarEntry.hide(this); + } + this.previewState = PreviewState.Disposed; + })); + + this.updateBinarySize(); + this.render(); + this.updateState(); + + this.webviewEditor.webview.postMessage({ type: 'setActive', value: this.webviewEditor.active }); + } + + public override dispose(): void { + super.dispose(); + this.sizeStatusBarEntry.hide(this); + this.zoomStatusBarEntry.hide(this); + } + + public zoomIn() { + if (this.previewState === PreviewState.Active) { + this.webviewEditor.webview.postMessage({ type: 'zoomIn' }); + } + } + + public zoomOut() { + if (this.previewState === PreviewState.Active) { + this.webviewEditor.webview.postMessage({ type: 'zoomOut' }); + } + } + + public copyImage() { + if (this.previewState === PreviewState.Active) { + this.webviewEditor.reveal(); + this.webviewEditor.webview.postMessage({ type: 'copyImage' }); + } + } + + protected override updateState() { + super.updateState(); + + if (this.previewState === PreviewState.Disposed) { + return; + } + + if (this.webviewEditor.active) { + this.sizeStatusBarEntry.show(this, this._imageSize || ''); + this.zoomStatusBarEntry.show(this, this._imageZoom || 'fit'); + } else { + this.sizeStatusBarEntry.hide(this); + this.zoomStatusBarEntry.hide(this); + } + } + + protected override async getWebviewContents(): Promise { + const version = Date.now().toString(); + const settings = { + src: await this.getResourcePath(this.webviewEditor, this.resource, version), + }; + + const nonce = getNonce(); + + const cspSource = this.webviewEditor.webview.cspSource; + return /* html */` + + + + + + + + Image Preview + + + + + + + +
+
+

${vscode.l10n.t("An error occurred while loading the image.")}

+ ${vscode.l10n.t("Open file using VS Code's standard text/binary editor?")} +
+ + +`; + } + + private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise { + if (resource.scheme === 'git') { + const stat = await vscode.workspace.fs.stat(resource); + if (stat.size === 0) { + return this.emptyPngDataUri; + } + } + + // Avoid adding cache busting if there is already a query string + if (resource.query) { + return webviewEditor.webview.asWebviewUri(resource).toString(); + } + return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(); + } + + private extensionResource(...parts: string[]) { + return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts)); + } +} + + +export function registerImagePreviewSupport(context: vscode.ExtensionContext, binarySizeStatusBarEntry: BinarySizeStatusBarEntry): vscode.Disposable { + const disposables: vscode.Disposable[] = []; + + const sizeStatusBarEntry = new SizeStatusBarEntry(); + disposables.push(sizeStatusBarEntry); + + const zoomStatusBarEntry = new ZoomStatusBarEntry(); + disposables.push(zoomStatusBarEntry); + + const previewManager = new PreviewManager(context.extensionUri, sizeStatusBarEntry, binarySizeStatusBarEntry, zoomStatusBarEntry); + + disposables.push(vscode.window.registerCustomEditorProvider(PreviewManager.viewType, previewManager, { + supportsMultipleEditorsPerDocument: true, + })); + + disposables.push(vscode.commands.registerCommand('imagePreview.zoomIn', () => { + previewManager.activePreview?.zoomIn(); + })); + + disposables.push(vscode.commands.registerCommand('imagePreview.zoomOut', () => { + previewManager.activePreview?.zoomOut(); + })); + + disposables.push(vscode.commands.registerCommand('imagePreview.copyImage', () => { + previewManager.activePreview?.copyImage(); + })); + + return vscode.Disposable.from(...disposables); +} diff --git a/extensions/image-preview/src/sizeStatusBarEntry.ts b/extensions/media-preview/src/imagePreview/sizeStatusBarEntry.ts similarity index 59% rename from extensions/image-preview/src/sizeStatusBarEntry.ts rename to extensions/media-preview/src/imagePreview/sizeStatusBarEntry.ts index 73ce2eecf7..368ca9a8c7 100644 --- a/extensions/image-preview/src/sizeStatusBarEntry.ts +++ b/extensions/media-preview/src/imagePreview/sizeStatusBarEntry.ts @@ -4,18 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; -import { PreviewStatusBarEntry } from './ownedStatusBarEntry'; +import { PreviewStatusBarEntry } from '../ownedStatusBarEntry'; -const localize = nls.loadMessageBundle(); export class SizeStatusBarEntry extends PreviewStatusBarEntry { constructor() { - super('status.imagePreview.size', localize('sizeStatusBar.name', "Image Size"), vscode.StatusBarAlignment.Right, 101 /* to the left of editor status (100) */); + super('status.imagePreview.size', vscode.l10n.t("Image Size"), vscode.StatusBarAlignment.Right, 101 /* to the left of editor status (100) */); } - public show(owner: string, text: string) { + public show(owner: unknown, text: string) { this.showItem(owner, text); } } diff --git a/extensions/image-preview/src/zoomStatusBarEntry.ts b/extensions/media-preview/src/imagePreview/zoomStatusBarEntry.ts similarity index 73% rename from extensions/image-preview/src/zoomStatusBarEntry.ts rename to extensions/media-preview/src/imagePreview/zoomStatusBarEntry.ts index 2563755070..bda501b591 100644 --- a/extensions/image-preview/src/zoomStatusBarEntry.ts +++ b/extensions/media-preview/src/imagePreview/zoomStatusBarEntry.ts @@ -4,10 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; -import { PreviewStatusBarEntry as OwnedStatusBarEntry } from './ownedStatusBarEntry'; +import { PreviewStatusBarEntry as OwnedStatusBarEntry } from '../ownedStatusBarEntry'; -const localize = nls.loadMessageBundle(); const selectZoomLevelCommandId = '_imagePreview.selectZoomLevel'; @@ -19,7 +17,7 @@ export class ZoomStatusBarEntry extends OwnedStatusBarEntry { public readonly onDidChangeScale = this._onDidChangeScale.event; constructor() { - super('status.imagePreview.zoom', localize('zoomStatusBar.name', "Image Zoom"), vscode.StatusBarAlignment.Right, 102 /* to the left of editor size entry (101) */); + super('status.imagePreview.zoom', vscode.l10n.t("Image Zoom"), vscode.StatusBarAlignment.Right, 102 /* to the left of editor size entry (101) */); this._register(vscode.commands.registerCommand(selectZoomLevelCommandId, async () => { type MyPickItem = vscode.QuickPickItem & { scale: Scale }; @@ -31,7 +29,7 @@ export class ZoomStatusBarEntry extends OwnedStatusBarEntry { })); const pick = await vscode.window.showQuickPick(options, { - placeHolder: localize('zoomStatusBar.placeholder', "Select zoom level") + placeHolder: vscode.l10n.t("Select zoom level") }); if (pick) { this._onDidChangeScale.fire({ scale: pick.scale }); @@ -41,13 +39,13 @@ export class ZoomStatusBarEntry extends OwnedStatusBarEntry { this.entry.command = selectZoomLevelCommandId; } - public show(owner: string, scale: Scale) { + public show(owner: unknown, scale: Scale) { this.showItem(owner, this.zoomLabel(scale)); } private zoomLabel(scale: Scale): string { return scale === 'fit' - ? localize('zoomStatusBar.wholeImageLabel', "Whole Image") + ? vscode.l10n.t("Whole Image") : `${Math.round(scale * 100)}%`; } } diff --git a/extensions/media-preview/src/mediaPreview.ts b/extensions/media-preview/src/mediaPreview.ts new file mode 100644 index 0000000000..b04ae5615a --- /dev/null +++ b/extensions/media-preview/src/mediaPreview.ts @@ -0,0 +1,107 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Utils } from 'vscode-uri'; +import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; +import { Disposable } from './util/dispose'; + +export function reopenAsText(resource: vscode.Uri, viewColumn: vscode.ViewColumn | undefined) { + vscode.commands.executeCommand('vscode.openWith', resource, 'default', viewColumn); +} + +export const enum PreviewState { + Disposed, + Visible, + Active, +} + +export abstract class MediaPreview extends Disposable { + + protected previewState = PreviewState.Visible; + private _binarySize: number | undefined; + + constructor( + extensionRoot: vscode.Uri, + protected readonly resource: vscode.Uri, + protected readonly webviewEditor: vscode.WebviewPanel, + private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, + ) { + super(); + + webviewEditor.webview.options = { + enableScripts: true, + enableForms: false, + localResourceRoots: [ + Utils.dirname(resource), + extensionRoot, + ] + }; + + this._register(webviewEditor.onDidChangeViewState(() => { + this.updateState(); + })); + + this._register(webviewEditor.onDidDispose(() => { + this.previewState = PreviewState.Disposed; + this.dispose(); + })); + + const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*'))); + this._register(watcher.onDidChange(e => { + if (e.toString() === this.resource.toString()) { + this.updateBinarySize(); + this.render(); + } + })); + + this._register(watcher.onDidDelete(e => { + if (e.toString() === this.resource.toString()) { + this.webviewEditor.dispose(); + } + })); + } + + public override dispose() { + super.dispose(); + this.binarySizeStatusBarEntry.hide(this); + } + + protected updateBinarySize() { + vscode.workspace.fs.stat(this.resource).then(({ size }) => { + this._binarySize = size; + this.updateState(); + }); + } + + protected async render() { + if (this.previewState === PreviewState.Disposed) { + return; + } + + const content = await this.getWebviewContents(); + if (this.previewState as PreviewState === PreviewState.Disposed) { + return; + } + + this.webviewEditor.webview.html = content; + } + + protected abstract getWebviewContents(): Promise; + + protected updateState() { + if (this.previewState === PreviewState.Disposed) { + return; + } + + if (this.webviewEditor.active) { + this.previewState = PreviewState.Active; + this.binarySizeStatusBarEntry.show(this, this._binarySize); + } else { + this.binarySizeStatusBarEntry.hide(this); + this.previewState = PreviewState.Visible; + } + } +} diff --git a/extensions/image-preview/src/ownedStatusBarEntry.ts b/extensions/media-preview/src/ownedStatusBarEntry.ts similarity index 84% rename from extensions/image-preview/src/ownedStatusBarEntry.ts rename to extensions/media-preview/src/ownedStatusBarEntry.ts index bd394ec3d1..2fb45e5c6e 100644 --- a/extensions/image-preview/src/ownedStatusBarEntry.ts +++ b/extensions/media-preview/src/ownedStatusBarEntry.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { Disposable } from './dispose'; +import { Disposable } from './util/dispose'; export abstract class PreviewStatusBarEntry extends Disposable { - private _showOwner: string | undefined; + private _showOwner: unknown | undefined; protected readonly entry: vscode.StatusBarItem; @@ -17,13 +17,13 @@ export abstract class PreviewStatusBarEntry extends Disposable { this.entry.name = name; } - protected showItem(owner: string, text: string) { + protected showItem(owner: unknown, text: string) { this._showOwner = owner; this.entry.text = text; this.entry.show(); } - public hide(owner: string) { + public hide(owner: unknown) { if (owner === this._showOwner) { this.entry.hide(); this._showOwner = undefined; diff --git a/extensions/image-preview/src/dispose.ts b/extensions/media-preview/src/util/dispose.ts similarity index 100% rename from extensions/image-preview/src/dispose.ts rename to extensions/media-preview/src/util/dispose.ts diff --git a/extensions/media-preview/src/util/dom.ts b/extensions/media-preview/src/util/dom.ts new file mode 100644 index 0000000000..0435f52b71 --- /dev/null +++ b/extensions/media-preview/src/util/dom.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + +export function escapeAttribute(value: string | vscode.Uri): string { + return value.toString().replace(/"/g, '"'); +} + +export function getNonce() { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 64; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/extensions/media-preview/src/videoPreview.ts b/extensions/media-preview/src/videoPreview.ts new file mode 100644 index 0000000000..7e3df1def7 --- /dev/null +++ b/extensions/media-preview/src/videoPreview.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; +import { MediaPreview, reopenAsText } from './mediaPreview'; +import { escapeAttribute, getNonce } from './util/dom'; + + +class VideoPreviewProvider implements vscode.CustomReadonlyEditorProvider { + + public static readonly viewType = 'vscode.videoPreview'; + + constructor( + private readonly extensionRoot: vscode.Uri, + private readonly binarySizeStatusBarEntry: BinarySizeStatusBarEntry, + ) { } + + public async openCustomDocument(uri: vscode.Uri) { + return { uri, dispose: () => { } }; + } + + public async resolveCustomEditor(document: vscode.CustomDocument, webviewEditor: vscode.WebviewPanel): Promise { + new VideoPreview(this.extensionRoot, document.uri, webviewEditor, this.binarySizeStatusBarEntry); + } +} + + +class VideoPreview extends MediaPreview { + + constructor( + private readonly extensionRoot: vscode.Uri, + resource: vscode.Uri, + webviewEditor: vscode.WebviewPanel, + binarySizeStatusBarEntry: BinarySizeStatusBarEntry, + ) { + super(extensionRoot, resource, webviewEditor, binarySizeStatusBarEntry); + + this._register(webviewEditor.webview.onDidReceiveMessage(message => { + switch (message.type) { + case 'reopen-as-text': { + reopenAsText(resource, webviewEditor.viewColumn); + break; + } + } + })); + + this.updateBinarySize(); + this.render(); + this.updateState(); + } + + protected async getWebviewContents(): Promise { + const version = Date.now().toString(); + const settings = { + src: await this.getResourcePath(this.webviewEditor, this.resource, version), + }; + + const nonce = getNonce(); + + const cspSource = this.webviewEditor.webview.cspSource; + return /* html */` + + + + + + + + Video Preview + + + + + + + +
+
+

${vscode.l10n.t("An error occurred while loading the video file.")}

+ ${vscode.l10n.t("Open file using VS Code's standard text/binary editor?")} +
+ + +`; + } + + private async getResourcePath(webviewEditor: vscode.WebviewPanel, resource: vscode.Uri, version: string): Promise { + if (resource.scheme === 'git') { + const stat = await vscode.workspace.fs.stat(resource); + if (stat.size === 0) { + // The file is stored on git lfs + return null; + } + } + + // Avoid adding cache busting if there is already a query string + if (resource.query) { + return webviewEditor.webview.asWebviewUri(resource).toString(); + } + return webviewEditor.webview.asWebviewUri(resource).with({ query: `version=${version}` }).toString(); + } + + private extensionResource(...parts: string[]) { + return this.webviewEditor.webview.asWebviewUri(vscode.Uri.joinPath(this.extensionRoot, ...parts)); + } +} + +export function registerVideoPreviewSupport(context: vscode.ExtensionContext, binarySizeStatusBarEntry: BinarySizeStatusBarEntry): vscode.Disposable { + const provider = new VideoPreviewProvider(context.extensionUri, binarySizeStatusBarEntry); + return vscode.window.registerCustomEditorProvider(VideoPreviewProvider.viewType, provider, { + supportsMultipleEditorsPerDocument: true, + webviewOptions: { + retainContextWhenHidden: true, + } + }); +} diff --git a/extensions/image-preview/tsconfig.json b/extensions/media-preview/tsconfig.json similarity index 79% rename from extensions/image-preview/tsconfig.json rename to extensions/media-preview/tsconfig.json index c5194e2e33..fcd79775de 100644 --- a/extensions/image-preview/tsconfig.json +++ b/extensions/media-preview/tsconfig.json @@ -1,8 +1,7 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "./out", - "types": [] + "outDir": "./out" }, "include": [ "src/**/*", diff --git a/extensions/media-preview/yarn.lock b/extensions/media-preview/yarn.lock new file mode 100644 index 0000000000..2fb59ea6fe --- /dev/null +++ b/extensions/media-preview/yarn.lock @@ -0,0 +1,348 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== + dependencies: + tslib "^2.2.0" + +"@azure/core-auth@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e" + integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/core-rest-pipeline@^1.10.0": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz#348290847ca31b9eecf9cf5de7519aaccdd30968" + integrity sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.0.0" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" + uuid "^8.3.0" + +"@azure/core-tracing@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" + integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw== + dependencies: + tslib "^2.2.0" + +"@azure/core-util@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.1.1.tgz#8f87b3dd468795df0f0849d9f096c3e7b29452c1" + integrity sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.3.tgz#6e36704aa51be7d4a1bae24731ea580836293c96" + integrity sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g== + dependencies: + tslib "^2.2.0" + +"@microsoft/1ds-core-js@3.2.8", "@microsoft/1ds-core-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.8.tgz#1b6b7d9bb858238c818ccf4e4b58ece7aeae5760" + integrity sha512-9o9SUAamJiTXIYwpkQDuueYt83uZfXp8zp8YFix1IwVPwC9RmE36T2CX9gXOeq1nDckOuOduYpA8qHvdh5BGfQ== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/1ds-post-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.8.tgz#46793842cca161bf7a2a5b6053c349f429e55110" + integrity sha512-SjlRoNcXcXBH6WQD/5SkkaCHIVqldH3gDu+bI7YagrOVJ5APxwT1Duw9gm3L1FjFa9S2i81fvJ3EVSKpp9wULA== + dependencies: + "@microsoft/1ds-core-js" "3.2.8" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-channel-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.9.tgz#840656f3c716de8b3eb0a98c122aa1b92bb8ebfb" + integrity sha512-fMBsAEB7pWtPn43y72q9Xy5E5y55r6gMuDQqRRccccVoQDPXyS57VCj5IdATblctru0C6A8XpL2vRyNmEsu0Vg== + dependencies: + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-common@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.9.tgz#a75e4a3143a7fd797687830c0ddd2069fd900827" + integrity sha512-mObn1moElyxZaGIRF/IU3cOaeKMgxghXnYEoHNUCA2e+rNwBIgxjyKkblFIpmGuHf4X7Oz3o3yBWpaC6AoMpig== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-core-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.9.tgz#0e5d207acfae6986a6fc97249eeb6117e523bf1b" + integrity sha512-HRuIuZ6aOWezcg/G5VyFDDWGL8hDNe/ljPP01J7ImH2kRPEgbtcfPSUMjkamGMefgdq81GZsSoC/NNGTP4pp2w== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085" + integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg== + +"@microsoft/applicationinsights-web-basic@^2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.9.tgz#eed2f3d1e19069962ed2155915c1656e6936e1d5" + integrity sha512-CH0J8JFOy7MjK8JO4pXXU+EML+Ilix+94PMZTX5EJlBU1in+mrik74/8qSg3UC4ekPi12KwrXaHCQSVC3WseXQ== + dependencies: + "@microsoft/applicationinsights-channel-js" "2.8.9" + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-web-snippet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz#6bb788b2902e48bf5d460c38c6bb7fedd686ddd7" + integrity sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ== + +"@microsoft/dynamicproto-js@^1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz#ede48dd3f85af14ee369c805e5ed5b84222b9fe2" + integrity sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA== + +"@opentelemetry/api@^1.0.4": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.2.0.tgz#89ef99401cde6208cff98760b67663726ef26686" + integrity sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g== + +"@opentelemetry/core@1.7.0", "@opentelemetry/core@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.7.0.tgz#83bdd1b7a4ceafcdffd6590420657caec5f7b34c" + integrity sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/resources@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.7.0.tgz#90ccd3a6a86b4dfba4e833e73944bd64958d78c5" + integrity sha512-u1M0yZotkjyKx8dj+46Sg5thwtOTBmtRieNXqdCRiWUp6SfFiIP0bI+1XK3LhuXqXkBXA1awJZaTqKduNMStRg== + dependencies: + "@opentelemetry/core" "1.7.0" + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/sdk-trace-base@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.7.0.tgz#b498424e0c6340a9d80de63fd408c5c2130a60a5" + integrity sha512-Iz84C+FVOskmauh9FNnj4+VrA+hG5o+tkMzXuoesvSfunVSioXib0syVFeNXwOm4+M5GdWCuW632LVjqEXStIg== + dependencies: + "@opentelemetry/core" "1.7.0" + "@opentelemetry/resources" "1.7.0" + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/semantic-conventions@1.7.0", "@opentelemetry/semantic-conventions@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.7.0.tgz#af80a1ef7cf110ea3a68242acd95648991bcd763" + integrity sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA== + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@vscode/extension-telemetry@0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.7.5.tgz#bf965731816e08c3f146f96d901ec67954fc913b" + integrity sha512-fJ5y3TcpqqkFYHneabYaoB4XAhDdVflVm+TDKshw9VOs77jkgNS4UA7LNXrWeO0eDne3Sh3JgURf+xzc1rk69w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.8" + "@microsoft/1ds-post-js" "^3.2.8" + "@microsoft/applicationinsights-web-basic" "^2.8.9" + applicationinsights "2.4.1" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +applicationinsights@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-2.4.1.tgz#4de4c4dd3c7c4a44445cfbf3d15808fc0dcc423d" + integrity sha512-0n0Ikd0gzSm460xm+M0UTWIwXrhrH/0bqfZatcJjYObWyefxfAxapGEyNnSGd1Tg90neHz+Yhf+Ff/zgvPiQYA== + dependencies: + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.10.0" + "@microsoft/applicationinsights-web-snippet" "^1.0.1" + "@opentelemetry/api" "^1.0.4" + "@opentelemetry/core" "^1.0.1" + "@opentelemetry/sdk-trace-base" "^1.0.1" + "@opentelemetry/semantic-conventions" "^1.0.1" + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "1.1.0" + diagnostic-channel-publishers "1.0.5" + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +diagnostic-channel-publishers@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz#df8c317086c50f5727fdfb5d2fce214d2e4130ae" + integrity sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg== + +diagnostic-channel@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz#6985e9dfedfbc072d91dc4388477e4087147756e" + integrity sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ== + dependencies: + semver "^5.3.0" + +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +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== + +semver@^5.3.0, semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== + +tslib@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +vscode-uri@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.6.tgz#5e6e2e1a4170543af30151b561a41f71db1d6f91" + integrity sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ== diff --git a/extensions/merge-conflict/package.json b/extensions/merge-conflict/package.json index efc984ed5b..3c6c41dab7 100644 --- a/extensions/merge-conflict/package.json +++ b/extensions/merge-conflict/package.json @@ -34,43 +34,50 @@ "category": "%command.category%", "title": "%command.accept.all-current%", "original": "Accept All Current", - "command": "merge-conflict.accept.all-current" + "command": "merge-conflict.accept.all-current", + "enablement": "!isMergeEditor" }, { "category": "%command.category%", "title": "%command.accept.all-incoming%", "original": "Accept All Incoming", - "command": "merge-conflict.accept.all-incoming" + "command": "merge-conflict.accept.all-incoming", + "enablement": "!isMergeEditor" }, { "category": "%command.category%", "title": "%command.accept.all-both%", "original": "Accept All Both", - "command": "merge-conflict.accept.all-both" + "command": "merge-conflict.accept.all-both", + "enablement": "!isMergeEditor" }, { "category": "%command.category%", "title": "%command.accept.current%", "original": "Accept Current", - "command": "merge-conflict.accept.current" + "command": "merge-conflict.accept.current", + "enablement": "!isMergeEditor" }, { "category": "%command.category%", "title": "%command.accept.incoming%", "original": "Accept Incoming", - "command": "merge-conflict.accept.incoming" + "command": "merge-conflict.accept.incoming", + "enablement": "!isMergeEditor" }, { "category": "%command.category%", "title": "%command.accept.selection%", "original": "Accept Selection", - "command": "merge-conflict.accept.selection" + "command": "merge-conflict.accept.selection", + "enablement": "!isMergeEditor" }, { "category": "%command.category%", "title": "%command.accept.both%", "original": "Accept Both", - "command": "merge-conflict.accept.both" + "command": "merge-conflict.accept.both", + "enablement": "!isMergeEditor" }, { "category": "%command.category%", @@ -92,7 +99,8 @@ "category": "%command.category%", "title": "%command.compare%", "original": "Compare Current Conflict", - "command": "merge-conflict.compare" + "command": "merge-conflict.compare", + "enablement": "!isMergeEditor" } ], "menus": { @@ -158,7 +166,7 @@ } }, "dependencies": { - "vscode-nls": "^5.0.0" + "@vscode/extension-telemetry": "0.7.5" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/merge-conflict/src/codelensProvider.ts b/extensions/merge-conflict/src/codelensProvider.ts index 7e99edcb9c..98347000e5 100644 --- a/extensions/merge-conflict/src/codelensProvider.ts +++ b/extensions/merge-conflict/src/codelensProvider.ts @@ -5,8 +5,6 @@ import * as vscode from 'vscode'; import * as interfaces from './interfaces'; -import { loadMessageBundle } from 'vscode-nls'; -const localize = loadMessageBundle(); export default class MergeConflictCodeLensProvider implements vscode.CodeLensProvider, vscode.Disposable { private codeLensRegistrationHandle?: vscode.Disposable | null; @@ -65,33 +63,34 @@ export default class MergeConflictCodeLensProvider implements vscode.CodeLensPro conflicts.forEach(conflict => { const acceptCurrentCommand: vscode.Command = { command: 'merge-conflict.accept.current', - title: localize('acceptCurrentChange', 'Accept Current Change'), + title: vscode.l10n.t("Accept Current Change"), arguments: ['known-conflict', conflict] }; const acceptIncomingCommand: vscode.Command = { command: 'merge-conflict.accept.incoming', - title: localize('acceptIncomingChange', 'Accept Incoming Change'), + title: vscode.l10n.t("Accept Incoming Change"), arguments: ['known-conflict', conflict] }; const acceptBothCommand: vscode.Command = { command: 'merge-conflict.accept.both', - title: localize('acceptBothChanges', 'Accept Both Changes'), + title: vscode.l10n.t("Accept Both Changes"), arguments: ['known-conflict', conflict] }; const diffCommand: vscode.Command = { command: 'merge-conflict.compare', - title: localize('compareChanges', 'Compare Changes'), + title: vscode.l10n.t("Compare Changes"), arguments: [conflict] }; + const range = document.lineAt(conflict.range.start.line).range; items.push( - new vscode.CodeLens(conflict.range, acceptCurrentCommand), - new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 1 })), acceptIncomingCommand), - new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 2 })), acceptBothCommand), - new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 3 })), diffCommand) + new vscode.CodeLens(range, acceptCurrentCommand), + new vscode.CodeLens(range, acceptIncomingCommand), + new vscode.CodeLens(range, acceptBothCommand), + new vscode.CodeLens(range, diffCommand) ); }); diff --git a/extensions/merge-conflict/src/commandHandler.ts b/extensions/merge-conflict/src/commandHandler.ts index 10bead294f..055ee3d550 100644 --- a/extensions/merge-conflict/src/commandHandler.ts +++ b/extensions/merge-conflict/src/commandHandler.ts @@ -5,8 +5,6 @@ import * as vscode from 'vscode'; import * as interfaces from './interfaces'; import ContentProvider from './contentProvider'; -import { loadMessageBundle } from 'vscode-nls'; -const localize = loadMessageBundle(); interface IDocumentMergeConflictNavigationResults { canNavigate: boolean; @@ -92,7 +90,7 @@ export default class CommandHandler implements vscode.Disposable { // Still failed to find conflict, warn the user and exit if (!conflict) { - vscode.window.showWarningMessage(localize('cursorNotInConflict', 'Editor cursor is not within a merge conflict')); + vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict")); return; } } @@ -101,7 +99,7 @@ export default class CommandHandler implements vscode.Disposable { // Still failed to find conflict, warn the user and exit if (!conflicts) { - vscode.window.showWarningMessage(localize('cursorNotInConflict', 'Editor cursor is not within a merge conflict')); + vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict")); return; } @@ -134,7 +132,7 @@ export default class CommandHandler implements vscode.Disposable { const docPath = editor.document.uri.path; const fileName = docPath.substring(docPath.lastIndexOf('/') + 1); // avoid NodeJS path to keep browser webpack small - const title = localize('compareChangesTitle', '{0}: Current Changes ↔ Incoming Changes', fileName); + const title = vscode.l10n.t("{0}: Current Changes ↔ Incoming Changes", fileName); const mergeConflictConfig = vscode.workspace.getConfiguration('merge-conflict'); const openToTheSide = mergeConflictConfig.get('diffViewPosition'); const opts: vscode.TextDocumentShowOptions = { @@ -161,7 +159,7 @@ export default class CommandHandler implements vscode.Disposable { const conflict = await this.findConflictContainingSelection(editor); if (!conflict) { - vscode.window.showWarningMessage(localize('cursorNotInConflict', 'Editor cursor is not within a merge conflict')); + vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict")); return; } @@ -184,11 +182,11 @@ export default class CommandHandler implements vscode.Disposable { typeToAccept = interfaces.CommitType.Incoming; } else if (editor.selection.active.isBefore(conflict.splitter.start)) { - vscode.window.showWarningMessage(localize('cursorOnCommonAncestorsRange', 'Editor cursor is within the common ancestors block, please move it to either the "current" or "incoming" block')); + vscode.window.showWarningMessage(vscode.l10n.t('Editor cursor is within the common ancestors block, please move it to either the "current" or "incoming" block')); return; } else { - vscode.window.showWarningMessage(localize('cursorOnSplitterRange', 'Editor cursor is within the merge conflict splitter, please move it to either the "current" or "incoming" block')); + vscode.window.showWarningMessage(vscode.l10n.t('Editor cursor is within the merge conflict splitter, please move it to either the "current" or "incoming" block')); return; } @@ -210,11 +208,11 @@ export default class CommandHandler implements vscode.Disposable { if (mergeConflictConfig.get('autoNavigateNextConflict.enabled')) { return; } - vscode.window.showWarningMessage(localize('noConflicts', 'No merge conflicts found in this file')); + vscode.window.showWarningMessage(vscode.l10n.t("No merge conflicts found in this file")); return; } else if (!navigationResult.canNavigate) { - vscode.window.showWarningMessage(localize('noOtherConflictsInThisFile', 'No other merge conflicts within this file')); + vscode.window.showWarningMessage(vscode.l10n.t("No other merge conflicts within this file")); return; } else if (!navigationResult.conflict) { @@ -241,7 +239,7 @@ export default class CommandHandler implements vscode.Disposable { } if (!conflict) { - vscode.window.showWarningMessage(localize('cursorNotInConflict', 'Editor cursor is not within a merge conflict')); + vscode.window.showWarningMessage(vscode.l10n.t("Editor cursor is not within a merge conflict")); return; } @@ -261,7 +259,7 @@ export default class CommandHandler implements vscode.Disposable { const conflicts = await this.tracker.getConflicts(editor.document); if (!conflicts || conflicts.length === 0) { - vscode.window.showWarningMessage(localize('noConflicts', 'No merge conflicts found in this file')); + vscode.window.showWarningMessage(vscode.l10n.t("No merge conflicts found in this file")); return; } diff --git a/extensions/merge-conflict/src/documentMergeConflict.ts b/extensions/merge-conflict/src/documentMergeConflict.ts index d6ba25a743..bd1905c996 100644 --- a/extensions/merge-conflict/src/documentMergeConflict.ts +++ b/extensions/merge-conflict/src/documentMergeConflict.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as interfaces from './interfaces'; import * as vscode from 'vscode'; +import type TelemetryReporter from '@vscode/extension-telemetry'; export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict { @@ -12,8 +13,9 @@ export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict public incoming: interfaces.IMergeRegion; public commonAncestors: interfaces.IMergeRegion[]; public splitter: vscode.Range; + private applied = false; - constructor(descriptor: interfaces.IDocumentMergeConflictDescriptor) { + constructor(descriptor: interfaces.IDocumentMergeConflictDescriptor, private readonly telemetryReporter: TelemetryReporter) { this.range = descriptor.range; this.current = descriptor.current; this.incoming = descriptor.incoming; @@ -22,6 +24,25 @@ export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict } public commitEdit(type: interfaces.CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit): Thenable { + function commitTypeToString(type: interfaces.CommitType): string { + switch (type) { + case interfaces.CommitType.Current: + return 'current'; + case interfaces.CommitType.Incoming: + return 'incoming'; + case interfaces.CommitType.Both: + return 'both'; + } + } + + /* __GDPR__ + "mergeMarkers.accept" : { + "owner": "hediet", + "comment": "Used to understand how the inline merge editor experience is used.", + "resolution": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Indicates how the merge conflict was resolved by the user" } + } + */ + this.telemetryReporter.sendTelemetryEvent('mergeMarkers.accept', { resolution: commitTypeToString(type) }); if (edit) { @@ -33,6 +54,10 @@ export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict } public applyEdit(type: interfaces.CommitType, document: vscode.TextDocument, edit: { replace(range: vscode.Range, newText: string): void }): void { + if (this.applied) { + return; + } + this.applied = true; // Each conflict is a set of ranges as follows, note placements or newlines // which may not in spans diff --git a/extensions/merge-conflict/src/documentTracker.ts b/extensions/merge-conflict/src/documentTracker.ts index ac3f4ab9a1..a70bd2f9e8 100644 --- a/extensions/merge-conflict/src/documentTracker.ts +++ b/extensions/merge-conflict/src/documentTracker.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { MergeConflictParser } from './mergeConflictParser'; import * as interfaces from './interfaces'; import { Delayer } from './delayer'; +import TelemetryReporter from '@vscode/extension-telemetry'; class ScanTask { public origins: Set = new Set(); @@ -47,6 +48,8 @@ export default class DocumentMergeConflictTracker implements vscode.Disposable, private cache: Map = new Map(); private delayExpireTime: number = 0; + constructor(private readonly telemetryReporter: TelemetryReporter) { } + getConflicts(document: vscode.TextDocument, origin: string): PromiseLike { // Attempt from cache @@ -109,6 +112,8 @@ export default class DocumentMergeConflictTracker implements vscode.Disposable, this.cache.clear(); } + private readonly seenDocumentsWithConflicts = new Set(); + private getConflictsOrEmpty(document: vscode.TextDocument, _origins: string[]): interfaces.IDocumentMergeConflict[] { const containsConflict = MergeConflictParser.containsConflict(document); @@ -116,7 +121,26 @@ export default class DocumentMergeConflictTracker implements vscode.Disposable, return []; } - const conflicts = MergeConflictParser.scanDocument(document); + const conflicts = MergeConflictParser.scanDocument(document, this.telemetryReporter); + + const key = document.uri.toString(); + // Don't report telemetry for the same document twice. This is an approximation, but good enough. + // Otherwise redo/undo could trigger this event multiple times. + if (!this.seenDocumentsWithConflicts.has(key)) { + this.seenDocumentsWithConflicts.add(key); + + /* __GDPR__ + "mergeMarkers.documentWithConflictMarkersOpened" : { + "owner": "hediet", + "comment": "Used to determine how many documents with conflicts are opened.", + "conflictCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of conflict counts" } + } + */ + this.telemetryReporter.sendTelemetryEvent('mergeMarkers.documentWithConflictMarkersOpened', {}, { + conflictCount: conflicts.length, + }); + } + return conflicts; } diff --git a/extensions/merge-conflict/src/mergeConflictParser.ts b/extensions/merge-conflict/src/mergeConflictParser.ts index 26794ad49e..3c2ad7c744 100644 --- a/extensions/merge-conflict/src/mergeConflictParser.ts +++ b/extensions/merge-conflict/src/mergeConflictParser.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import * as interfaces from './interfaces'; import { DocumentMergeConflict } from './documentMergeConflict'; +import TelemetryReporter from '@vscode/extension-telemetry'; const startHeaderMarker = '<<<<<<<'; const commonAncestorsMarker = '|||||||'; @@ -20,7 +21,7 @@ interface IScanMergedConflict { export class MergeConflictParser { - static scanDocument(document: vscode.TextDocument): interfaces.IDocumentMergeConflict[] { + static scanDocument(document: vscode.TextDocument, telemetryReporter: TelemetryReporter): interfaces.IDocumentMergeConflict[] { // Scan each line in the document, we already know there is at least a <<<<<<< and // >>>>>> marker within the document, we need to group these into conflict ranges. @@ -81,7 +82,7 @@ export class MergeConflictParser { return conflictDescriptors .filter(Boolean) - .map(descriptor => new DocumentMergeConflict(descriptor)); + .map(descriptor => new DocumentMergeConflict(descriptor, telemetryReporter)); } private static scanItemTolMergeConflictDescriptor(document: vscode.TextDocument, scanned: IScanMergedConflict): interfaces.IDocumentMergeConflictDescriptor | null { diff --git a/extensions/merge-conflict/src/mergeDecorator.ts b/extensions/merge-conflict/src/mergeDecorator.ts index a56af985e2..0cd0816e1a 100644 --- a/extensions/merge-conflict/src/mergeDecorator.ts +++ b/extensions/merge-conflict/src/mergeDecorator.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import * as interfaces from './interfaces'; -import { loadMessageBundle } from 'vscode-nls'; -const localize = loadMessageBundle(); + export default class MergeDecorator implements vscode.Disposable { @@ -88,7 +87,7 @@ export default class MergeDecorator implements vscode.Disposable { outlineWidth: '1pt', outlineColor: new vscode.ThemeColor('merge.border'), after: { - contentText: ' ' + localize('currentChange', '(Current Change)'), + contentText: ' ' + vscode.l10n.t("(Current Change)"), color: new vscode.ThemeColor('descriptionForeground') } }); @@ -118,7 +117,7 @@ export default class MergeDecorator implements vscode.Disposable { outlineColor: new vscode.ThemeColor('merge.border'), isWholeLine: this.decorationUsesWholeLine, after: { - contentText: ' ' + localize('incomingChange', '(Incoming Change)'), + contentText: ' ' + vscode.l10n.t("(Incoming Change)"), color: new vscode.ThemeColor('descriptionForeground') } }); diff --git a/extensions/merge-conflict/src/services.ts b/extensions/merge-conflict/src/services.ts index 64de4ecd6b..c6e22bc4d7 100644 --- a/extensions/merge-conflict/src/services.ts +++ b/extensions/merge-conflict/src/services.ts @@ -9,20 +9,25 @@ import CommandHandler from './commandHandler'; import ContentProvider from './contentProvider'; import Decorator from './mergeDecorator'; import * as interfaces from './interfaces'; +import TelemetryReporter from '@vscode/extension-telemetry'; const ConfigurationSectionName = 'merge-conflict'; export default class ServiceWrapper implements vscode.Disposable { private services: vscode.Disposable[] = []; + private telemetryReporter: TelemetryReporter; constructor(private context: vscode.ExtensionContext) { + const { aiKey } = context.extension.packageJSON as { aiKey: string }; + this.telemetryReporter = new TelemetryReporter(aiKey); + context.subscriptions.push(this.telemetryReporter); } begin() { const configuration = this.createExtensionConfiguration(); - const documentTracker = new DocumentTracker(); + const documentTracker = new DocumentTracker(this.telemetryReporter); this.services.push( documentTracker, @@ -48,19 +53,6 @@ export default class ServiceWrapper implements vscode.Disposable { } createExtensionConfiguration(): interfaces.IExtensionConfiguration { - - // PRAGMATIC way to avoid conflicting with the new merge editor: when git opts into - // using the merge editor we disable this extension - for the merge editor but also - // for "other" editors - const gitConfig = vscode.workspace.getConfiguration('git'); - if (gitConfig.get('mergeEditor')) { - return { - enableCodeLens: false, - enableDecorations: false, - enableEditorOverview: false - }; - } - const workspaceConfiguration = vscode.workspace.getConfiguration(ConfigurationSectionName); const codeLensEnabled: boolean = workspaceConfiguration.get('codeLens.enabled', true); const decoratorsEnabled: boolean = workspaceConfiguration.get('decorators.enabled', true); diff --git a/extensions/merge-conflict/yarn.lock b/extensions/merge-conflict/yarn.lock index 699f1238a9..8625415dde 100644 --- a/extensions/merge-conflict/yarn.lock +++ b/extensions/merge-conflict/yarn.lock @@ -2,12 +2,347 @@ # yarn lockfile v1 +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== + dependencies: + tslib "^2.2.0" + +"@azure/core-auth@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e" + integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/core-rest-pipeline@^1.10.0": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz#348290847ca31b9eecf9cf5de7519aaccdd30968" + integrity sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.0.0" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" + uuid "^8.3.0" + +"@azure/core-tracing@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" + integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw== + dependencies: + tslib "^2.2.0" + +"@azure/core-util@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.1.1.tgz#8f87b3dd468795df0f0849d9f096c3e7b29452c1" + integrity sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.3.tgz#6e36704aa51be7d4a1bae24731ea580836293c96" + integrity sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g== + dependencies: + tslib "^2.2.0" + +"@microsoft/1ds-core-js@3.2.8", "@microsoft/1ds-core-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.8.tgz#1b6b7d9bb858238c818ccf4e4b58ece7aeae5760" + integrity sha512-9o9SUAamJiTXIYwpkQDuueYt83uZfXp8zp8YFix1IwVPwC9RmE36T2CX9gXOeq1nDckOuOduYpA8qHvdh5BGfQ== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/1ds-post-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.8.tgz#46793842cca161bf7a2a5b6053c349f429e55110" + integrity sha512-SjlRoNcXcXBH6WQD/5SkkaCHIVqldH3gDu+bI7YagrOVJ5APxwT1Duw9gm3L1FjFa9S2i81fvJ3EVSKpp9wULA== + dependencies: + "@microsoft/1ds-core-js" "3.2.8" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-channel-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.9.tgz#840656f3c716de8b3eb0a98c122aa1b92bb8ebfb" + integrity sha512-fMBsAEB7pWtPn43y72q9Xy5E5y55r6gMuDQqRRccccVoQDPXyS57VCj5IdATblctru0C6A8XpL2vRyNmEsu0Vg== + dependencies: + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-common@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.9.tgz#a75e4a3143a7fd797687830c0ddd2069fd900827" + integrity sha512-mObn1moElyxZaGIRF/IU3cOaeKMgxghXnYEoHNUCA2e+rNwBIgxjyKkblFIpmGuHf4X7Oz3o3yBWpaC6AoMpig== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-core-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.9.tgz#0e5d207acfae6986a6fc97249eeb6117e523bf1b" + integrity sha512-HRuIuZ6aOWezcg/G5VyFDDWGL8hDNe/ljPP01J7ImH2kRPEgbtcfPSUMjkamGMefgdq81GZsSoC/NNGTP4pp2w== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085" + integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg== + +"@microsoft/applicationinsights-web-basic@^2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.9.tgz#eed2f3d1e19069962ed2155915c1656e6936e1d5" + integrity sha512-CH0J8JFOy7MjK8JO4pXXU+EML+Ilix+94PMZTX5EJlBU1in+mrik74/8qSg3UC4ekPi12KwrXaHCQSVC3WseXQ== + dependencies: + "@microsoft/applicationinsights-channel-js" "2.8.9" + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-web-snippet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz#6bb788b2902e48bf5d460c38c6bb7fedd686ddd7" + integrity sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ== + +"@microsoft/dynamicproto-js@^1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz#ede48dd3f85af14ee369c805e5ed5b84222b9fe2" + integrity sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA== + +"@opentelemetry/api@^1.0.4": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.3.0.tgz#27c6f776ac3c1c616651e506a89f438a0ed6a055" + integrity sha512-YveTnGNsFFixTKJz09Oi4zYkiLT5af3WpZDu4aIUM7xX+2bHAkOJayFTVQd6zB8kkWPpbua4Ha6Ql00grdLlJQ== + +"@opentelemetry/core@1.8.0", "@opentelemetry/core@^1.0.1": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.8.0.tgz#cca18594dd48ded6dc0d08c7e789c79af0315934" + integrity sha512-6SDjwBML4Am0AQmy7z1j6HGrWDgeK8awBRUvl1PGw6HayViMk4QpnUXvv4HTHisecgVBy43NE/cstWprm8tIfw== + dependencies: + "@opentelemetry/semantic-conventions" "1.8.0" + +"@opentelemetry/resources@1.8.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.8.0.tgz#260be9742cf7bceccc0db928d8ca8d64391acfe3" + integrity sha512-KSyMH6Jvss/PFDy16z5qkCK0ERlpyqixb1xwb73wLMvVq+j7i89lobDjw3JkpCcd1Ws0J6jAI4fw28Zufj2ssg== + dependencies: + "@opentelemetry/core" "1.8.0" + "@opentelemetry/semantic-conventions" "1.8.0" + +"@opentelemetry/sdk-trace-base@^1.0.1": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.8.0.tgz#70713aab90978a16dea188c8335209f857be7384" + integrity sha512-iH41m0UTddnCKJzZx3M85vlhKzRcmT48pUeBbnzsGrq4nIay1oWVHKM5nhB5r8qRDGvd/n7f/YLCXClxwM0tvA== + dependencies: + "@opentelemetry/core" "1.8.0" + "@opentelemetry/resources" "1.8.0" + "@opentelemetry/semantic-conventions" "1.8.0" + +"@opentelemetry/semantic-conventions@1.8.0", "@opentelemetry/semantic-conventions@^1.0.1": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.8.0.tgz#fe2aa90e6df050a11cd57f5c0f47b0641fd2cad3" + integrity sha512-TYh1MRcm4JnvpqtqOwT9WYaBYY4KERHdToxs/suDTLviGRsQkIjS5yYROTYTSJQUnYLOn/TuOh5GoMwfLSU+Ew== + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + "@types/node@16.x": version "16.11.6" resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-nls@^5.0.0: +"@vscode/extension-telemetry@0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.7.5.tgz#bf965731816e08c3f146f96d901ec67954fc913b" + integrity sha512-fJ5y3TcpqqkFYHneabYaoB4XAhDdVflVm+TDKshw9VOs77jkgNS4UA7LNXrWeO0eDne3Sh3JgURf+xzc1rk69w== + dependencies: + "@microsoft/1ds-core-js" "^3.2.8" + "@microsoft/1ds-post-js" "^3.2.8" + "@microsoft/applicationinsights-web-basic" "^2.8.9" + applicationinsights "2.4.1" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +applicationinsights@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-2.4.1.tgz#4de4c4dd3c7c4a44445cfbf3d15808fc0dcc423d" + integrity sha512-0n0Ikd0gzSm460xm+M0UTWIwXrhrH/0bqfZatcJjYObWyefxfAxapGEyNnSGd1Tg90neHz+Yhf+Ff/zgvPiQYA== + dependencies: + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.10.0" + "@microsoft/applicationinsights-web-snippet" "^1.0.1" + "@opentelemetry/api" "^1.0.4" + "@opentelemetry/core" "^1.0.1" + "@opentelemetry/sdk-trace-base" "^1.0.1" + "@opentelemetry/semantic-conventions" "^1.0.1" + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "1.1.0" + diagnostic-channel-publishers "1.0.5" + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +diagnostic-channel-publishers@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz#df8c317086c50f5727fdfb5d2fce214d2e4130ae" + integrity sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg== + +diagnostic-channel@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz#6985e9dfedfbc072d91dc4388477e4087147756e" + integrity sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ== + dependencies: + semver "^5.3.0" + +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +http-proxy-agent@^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== + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +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== + +semver@^5.3.0, semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== + +tslib@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== diff --git a/extensions/microsoft-authentication/README.md b/extensions/microsoft-authentication/README.md index 8d914b0e39..2462c2b3bc 100644 --- a/extensions/microsoft-authentication/README.md +++ b/extensions/microsoft-authentication/README.md @@ -5,3 +5,5 @@ ## Features This extension provides support for authenticating to Microsoft. It registers the `microsoft` Authentication Provider that can be leveraged by other extensions. This also provides the Microsoft authentication used by Settings Sync. + +Additionally, it provides the `microsoft-sovereign-cloud` Authentication Provider that can be used to sign in to other Azure clouds like Azure for US Government or Azure China. Use the setting `microsoft-sovereign-cloud.endpoint` to select the authentication endpoint the provider should use. Please note that different scopes may also be required in different environments. diff --git a/extensions/microsoft-authentication/extension-browser.webpack.config.js b/extensions/microsoft-authentication/extension-browser.webpack.config.js index 96b4444763..801ea17191 100644 --- a/extensions/microsoft-authentication/extension-browser.webpack.config.js +++ b/extensions/microsoft-authentication/extension-browser.webpack.config.js @@ -21,12 +21,17 @@ module.exports = withBrowserDefaults({ extension: './src/extension.ts', }, externals: { - 'keytar': 'commonjs keytar' + 'keytar': 'commonjs keytar', }, resolve: { + fallback: { + 'querystring': require.resolve('querystring-es3') + }, alias: { - './env/node': path.resolve(__dirname, 'src/env/browser'), - './authServer': path.resolve(__dirname, 'src/env/browser/authServer'), + './node/crypto': path.resolve(__dirname, 'src/browser/crypto'), + './node/authServer': path.resolve(__dirname, 'src/browser/authServer'), + './node/buffer': path.resolve(__dirname, 'src/browser/buffer'), + './node/fetch': path.resolve(__dirname, 'src/browser/fetch'), } } }); diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index f8b8a6931d..702b5cbfd1 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -12,9 +12,7 @@ "categories": [ "Other" ], - "activationEvents": [ - "onAuthenticationRequest:microsoft" - ], + "activationEvents": [], "enabledApiProposals": [ "idToken" ], @@ -33,6 +31,32 @@ { "label": "Microsoft", "id": "microsoft" + }, + { + "label": "Microsoft Sovereign Cloud", + "id": "microsoft-sovereign-cloud" + } + ], + "configuration": [ + { + "title": "Microsoft Sovereign Cloud", + "properties": { + "microsoft-sovereign-cloud.endpoint": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string", + "enum": [ + "Azure China", + "Azure US Government" + ] + } + ], + "description": "%microsoft-sovereign-cloud.endpoint.description%" + } + } } ] }, @@ -51,17 +75,12 @@ "@types/node-fetch": "^2.5.7", "@types/randombytes": "^2.0.0", "@types/sha.js": "^2.4.0", - "@types/uuid": "8.0.0" + "@types/uuid": "8.0.0", + "querystring-es3": "^0.2.1" }, "dependencies": { - "buffer": "^5.6.0", "node-fetch": "2.6.7", - "randombytes": "~2.1.0", - "sha.js": "2.4.11", - "stream": "0.0.2", - "uuid": "^8.2.0", - "@vscode/extension-telemetry": "0.6.2", - "vscode-nls": "^5.0.0" + "@vscode/extension-telemetry": "0.7.5" }, "repository": { "type": "git", diff --git a/extensions/microsoft-authentication/package.nls.json b/extensions/microsoft-authentication/package.nls.json index c0bb4c4a6a..12f51bb016 100644 --- a/extensions/microsoft-authentication/package.nls.json +++ b/extensions/microsoft-authentication/package.nls.json @@ -2,5 +2,6 @@ "displayName": "Microsoft Account", "description": "Microsoft authentication provider", "signIn": "Sign In", - "signOut": "Sign Out" + "signOut": "Sign Out", + "microsoft-sovereign-cloud.endpoint.description": "Login endpoint for Azure authentication. Select a national cloud or enter the login URL for a custom Azure cloud." } diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts index 6ae27b3a2b..0fb12615b1 100644 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ b/extensions/microsoft-authentication/src/AADHelper.ts @@ -3,26 +3,30 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as randomBytes from 'randombytes'; -import * as querystring from 'querystring'; -import { Buffer } from 'buffer'; import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; -import { v4 as uuid } from 'uuid'; -import fetch, { Response } from 'node-fetch'; -import Logger from './logger'; -import { isSupportedEnvironment, toBase64UrlEncoding } from './utils'; -import { sha256 } from './env/node/sha256'; +import * as querystring from 'querystring'; +import * as path from 'path'; +import { isSupportedEnvironment } from './utils'; +import { generateCodeChallenge, generateCodeVerifier, randomUUID } from './cryptoUtils'; import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage'; -import { LoopbackAuthServer } from './authServer'; -import path = require('path'); - -const localize = nls.loadMessageBundle(); +import { LoopbackAuthServer } from './node/authServer'; +import { base64Decode } from './node/buffer'; +import { fetching } from './node/fetch'; +import { UriEventHandler } from './UriEventHandler'; +import TelemetryReporter from '@vscode/extension-telemetry'; const redirectUrl = 'https://vscode-redirect.azurewebsites.net/'; -const loginEndpointUrl = 'https://login.microsoftonline.com/'; +const defaultLoginEndpointUrl = 'https://login.microsoftonline.com/'; const DEFAULT_CLIENT_ID = 'aebc6443-996d-45c2-90f0-388ff96faa56'; const DEFAULT_TENANT = 'organizations'; +const MSA_TID = '9188040d-6c67-4c5b-b112-36a304b66dad'; +const MSA_PASSTHRU_TID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a'; + +const enum MicrosoftAccountType { + AAD = 'aad', + MSA = 'msa', + Unknown = 'unknown' +} interface IToken { accessToken?: string; // When unable to refresh due to network problems, the access token becomes undefined @@ -35,20 +39,21 @@ interface IToken { account: { label: string; id: string; + type: MicrosoftAccountType; }; scope: string; sessionId: string; // The account id + the scope } -interface IStoredSession { +export interface IStoredSession { id: string; refreshToken: string; scope: string; // Scopes are alphabetized and joined with a space account: { - label?: string; - displayName?: string; + label: string; id: string; }; + endpoint: string | undefined; } export interface ITokenResponse { @@ -67,6 +72,7 @@ export interface IMicrosoftTokens { } interface IScopeData { + originalScopes?: string[]; scopes: string[]; scopeStr: string; scopesToSend: string; @@ -74,16 +80,8 @@ interface IScopeData { tenant: string; } -export const onDidChangeSessions = new vscode.EventEmitter(); - export const REFRESH_NETWORK_FAILURE = 'Network failure'; -class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { - public handleUri(uri: vscode.Uri) { - this.fire(uri); - } -} - export class AzureActiveDirectoryService { // For details on why this is set to 2/3... see https://github.com/microsoft/vscode/issues/133201#issuecomment-966668197 private static REFRESH_TIMEOUT_MODIFIER = 1000 * 2 / 3; @@ -91,29 +89,31 @@ export class AzureActiveDirectoryService { private _tokens: IToken[] = []; private _refreshTimeouts: Map = new Map(); private _refreshingPromise: Promise | undefined; - private _uriHandler: UriEventHandler; + private _sessionChangeEmitter: vscode.EventEmitter = new vscode.EventEmitter(); // Used to keep track of current requests when not using the local server approach. private _pendingNonces = new Map(); private _codeExchangePromises = new Map>(); private _codeVerfifiers = new Map(); - private readonly _tokenStorage: BetterTokenStorage; - - constructor(private _context: vscode.ExtensionContext) { - this._tokenStorage = new BetterTokenStorage('microsoft.login.keylist', _context); - this._uriHandler = new UriEventHandler(); - _context.subscriptions.push(vscode.window.registerUriHandler(this._uriHandler)); + constructor( + private readonly _logger: vscode.LogOutputChannel, + _context: vscode.ExtensionContext, + private readonly _uriHandler: UriEventHandler, + private readonly _tokenStorage: BetterTokenStorage, + private readonly _telemetryReporter: TelemetryReporter, + private readonly _loginEndpointUrl: string = defaultLoginEndpointUrl + ) { _context.subscriptions.push(this._tokenStorage.onDidChangeInOtherWindow((e) => this.checkForUpdates(e))); } public async initialize(): Promise { - Logger.info('Reading sessions from secret storage...'); - const sessions = await this._tokenStorage.getAll(); - Logger.info(`Got ${sessions.length} stored sessions`); + this._logger.info('Reading sessions from secret storage...'); + const sessions = await this._tokenStorage.getAll(item => this.sessionMatchesEndpoint(item)); + this._logger.info(`Got ${sessions.length} stored sessions`); const refreshes = sessions.map(async session => { - Logger.trace(`Read the following stored session with scopes: ${session.scope}`); + this._logger.trace(`Read the following stored session with scopes: ${session.scope}`); const scopes = session.scope.split(' '); const scopeData: IScopeData = { scopes, @@ -132,21 +132,21 @@ export class AzureActiveDirectoryService { accessToken: undefined, refreshToken: session.refreshToken, account: { - label: session.account.label ?? session.account.displayName!, - id: session.account.id + ...session.account, + type: MicrosoftAccountType.Unknown }, scope: session.scope, sessionId: session.id }); } else { - vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed.")); - Logger.error(e); + vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); + this._logger.error(e); await this.removeSessionByIToken({ accessToken: undefined, refreshToken: session.refreshToken, account: { - label: session.account.label ?? session.account.displayName!, - id: session.account.id + ...session.account, + type: MicrosoftAccountType.Unknown }, scope: session.scope, sessionId: session.id @@ -158,19 +158,40 @@ export class AzureActiveDirectoryService { const result = await Promise.allSettled(refreshes); for (const res of result) { if (res.status === 'rejected') { - Logger.error(`Failed to initialize stored data: ${res.reason}`); + this._logger.error(`Failed to initialize stored data: ${res.reason}`); this.clearSessions(); + break; } } + + for (const token of this._tokens) { + /* __GDPR__ + "login" : { + "owner": "TylerLeonhardt", + "comment": "Used to determine the usage of the Microsoft Auth Provider.", + "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }, + "accountType": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what account types are being used." } + } + */ + this._telemetryReporter.sendTelemetryEvent('account', { + // Get rid of guids from telemetry. + scopes: JSON.stringify(token.scope.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}').split(' ')), + accountType: token.account.type + }); + } } //#region session operations + public get onDidChangeSessions(): vscode.Event { + return this._sessionChangeEmitter.event; + } + async getSessions(scopes?: string[]): Promise { if (!scopes) { - Logger.info('Getting sessions for all scopes...'); + this._logger.info('Getting sessions for all scopes...'); const sessions = this._tokens.map(token => this.convertToSessionSync(token)); - Logger.info(`Got ${sessions.length} sessions for all scopes...`); + this._logger.info(`Got ${sessions.length} sessions for all scopes...`); return sessions; } @@ -184,13 +205,16 @@ export class AzureActiveDirectoryService { if (!modifiedScopes.includes('profile')) { modifiedScopes.push('profile'); } + if (!modifiedScopes.includes('offline_access')) { + modifiedScopes.push('offline_access'); + } modifiedScopes = modifiedScopes.sort(); let modifiedScopesStr = modifiedScopes.join(' '); - Logger.info(`Getting sessions for the following scopes: ${modifiedScopesStr}`); + this._logger.info(`Getting sessions for the following scopes: ${modifiedScopesStr}`); if (this._refreshingPromise) { - Logger.info('Refreshing in progress. Waiting for completion before continuing.'); + this._logger.info('Refreshing in progress. Waiting for completion before continuing.'); try { await this._refreshingPromise; } catch (e) { @@ -205,82 +229,92 @@ export class AzureActiveDirectoryService { // without an idtoken. if (!matchingTokens.length) { const fallbackOrderedScopes = scopes.sort().join(' '); - Logger.trace(`No session found with idtoken scopes... Using fallback scope list of: ${fallbackOrderedScopes}`); + this._logger.trace(`No session found with idtoken scopes... Using fallback scope list of: ${fallbackOrderedScopes}`); matchingTokens = this._tokens.filter(token => token.scope === fallbackOrderedScopes); if (matchingTokens.length) { modifiedScopesStr = fallbackOrderedScopes; } } + const clientId = this.getClientId(scopes); + const scopeData: IScopeData = { + clientId, + originalScopes: scopes, + scopes: modifiedScopes, + scopeStr: modifiedScopesStr, + // filter our special scopes + scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '), + tenant: this.getTenantId(scopes), + }; + // If we still don't have a matching token try to get a new token from an existing token by using // the refreshToken. This is documented here: // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token // "Refresh tokens are valid for all permissions that your client has already received consent for." if (!matchingTokens.length) { - const clientId = this.getClientId(modifiedScopes); // Get a token with the correct client id. const token = clientId === DEFAULT_CLIENT_ID ? this._tokens.find(t => t.refreshToken && !t.scope.includes('VSCODE_CLIENT_ID')) : this._tokens.find(t => t.refreshToken && t.scope.includes(`VSCODE_CLIENT_ID:${clientId}`)); if (token) { - const scopeData: IScopeData = { - clientId, - scopes: modifiedScopes, - scopeStr: modifiedScopesStr, - // filter our special scopes - scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '), - tenant: this.getTenantId(modifiedScopes), - }; - try { const itoken = await this.refreshToken(token.refreshToken, scopeData); matchingTokens.push(itoken); } catch (err) { - Logger.error(`Attempted to get a new session for scopes '${scopeData.scopeStr}' using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`); + this._logger.error(`Attempted to get a new session for scopes '${scopeData.scopeStr}' using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`); } } } - Logger.info(`Got ${matchingTokens.length} sessions for scopes: ${modifiedScopesStr}`); - return Promise.all(matchingTokens.map(token => this.convertToSession(token))); + this._logger.info(`Got ${matchingTokens.length} sessions for scopes: ${modifiedScopesStr}`); + return Promise.all(matchingTokens.map(token => this.convertToSession(token, scopeData))); } - public createSession(scopes: string[]): Promise { - if (!scopes.includes('openid')) { - scopes.push('openid'); + public async createSession(scopes: string[]): Promise { + let modifiedScopes = [...scopes]; + if (!modifiedScopes.includes('openid')) { + modifiedScopes.push('openid'); } - if (!scopes.includes('email')) { - scopes.push('email'); + if (!modifiedScopes.includes('email')) { + modifiedScopes.push('email'); } - if (!scopes.includes('profile')) { - scopes.push('profile'); + if (!modifiedScopes.includes('profile')) { + modifiedScopes.push('profile'); } - scopes = scopes.sort(); + if (!modifiedScopes.includes('offline_access')) { + modifiedScopes.push('offline_access'); + } + modifiedScopes = modifiedScopes.sort(); const scopeData: IScopeData = { - scopes, - scopeStr: scopes.join(' '), + originalScopes: scopes, + scopes: modifiedScopes, + scopeStr: modifiedScopes.join(' '), // filter our special scopes - scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '), + scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '), clientId: this.getClientId(scopes), tenant: this.getTenantId(scopes), }; - Logger.info(`Logging in for the following scopes: ${scopeData.scopeStr}`); - if (!scopeData.scopes.includes('offline_access')) { - Logger.info('Warning: The \'offline_access\' scope was not included, so the generated token will not be able to be refreshed.'); - } + this._logger.info(`Logging in for the following scopes: ${scopeData.scopeStr}`); const runsRemote = vscode.env.remoteName !== undefined; const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web; + + if (runsServerless && this._loginEndpointUrl !== defaultLoginEndpointUrl) { + throw new Error('Sign in to non-public clouds is not supported on the web.'); + } + if (runsRemote || runsServerless) { return this.createSessionWithoutLocalServer(scopeData); } try { - return this.createSessionWithLocalServer(scopeData); + const session = await this.createSessionWithLocalServer(scopeData); + this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); + return session; } catch (e) { - Logger.error(`Error creating session for scopes: ${scopeData.scopeStr} Error: ${e}`); + this._logger.error(`Error creating session for scopes: ${scopeData.scopeStr} Error: ${e}`); // If the error was about starting the server, try directly hitting the login endpoint instead if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') { @@ -292,8 +326,8 @@ export class AzureActiveDirectoryService { } private async createSessionWithLocalServer(scopeData: IScopeData) { - const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64')); - const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier)); + const codeVerifier = generateCodeVerifier(); + const codeChallenge = await generateCodeChallenge(codeVerifier); const qs = new URLSearchParams({ response_type: 'code', response_mode: 'query', @@ -304,7 +338,7 @@ export class AzureActiveDirectoryService { code_challenge_method: 'S256', code_challenge: codeChallenge, }).toString(); - const loginUrl = `${loginEndpointUrl}${scopeData.tenant}/oauth2/v2.0/authorize?${qs}`; + const loginUrl = `${this._loginEndpointUrl}${scopeData.tenant}/oauth2/v2.0/authorize?${qs}`; const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl); await server.start(); @@ -325,16 +359,16 @@ export class AzureActiveDirectoryService { private async createSessionWithoutLocalServer(scopeData: IScopeData): Promise { let callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`)); - const nonce = randomBytes(16).toString('base64'); + const nonce = generateCodeVerifier(); const callbackQuery = new URLSearchParams(callbackUri.query); callbackQuery.set('nonce', encodeURIComponent(nonce)); callbackUri = callbackUri.with({ query: callbackQuery.toString() }); const state = encodeURIComponent(callbackUri.toString(true)); - const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64')); - const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier)); - const signInUrl = `${loginEndpointUrl}${scopeData.tenant}/oauth2/v2.0/authorize`; + const codeVerifier = generateCodeVerifier(); + const codeChallenge = await generateCodeChallenge(codeVerifier); + const signInUrl = `${this._loginEndpointUrl}${scopeData.tenant}/oauth2/v2.0/authorize`; const oauthStartQuery = new URLSearchParams({ response_type: 'code', client_id: encodeURIComponent(scopeData.clientId), @@ -369,7 +403,7 @@ export class AzureActiveDirectoryService { existingPromise = this.handleCodeResponse(scopeData); } else { inputBox = vscode.window.createInputBox(); - existingPromise = Promise.race([this.handleCodeInputBox(inputBox, codeVerifier, scopeData), this.handleCodeResponse(scopeData)]); + existingPromise = this.handleCodeInputBox(inputBox, codeVerifier, scopeData); } this._codeExchangePromises.set(scopeData.scopeStr, existingPromise); } @@ -384,22 +418,28 @@ export class AzureActiveDirectoryService { }); } - public removeSessionById(sessionId: string, writeToDisk: boolean = true): Promise { - Logger.info(`Logging out of session '${sessionId}'`); + public async removeSessionById(sessionId: string, writeToDisk: boolean = true): Promise { + this._logger.info(`Logging out of session '${sessionId}'`); const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId); if (tokenIndex === -1) { - Logger.info(`Session not found '${sessionId}'`); + this._logger.info(`Session not found '${sessionId}'`); return Promise.resolve(undefined); } const token = this._tokens.splice(tokenIndex, 1)[0]; - return this.removeSessionByIToken(token, writeToDisk); + const session = await this.removeSessionByIToken(token, writeToDisk); + + if (session) { + this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); + } + + return session; } public async clearSessions() { - Logger.info('Logging out of all sessions'); + this._logger.info('Logging out of all sessions'); this._tokens = []; - await this._tokenStorage.deleteAll(); + await this._tokenStorage.deleteAll(item => this.sessionMatchesEndpoint(item)); this._refreshTimeouts.forEach(timeout => { clearTimeout(timeout); @@ -421,9 +461,9 @@ export class AzureActiveDirectoryService { } const session = this.convertToSessionSync(token); - Logger.info(`Sending change event for session that was removed with scopes: ${token.scope}`); - onDidChangeSessions.fire({ added: [], removed: [session], changed: [] }); - Logger.info(`Logged out of session '${token.sessionId}' with scopes: ${token.scope}`); + this._logger.info(`Sending change event for session that was removed with scopes: ${token.scope}`); + this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); + this._logger.info(`Logged out of session '${token.sessionId}' with scopes: ${token.scope}`); return session; } @@ -436,11 +476,11 @@ export class AzureActiveDirectoryService { this._refreshTimeouts.set(sessionId, setTimeout(async () => { try { const refreshedToken = await this.refreshToken(refreshToken, scopeData, sessionId); - Logger.info('Triggering change session event...'); - onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] }); + this._logger.info('Triggering change session event...'); + this._sessionChangeEmitter.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] }); } catch (e) { if (e.message !== REFRESH_NETWORK_FAILURE) { - vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed.")); + vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); await this.removeSessionById(sessionId); } } @@ -464,10 +504,10 @@ export class AzureActiveDirectoryService { try { if (json.id_token) { - claims = JSON.parse(Buffer.from(json.id_token.split('.')[1], 'base64').toString()); + claims = JSON.parse(base64Decode(json.id_token.split('.')[1])); } else { - Logger.info('Attempting to parse access_token instead since no id_token was included in the response.'); - claims = JSON.parse(Buffer.from(json.access_token.split('.')[1], 'base64').toString()); + this._logger.info('Attempting to parse access_token instead since no id_token was included in the response.'); + claims = JSON.parse(base64Decode(json.access_token.split('.')[1])); } } catch (e) { throw e; @@ -488,10 +528,11 @@ export class AzureActiveDirectoryService { idToken: json.id_token, refreshToken: json.refresh_token, scope: scopeData.scopeStr, - sessionId: existingId || `${id}/${uuid()}`, + sessionId: existingId || `${id}/${randomUUID()}`, account: { label, - id + id, + type: claims.tid === MSA_TID || claims.tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD } }; } @@ -510,31 +551,22 @@ export class AzureActiveDirectoryService { }; } - private async convertToSession(token: IToken): Promise { + private async convertToSession(token: IToken, scopeData: IScopeData): Promise { if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) { token.expiresAt - ? Logger.info(`Token available from cache (for scopes ${token.scope}), expires in ${token.expiresAt - Date.now()} milliseconds`) - : Logger.info('Token available from cache (for scopes ${token.scope})'); + ? this._logger.info(`Token available from cache (for scopes ${token.scope}), expires in ${token.expiresAt - Date.now()} milliseconds`) + : this._logger.info('Token available from cache (for scopes ${token.scope})'); return { id: token.sessionId, accessToken: token.accessToken, idToken: token.idToken, account: token.account, - scopes: token.scope.split(' ') + scopes: scopeData.originalScopes ?? scopeData.scopes }; } try { - Logger.info(`Token expired or unavailable (for scopes ${token.scope}), trying refresh`); - const scopes = token.scope.split(' '); - const scopeData: IScopeData = { - scopes, - scopeStr: token.scope, - // filter our special scopes - scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '), - clientId: this.getClientId(scopes), - tenant: this.getTenantId(scopes), - }; + this._logger.info(`Token expired or unavailable (for scopes ${token.scope}), trying refresh`); const refreshedToken = await this.refreshToken(token.refreshToken, scopeData, token.sessionId); if (refreshedToken.accessToken) { return { @@ -542,7 +574,8 @@ export class AzureActiveDirectoryService { accessToken: refreshedToken.accessToken, idToken: refreshedToken.idToken, account: token.account, - scopes: token.scope.split(' ') + // We always prefer the original scopes requested since that array is used as a key in the AuthService + scopes: scopeData.originalScopes ?? scopeData.scopes }; } else { throw new Error(); @@ -567,7 +600,7 @@ export class AzureActiveDirectoryService { } private async doRefreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise { - Logger.info(`Refreshing token for scopes: ${scopeData.scopeStr}`); + this._logger.info(`Refreshing token for scopes: ${scopeData.scopeStr}`); const postData = querystring.stringify({ refresh_token: refreshToken, client_id: scopeData.clientId, @@ -576,7 +609,7 @@ export class AzureActiveDirectoryService { }); const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints'); - const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl; + const endpointUrl = proxyEndpoints?.microsoft || this._loginEndpointUrl; const endpoint = `${endpointUrl}${scopeData.tenant}/oauth2/v2.0/token`; try { @@ -585,8 +618,8 @@ export class AzureActiveDirectoryService { if (token.expiresIn) { this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); } - await this.setToken(token, scopeData); - Logger.info(`Token refresh success for scopes: ${token.scope}`); + this.setToken(token, scopeData); + this._logger.info(`Token refresh success for scopes: ${token.scope}`); return token; } catch (e) { if (e.message === REFRESH_NETWORK_FAILURE) { @@ -597,7 +630,7 @@ export class AzureActiveDirectoryService { } throw e; } - Logger.error(`Refreshing token failed (for scopes: ${scopeData.scopeStr}): ${e.message}`); + this._logger.error(`Refreshing token failed (for scopes: ${scopeData.scopeStr}): ${e.message}`); throw e; } } @@ -633,7 +666,6 @@ export class AzureActiveDirectoryService { return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => { uriEventListener = this._uriHandler.event(async (uri: vscode.Uri) => { try { - console.log(uri.query); const query = querystring.parse(uri.query); let { code, nonce } = query; if (Array.isArray(code)) { @@ -677,9 +709,9 @@ export class AzureActiveDirectoryService { private async handleCodeInputBox(inputBox: vscode.InputBox, verifier: string, scopeData: IScopeData): Promise { inputBox.ignoreFocusOut = true; - inputBox.title = localize('pasteCodeTitle', 'Microsoft Authentication'); - inputBox.prompt = localize('pasteCodePrompt', 'Provide the authorization code to complete the sign in flow.'); - inputBox.placeholder = localize('pasteCodePlaceholder', 'Paste authorization code here...'); + inputBox.title = vscode.l10n.t('Microsoft Authentication'); + inputBox.prompt = vscode.l10n.t('Provide the authorization code to complete the sign in flow.'); + inputBox.placeholder = vscode.l10n.t('Paste authorization code here...'); return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => { inputBox.show(); inputBox.onDidAccept(async () => { @@ -700,7 +732,7 @@ export class AzureActiveDirectoryService { } private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise { - Logger.info(`Exchanging login code for token for scopes: ${scopeData.scopeStr}`); + this._logger.info(`Exchanging login code for token for scopes: ${scopeData.scopeStr}`); let token: IToken | undefined; try { const postData = querystring.stringify({ @@ -712,34 +744,42 @@ export class AzureActiveDirectoryService { redirect_uri: redirectUrl }); - const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints'); - const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl; + let endpointUrl: string; + + if (this._loginEndpointUrl !== defaultLoginEndpointUrl) { + // If this is for sovereign clouds, don't try using the proxy endpoint, which supports only public cloud + endpointUrl = this._loginEndpointUrl; + } else { + const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints'); + endpointUrl = proxyEndpoints?.microsoft || this._loginEndpointUrl; + } + const endpoint = `${endpointUrl}${scopeData.tenant}/oauth2/v2.0/token`; const json = await this.fetchTokenResponse(endpoint, postData, scopeData); - Logger.info(`Exchanging login code for token (for scopes: ${scopeData.scopeStr}) succeeded!`); + this._logger.info(`Exchanging login code for token (for scopes: ${scopeData.scopeStr}) succeeded!`); token = this.convertToTokenSync(json, scopeData); } catch (e) { - Logger.error(`Error exchanging code for token (for scopes ${scopeData.scopeStr}): ${e}`); + this._logger.error(`Error exchanging code for token (for scopes ${scopeData.scopeStr}): ${e}`); throw e; } if (token.expiresIn) { this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); } - await this.setToken(token, scopeData); - Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); - return await this.convertToSession(token); + this.setToken(token, scopeData); + this._logger.info(`Login successful for scopes: ${scopeData.scopeStr}`); + return await this.convertToSession(token, scopeData); } private async fetchTokenResponse(endpoint: string, postData: string, scopeData: IScopeData): Promise { let attempts = 0; while (attempts <= 3) { attempts++; - let result: Response | undefined; + let result; let errorMessage: string | undefined; try { - result = await fetch(endpoint, { + result = await fetching(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -753,7 +793,7 @@ export class AzureActiveDirectoryService { if (!result || result.status > 499) { if (attempts > 3) { - Logger.error(`Fetching token failed for scopes (${scopeData.scopeStr}): ${result ? await result.text() : errorMessage}`); + this._logger.error(`Fetching token failed for scopes (${scopeData.scopeStr}): ${result ? await result.text() : errorMessage}`); break; } // Exponential backoff @@ -776,8 +816,8 @@ export class AzureActiveDirectoryService { //#region storage operations - private async setToken(token: IToken, scopeData: IScopeData): Promise { - Logger.info(`Setting token for scopes: ${scopeData.scopeStr}`); + private setToken(token: IToken, scopeData: IScopeData): void { + this._logger.info(`Setting token for scopes: ${scopeData.scopeStr}`); const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId); if (existingTokenIndex > -1) { @@ -786,23 +826,67 @@ export class AzureActiveDirectoryService { this._tokens.push(token); } + // Don't await because setting the token is only useful for any new windows that open. + this.storeToken(token, scopeData); + } + + private async storeToken(token: IToken, scopeData: IScopeData): Promise { + if (!vscode.window.state.focused) { + const shouldStore = await new Promise((resolve, _) => { + // To handle the case where the window is not focused for a long time. We want to store the token + // at some point so that the next time they _do_ interact with VS Code, they don't have to sign in again. + const timer = setTimeout( + () => resolve(true), + // 5 hours + random extra 0-30 seconds so that each window doesn't try to store at the same time + (18000000) + Math.floor(Math.random() * 30000) + ); + const dispose = vscode.Disposable.from( + vscode.window.onDidChangeWindowState(e => { + if (e.focused) { + resolve(true); + dispose.dispose(); + clearTimeout(timer); + } + }), + this._tokenStorage.onDidChangeInOtherWindow(e => { + if (e.updated.includes(token.sessionId)) { + resolve(false); + dispose.dispose(); + clearTimeout(timer); + } + }) + ); + }); + + if (!shouldStore) { + this._logger.info(`Not storing token for scopes ${scopeData.scopeStr} because it was added in another window`); + return; + } + } + await this._tokenStorage.store(token.sessionId, { id: token.sessionId, refreshToken: token.refreshToken, scope: token.scope, - account: token.account + account: token.account, + endpoint: this._loginEndpointUrl, }); + this._logger.info(`Stored token for scopes: ${scopeData.scopeStr}`); } private async checkForUpdates(e: IDidChangeInOtherWindowEvent): Promise { - const added: vscode.AuthenticationSession[] = []; - const removed: vscode.AuthenticationSession[] = []; for (const key of e.added) { const session = await this._tokenStorage.get(key); if (!session) { - Logger.error('session not found that was apparently just added'); + this._logger.error('session not found that was apparently just added'); return; } + + if (!this.sessionMatchesEndpoint(session)) { + // If the session wasn't made for this login endpoint, ignore this update + continue; + } + const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id); if (!matchesExisting && session.refreshToken) { try { @@ -815,15 +899,15 @@ export class AzureActiveDirectoryService { clientId: this.getClientId(scopes), tenant: this.getTenantId(scopes), }; - Logger.info(`Session added in another window with scopes: ${session.scope}`); + this._logger.info(`Session added in another window with scopes: ${session.scope}`); const token = await this.refreshToken(session.refreshToken, scopeData, session.id); - Logger.info(`Sending change event for session that was added with scopes: ${scopeData.scopeStr}`); - onDidChangeSessions.fire({ added: [this.convertToSessionSync(token)], removed: [], changed: [] }); + this._logger.info(`Sending change event for session that was added with scopes: ${scopeData.scopeStr}`); + this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(token)], removed: [], changed: [] }); return; } catch (e) { // Network failures will automatically retry on next poll. if (e.message !== REFRESH_NETWORK_FAILURE) { - vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed.")); + vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); await this.removeSessionById(session.id); } return; @@ -832,33 +916,26 @@ export class AzureActiveDirectoryService { } for (const { value } of e.removed) { - Logger.info(`Session removed in another window with scopes: ${value.scope}`); - const session = await this.removeSessionById(value.id, false); - if (session) { - removed.push(session); + if (!this.sessionMatchesEndpoint(value)) { + // If the session wasn't made for this login endpoint, ignore this update + continue; } + + this._logger.info(`Session removed in another window with scopes: ${value.scope}`); + await this.removeSessionById(value.id, false); } + + // NOTE: We don't need to handle changed sessions because all that really would give us is a new refresh token + // because access tokens are not stored in Secret Storage due to their short lifespan. This new refresh token + // is not useful in this window because we really only care about the lifetime of the _access_ token which we + // are already managing (see usages of `setSessionTimeout`). } - //#endregion + private sessionMatchesEndpoint(session: IStoredSession): boolean { + // For older sessions with no endpoint set, it can be assumed to be the default endpoint + session.endpoint ||= defaultLoginEndpointUrl; - //#region static methods - - private static getCallbackEnvironment(callbackUri: vscode.Uri): string { - if (callbackUri.scheme !== 'https' && callbackUri.scheme !== 'http') { - return callbackUri.scheme; - } - - switch (callbackUri.authority) { - case 'online.visualstudio.com': - return 'vso'; - case 'online-ppe.core.vsengsaas.visualstudio.com': - return 'vsoppe'; - case 'online.dev.core.vsengsaas.visualstudio.com': - return 'vsodev'; - default: - return callbackUri.authority; - } + return session.endpoint === this._loginEndpointUrl; } //#endregion diff --git a/extensions/microsoft-authentication/src/env/node/sha256.ts b/extensions/microsoft-authentication/src/UriEventHandler.ts similarity index 64% rename from extensions/microsoft-authentication/src/env/node/sha256.ts rename to extensions/microsoft-authentication/src/UriEventHandler.ts index de76241ad0..2f3adc1092 100644 --- a/extensions/microsoft-authentication/src/env/node/sha256.ts +++ b/extensions/microsoft-authentication/src/UriEventHandler.ts @@ -3,8 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; +import * as vscode from 'vscode'; -export async function sha256(s: string | Uint8Array): Promise { - return (require('crypto')).createHash('sha256').update(s).digest('base64'); +export class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { + public handleUri(uri: vscode.Uri) { + this.fire(uri); + } } diff --git a/extensions/microsoft-authentication/src/betterSecretStorage.ts b/extensions/microsoft-authentication/src/betterSecretStorage.ts index 9fe77ede3f..d78f5dfd5c 100644 --- a/extensions/microsoft-authentication/src/betterSecretStorage.ts +++ b/extensions/microsoft-authentication/src/betterSecretStorage.ts @@ -81,11 +81,13 @@ export class BetterTokenStorage { return tokens.get(key); } - async getAll(): Promise { + async getAll(predicate?: (item: T) => boolean): Promise { const tokens = await this.getTokens(); const values = new Array(); for (const [_, value] of tokens) { - values.push(value); + if (!predicate || predicate(value)) { + values.push(value); + } } return values; } @@ -141,11 +143,13 @@ export class BetterTokenStorage { this._operationInProgress = false; } - async deleteAll(): Promise { + async deleteAll(predicate?: (item: T) => boolean): Promise { const tokens = await this.getTokens(); const promises = []; - for (const [key] of tokens) { - promises.push(this.delete(key)); + for (const [key, value] of tokens) { + if (!predicate || predicate(value)) { + promises.push(this.delete(key)); + } } await Promise.all(promises); } diff --git a/extensions/microsoft-authentication/src/env/browser/authServer.ts b/extensions/microsoft-authentication/src/browser/authServer.ts similarity index 100% rename from extensions/microsoft-authentication/src/env/browser/authServer.ts rename to extensions/microsoft-authentication/src/browser/authServer.ts diff --git a/extensions/microsoft-authentication/src/browser/buffer.ts b/extensions/microsoft-authentication/src/browser/buffer.ts new file mode 100644 index 0000000000..798809c0d7 --- /dev/null +++ b/extensions/microsoft-authentication/src/browser/buffer.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function base64Encode(text: string): string { + return btoa(text); +} + +export function base64Decode(text: string): string { + // modification of https://stackoverflow.com/a/38552302 + const replacedCharacters = text.replace(/-/g, '+').replace(/_/g, '/'); + const decodedText = decodeURIComponent(atob(replacedCharacters).split('').map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + return decodedText; +} diff --git a/extensions/microsoft-authentication/src/browser/crypto.ts b/extensions/microsoft-authentication/src/browser/crypto.ts new file mode 100644 index 0000000000..988eb6727d --- /dev/null +++ b/extensions/microsoft-authentication/src/browser/crypto.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const crypto = globalThis.crypto; diff --git a/extensions/microsoft-authentication/src/cryptoUtils.ts b/extensions/microsoft-authentication/src/cryptoUtils.ts new file mode 100644 index 0000000000..ca4dfa0be1 --- /dev/null +++ b/extensions/microsoft-authentication/src/cryptoUtils.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { base64Encode } from './node/buffer'; +import { crypto } from './node/crypto'; + +export function randomUUID() { + return crypto.randomUUID(); +} + +function dec2hex(dec: number): string { + return ('0' + dec.toString(16)).slice(-2); +} + +export function generateCodeVerifier(): string { + const array = new Uint32Array(56 / 2); + crypto.getRandomValues(array); + return Array.from(array, dec2hex).join(''); +} + +function sha256(plain: string | undefined) { + const encoder = new TextEncoder(); + const data = encoder.encode(plain); + return crypto.subtle.digest('SHA-256', data); +} + +function base64urlencode(a: ArrayBuffer) { + let str = ''; + const bytes = new Uint8Array(a); + const len = bytes.byteLength; + for (let i = 0; i < len; i++) { + str += String.fromCharCode(bytes[i]); + } + return base64Encode(str) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); +} + +export async function generateCodeChallenge(v: string) { + const hashed = await sha256(v); + const base64encoded = base64urlencode(hashed); + return base64encoded; +} diff --git a/extensions/microsoft-authentication/src/extension.ts b/extensions/microsoft-authentication/src/extension.ts index 661d16b034..6d1f57dec8 100644 --- a/extensions/microsoft-authentication/src/extension.ts +++ b/extensions/microsoft-authentication/src/extension.ts @@ -4,18 +4,115 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { AzureActiveDirectoryService, onDidChangeSessions } from './AADHelper'; +import { AzureActiveDirectoryService, IStoredSession } from './AADHelper'; +import { BetterTokenStorage } from './betterSecretStorage'; +import { UriEventHandler } from './UriEventHandler'; import TelemetryReporter from '@vscode/extension-telemetry'; -export async function activate(context: vscode.ExtensionContext) { - const { name, version, aiKey } = context.extension.packageJSON as { name: string; version: string; aiKey: string }; - const telemetryReporter = new TelemetryReporter(name, version, aiKey); +async function initMicrosoftSovereignCloudAuthProvider(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter, uriHandler: UriEventHandler, tokenStorage: BetterTokenStorage): Promise { + let settingValue = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('endpoint'); + let authProviderName: string | undefined; + if (!settingValue) { + return undefined; + } else if (settingValue === 'Azure China') { + authProviderName = settingValue; + settingValue = 'https://login.chinacloudapi.cn/'; + } else if (settingValue === 'Azure US Government') { + authProviderName = settingValue; + settingValue = 'https://login.microsoftonline.us/'; + } - const loginService = new AzureActiveDirectoryService(context); + // validate user value + let uri: vscode.Uri; + try { + uri = vscode.Uri.parse(settingValue, true); + } catch (e) { + vscode.window.showErrorMessage(vscode.l10n.t('Microsoft Sovereign Cloud login URI is not a valid URI: {0}', e.message ?? e)); + return; + } + + // Add trailing slash if needed + if (!settingValue.endsWith('/')) { + settingValue += '/'; + } + + const aadService = new AzureActiveDirectoryService( + vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), + context, + uriHandler, + tokenStorage, + telemetryReporter, + settingValue); + await aadService.initialize(); + + authProviderName ||= uri.authority; + const disposable = vscode.authentication.registerAuthenticationProvider('microsoft-sovereign-cloud', authProviderName, { + onDidChangeSessions: aadService.onDidChangeSessions, + getSessions: (scopes: string[]) => aadService.getSessions(scopes), + createSession: async (scopes: string[]) => { + try { + /* __GDPR__ + "login" : { + "owner": "TylerLeonhardt", + "comment": "Used to determine the usage of the Microsoft Sovereign Cloud Auth Provider.", + "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } + } + */ + telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', { + // Get rid of guids from telemetry. + scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), + }); + + return await aadService.createSession(scopes.sort()); + } catch (e) { + /* __GDPR__ + "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } + */ + telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed'); + + throw e; + } + }, + removeSession: async (id: string) => { + try { + /* __GDPR__ + "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } + */ + telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud'); + + await aadService.removeSessionById(id); + } catch (e) { + /* __GDPR__ + "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } + */ + telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed'); + } + } + }, { supportsMultipleAccounts: true }); + + context.subscriptions.push(disposable); + return disposable; +} + +export async function activate(context: vscode.ExtensionContext) { + const aiKey: string = context.extension.packageJSON.aiKey; + const telemetryReporter = new TelemetryReporter(aiKey); + + const uriHandler = new UriEventHandler(); + context.subscriptions.push(uriHandler); + context.subscriptions.push(vscode.window.registerUriHandler(uriHandler)); + const betterSecretStorage = new BetterTokenStorage('microsoft.login.keylist', context); + + const loginService = new AzureActiveDirectoryService( + vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Authentication'), { log: true }), + context, + uriHandler, + betterSecretStorage, + telemetryReporter); await loginService.initialize(); context.subscriptions.push(vscode.authentication.registerAuthenticationProvider('microsoft', 'Microsoft', { - onDidChangeSessions: onDidChangeSessions.event, + onDidChangeSessions: loginService.onDidChangeSessions, getSessions: (scopes: string[]) => loginService.getSessions(scopes), createSession: async (scopes: string[]) => { try { @@ -31,9 +128,7 @@ export async function activate(context: vscode.ExtensionContext) { scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), }); - const session = await loginService.createSession(scopes.sort()); - onDidChangeSessions.fire({ added: [session], removed: [], changed: [] }); - return session; + return await loginService.createSession(scopes.sort()); } catch (e) { /* __GDPR__ "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } @@ -50,10 +145,7 @@ export async function activate(context: vscode.ExtensionContext) { */ telemetryReporter.sendTelemetryEvent('logout'); - const session = await loginService.removeSessionById(id); - if (session) { - onDidChangeSessions.fire({ added: [], removed: [session], changed: [] }); - } + await loginService.removeSessionById(id); } catch (e) { /* __GDPR__ "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } @@ -63,6 +155,15 @@ export async function activate(context: vscode.ExtensionContext) { } }, { supportsMultipleAccounts: true })); + let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage); + + context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration('microsoft-sovereign-cloud.endpoint')) { + microsoftSovereignCloudAuthProviderDisposable?.dispose(); + microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage); + } + })); + return; } diff --git a/extensions/microsoft-authentication/src/logger.ts b/extensions/microsoft-authentication/src/logger.ts index e37b41d22d..d69ada0d3a 100644 --- a/extensions/microsoft-authentication/src/logger.ts +++ b/extensions/microsoft-authentication/src/logger.ts @@ -5,55 +5,5 @@ import * as vscode from 'vscode'; -type LogLevel = 'Trace' | 'Info' | 'Error'; - -class Log { - private output: vscode.OutputChannel; - - constructor() { - this.output = vscode.window.createOutputChannel('Microsoft Authentication'); - } - - private data2String(data: any): string { - if (data instanceof Error) { - return data.stack || data.message; - } - if (data.success === false && data.message) { - return data.message; - } - return data.toString(); - } - - public trace(message: string, data?: any): void { - this.logLevel('Trace', message, data); - } - - public info(message: string, data?: any): void { - this.logLevel('Info', message, data); - } - - public error(message: string, data?: any): void { - this.logLevel('Error', message, data); - } - - public logLevel(level: LogLevel, message: string, data?: any): void { - this.output.appendLine(`[${level} - ${this.now()}] ${message}`); - if (data) { - this.output.appendLine(this.data2String(data)); - } - } - - private now(): string { - const now = new Date(); - return padLeft(now.getUTCHours() + '', 2, '0') - + ':' + padLeft(now.getMinutes() + '', 2, '0') - + ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds(); - } -} - -function padLeft(s: string, n: number, pad = ' ') { - return pad.repeat(Math.max(0, n - s.length)) + s; -} - -const Logger = new Log(); +const Logger = vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Authentication'), { log: true }); export default Logger; diff --git a/extensions/microsoft-authentication/src/authServer.ts b/extensions/microsoft-authentication/src/node/authServer.ts similarity index 100% rename from extensions/microsoft-authentication/src/authServer.ts rename to extensions/microsoft-authentication/src/node/authServer.ts diff --git a/extensions/microsoft-authentication/src/node/buffer.ts b/extensions/microsoft-authentication/src/node/buffer.ts new file mode 100644 index 0000000000..f45f0cd3e8 --- /dev/null +++ b/extensions/microsoft-authentication/src/node/buffer.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function base64Encode(text: string): string { + return Buffer.from(text, 'binary').toString('base64'); +} + +export function base64Decode(text: string): string { + return Buffer.from(text, 'base64').toString('utf8'); +} diff --git a/extensions/markdown-language-features/src/util/string.ts b/extensions/microsoft-authentication/src/node/crypto.ts similarity index 78% rename from extensions/markdown-language-features/src/util/string.ts rename to extensions/microsoft-authentication/src/node/crypto.ts index 5e23609d04..3b6f246158 100644 --- a/extensions/markdown-language-features/src/util/string.ts +++ b/extensions/microsoft-authentication/src/node/crypto.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nodeCrypto from 'crypto'; -export function isEmptyOrWhitespace(str: string): boolean { - return /^\s*$/.test(str); -} +export const crypto: Crypto = nodeCrypto.webcrypto as any; diff --git a/extensions/microsoft-authentication/src/node/fetch.ts b/extensions/microsoft-authentication/src/node/fetch.ts new file mode 100644 index 0000000000..b7aa39ecbe --- /dev/null +++ b/extensions/microsoft-authentication/src/node/fetch.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import fetch from 'node-fetch'; + +export const fetching = fetch; diff --git a/extensions/microsoft-authentication/src/utils.ts b/extensions/microsoft-authentication/src/utils.ts index 5371e02576..b1acfea1f0 100644 --- a/extensions/microsoft-authentication/src/utils.ts +++ b/extensions/microsoft-authentication/src/utils.ts @@ -4,10 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { env, UIKind, Uri } from 'vscode'; -export function toBase64UrlEncoding(base64string: string) { - return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); // Need to use base64url encoding -} - const LOCALHOST_ADDRESSES = ['localhost', '127.0.0.1', '0:0:0:0:0:0:0:1', '::1']; function isLocalhost(uri: Uri): boolean { if (!/^https?$/i.test(uri.scheme)) { diff --git a/extensions/microsoft-authentication/yarn.lock b/extensions/microsoft-authentication/yarn.lock index 9328b1d4b1..5513eb3e99 100644 --- a/extensions/microsoft-authentication/yarn.lock +++ b/extensions/microsoft-authentication/yarn.lock @@ -2,41 +2,168 @@ # yarn lockfile v1 -"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" - integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + tslib "^2.2.0" -"@microsoft/1ds-post-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" - integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== +"@azure/core-auth@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e" + integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ== dependencies: - "@microsoft/1ds-core-js" "3.2.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== +"@azure/core-rest-pipeline@^1.10.0": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz#348290847ca31b9eecf9cf5de7519aaccdd30968" + integrity sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA== dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.0.0" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" + uuid "^8.3.0" -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== +"@azure/core-tracing@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" + integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw== + dependencies: + tslib "^2.2.0" -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== +"@azure/core-util@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.1.1.tgz#8f87b3dd468795df0f0849d9f096c3e7b29452c1" + integrity sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.3.tgz#6e36704aa51be7d4a1bae24731ea580836293c96" + integrity sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g== + dependencies: + tslib "^2.2.0" + +"@microsoft/1ds-core-js@3.2.8", "@microsoft/1ds-core-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.8.tgz#1b6b7d9bb858238c818ccf4e4b58ece7aeae5760" + integrity sha512-9o9SUAamJiTXIYwpkQDuueYt83uZfXp8zp8YFix1IwVPwC9RmE36T2CX9gXOeq1nDckOuOduYpA8qHvdh5BGfQ== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/1ds-post-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.8.tgz#46793842cca161bf7a2a5b6053c349f429e55110" + integrity sha512-SjlRoNcXcXBH6WQD/5SkkaCHIVqldH3gDu+bI7YagrOVJ5APxwT1Duw9gm3L1FjFa9S2i81fvJ3EVSKpp9wULA== + dependencies: + "@microsoft/1ds-core-js" "3.2.8" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-channel-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.9.tgz#840656f3c716de8b3eb0a98c122aa1b92bb8ebfb" + integrity sha512-fMBsAEB7pWtPn43y72q9Xy5E5y55r6gMuDQqRRccccVoQDPXyS57VCj5IdATblctru0C6A8XpL2vRyNmEsu0Vg== + dependencies: + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-common@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.9.tgz#a75e4a3143a7fd797687830c0ddd2069fd900827" + integrity sha512-mObn1moElyxZaGIRF/IU3cOaeKMgxghXnYEoHNUCA2e+rNwBIgxjyKkblFIpmGuHf4X7Oz3o3yBWpaC6AoMpig== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-core-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.9.tgz#0e5d207acfae6986a6fc97249eeb6117e523bf1b" + integrity sha512-HRuIuZ6aOWezcg/G5VyFDDWGL8hDNe/ljPP01J7ImH2kRPEgbtcfPSUMjkamGMefgdq81GZsSoC/NNGTP4pp2w== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085" + integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg== + +"@microsoft/applicationinsights-web-basic@^2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.9.tgz#eed2f3d1e19069962ed2155915c1656e6936e1d5" + integrity sha512-CH0J8JFOy7MjK8JO4pXXU+EML+Ilix+94PMZTX5EJlBU1in+mrik74/8qSg3UC4ekPi12KwrXaHCQSVC3WseXQ== + dependencies: + "@microsoft/applicationinsights-channel-js" "2.8.9" + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-web-snippet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz#6bb788b2902e48bf5d460c38c6bb7fedd686ddd7" + integrity sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ== + +"@microsoft/dynamicproto-js@^1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz#ede48dd3f85af14ee369c805e5ed5b84222b9fe2" + integrity sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA== + +"@opentelemetry/api@^1.0.4": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.2.0.tgz#89ef99401cde6208cff98760b67663726ef26686" + integrity sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g== + +"@opentelemetry/core@1.7.0", "@opentelemetry/core@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.7.0.tgz#83bdd1b7a4ceafcdffd6590420657caec5f7b34c" + integrity sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/resources@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.7.0.tgz#90ccd3a6a86b4dfba4e833e73944bd64958d78c5" + integrity sha512-u1M0yZotkjyKx8dj+46Sg5thwtOTBmtRieNXqdCRiWUp6SfFiIP0bI+1XK3LhuXqXkBXA1awJZaTqKduNMStRg== + dependencies: + "@opentelemetry/core" "1.7.0" + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/sdk-trace-base@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.7.0.tgz#b498424e0c6340a9d80de63fd408c5c2130a60a5" + integrity sha512-Iz84C+FVOskmauh9FNnj4+VrA+hG5o+tkMzXuoesvSfunVSioXib0syVFeNXwOm4+M5GdWCuW632LVjqEXStIg== + dependencies: + "@opentelemetry/core" "1.7.0" + "@opentelemetry/resources" "1.7.0" + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/semantic-conventions@1.7.0", "@opentelemetry/semantic-conventions@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.7.0.tgz#af80a1ef7cf110ea3a68242acd95648991bcd763" + integrity sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA== + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@types/node-fetch@^2.5.7": version "2.5.7" @@ -75,31 +202,68 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0" integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw== -"@vscode/extension-telemetry@0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" - integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== +"@vscode/extension-telemetry@0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.7.5.tgz#bf965731816e08c3f146f96d901ec67954fc913b" + integrity sha512-fJ5y3TcpqqkFYHneabYaoB4XAhDdVflVm+TDKshw9VOs77jkgNS4UA7LNXrWeO0eDne3Sh3JgURf+xzc1rk69w== dependencies: - "@microsoft/1ds-core-js" "^3.2.3" - "@microsoft/1ds-post-js" "^3.2.3" + "@microsoft/1ds-core-js" "^3.2.8" + "@microsoft/1ds-post-js" "^3.2.8" + "@microsoft/applicationinsights-web-basic" "^2.8.9" + applicationinsights "2.4.1" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +applicationinsights@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-2.4.1.tgz#4de4c4dd3c7c4a44445cfbf3d15808fc0dcc423d" + integrity sha512-0n0Ikd0gzSm460xm+M0UTWIwXrhrH/0bqfZatcJjYObWyefxfAxapGEyNnSGd1Tg90neHz+Yhf+Ff/zgvPiQYA== + dependencies: + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.10.0" + "@microsoft/applicationinsights-web-snippet" "^1.0.1" + "@opentelemetry/api" "^1.0.4" + "@opentelemetry/core" "^1.0.1" + "@opentelemetry/sdk-trace-base" "^1.0.1" + "@opentelemetry/semantic-conventions" "^1.0.1" + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "1.1.0" + diagnostic-channel-publishers "1.0.5" + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -base64-js@^1.0.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" - integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g== - -buffer@^5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" - integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" combined-stream@^1.0.8: version "1.0.8" @@ -108,15 +272,44 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -emitter-component@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6" - integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY= +diagnostic-channel-publishers@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz#df8c317086c50f5727fdfb5d2fce214d2e4130ae" + integrity sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg== + +diagnostic-channel@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz#6985e9dfedfbc072d91dc4388477e4087147756e" + integrity sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ== + dependencies: + semver "^5.3.0" + +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" form-data@^3.0.0: version "3.0.0" @@ -127,15 +320,31 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -ieee754@^1.1.4: - version "1.1.13" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" - integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg== +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" -inherits@^2.0.1: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" mime-db@1.44.0: version "1.44.0" @@ -149,6 +358,11 @@ mime-types@^2.1.12: dependencies: mime-db "1.44.0" +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + node-fetch@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -156,52 +370,40 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" -randombytes@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" +querystring-es3@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== -safe-buffer@^5.0.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" - integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +semver@^5.3.0, semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== -sha.js@2.4.11: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -stream@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/stream/-/stream-0.0.2.tgz#7f5363f057f6592c5595f00bc80a27f5cec1f0ef" - integrity sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8= - dependencies: - emitter-component "^1.1.1" +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= -uuid@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.2.0.tgz#cb10dd6b118e2dada7d0cd9730ba7417c93d920e" - integrity sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q== +tslib@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== -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== +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== webidl-conversions@^3.0.0: version "3.0.1" diff --git a/extensions/mssql/src/util/dispose.ts b/extensions/mssql/src/util/dispose.ts index a4b4f2de81..9db4b937a7 100644 --- a/extensions/mssql/src/util/dispose.ts +++ b/extensions/mssql/src/util/dispose.ts @@ -12,6 +12,10 @@ function disposeAll(disposables: vscode.Disposable[]) { } } +export interface IDisposable { + dispose(): void; +} + export abstract class Disposable { private _isDisposed = false; diff --git a/extensions/notebook-renderers/package.json b/extensions/notebook-renderers/package.json index f78839bbee..3f9f3d21e3 100644 --- a/extensions/notebook-renderers/package.json +++ b/extensions/notebook-renderers/package.json @@ -41,14 +41,15 @@ ] }, "scripts": { - "compile": "npm run build-notebook", - "watch": "node ./esbuild --watch", + "compile": "npx gulp compile-extension:notebook-renderers && npm run build-notebook", + "watch": "npx gulp compile-watch:notebook-renderers", "build-notebook": "node ./esbuild" }, - "dependencies": { - }, + "dependencies": {}, "devDependencies": { - "@types/vscode-notebook-renderer": "^1.60.0" + "@types/jsdom": "^21.1.0", + "@types/vscode-notebook-renderer": "^1.60.0", + "jsdom": "^21.1.1" }, "repository": { "type": "git", diff --git a/extensions/notebook-renderers/src/ansi.ts b/extensions/notebook-renderers/src/ansi.ts index 760d50c614..b0049c97dc 100644 --- a/extensions/notebook-renderers/src/ansi.ts +++ b/extensions/notebook-renderers/src/ansi.ts @@ -5,11 +5,12 @@ import { RGBA, Color } from './color'; import { ansiColorIdentifiers } from './colorMap'; +import { ttPolicy } from './htmlHelper'; import { linkify } from './linkify'; -export function handleANSIOutput(text: string): HTMLSpanElement { - let workspaceFolder = undefined; +export function handleANSIOutput(text: string, trustHtml: boolean): HTMLSpanElement { + const workspaceFolder = undefined; const root: HTMLSpanElement = document.createElement('span'); const textLength: number = text.length; @@ -52,7 +53,7 @@ export function handleANSIOutput(text: string): HTMLSpanElement { if (sequenceFound) { // Flush buffer with previous styles. - appendStylizedStringToContainer(root, buffer, styleNames, workspaceFolder, customFgColor, customBgColor, customUnderlineColor); + appendStylizedStringToContainer(root, buffer, trustHtml, styleNames, workspaceFolder, customFgColor, customBgColor, customUnderlineColor); buffer = ''; @@ -98,7 +99,7 @@ export function handleANSIOutput(text: string): HTMLSpanElement { // Flush remaining text buffer if not empty. if (buffer) { - appendStylizedStringToContainer(root, buffer, styleNames, workspaceFolder, customFgColor, customBgColor, customUnderlineColor); + appendStylizedStringToContainer(root, buffer, trustHtml, styleNames, workspaceFolder, customFgColor, customBgColor, customUnderlineColor); } return root; @@ -379,9 +380,10 @@ export function handleANSIOutput(text: string): HTMLSpanElement { } } -export function appendStylizedStringToContainer( +function appendStylizedStringToContainer( root: HTMLElement, stringContent: string, + trustHtml: boolean, cssClasses: string[], workspaceFolder: string | undefined, customTextColor?: RGBA | string, @@ -392,7 +394,17 @@ export function appendStylizedStringToContainer( return; } - const container = linkify(stringContent, true, workspaceFolder); + let container = document.createElement('span'); + + if (trustHtml) { + const trustedHtml = ttPolicy?.createHTML(stringContent) ?? stringContent; + container.innerHTML = trustedHtml as string; + } + + if (container.childElementCount === 0) { + // plain text + container = linkify(stringContent, true, workspaceFolder); + } container.className = cssClasses.join(' '); if (customTextColor) { diff --git a/extensions/notebook-renderers/src/color.ts b/extensions/notebook-renderers/src/color.ts index 60784226e0..02c8ad18cc 100644 --- a/extensions/notebook-renderers/src/color.ts +++ b/extensions/notebook-renderers/src/color.ts @@ -866,9 +866,7 @@ export class Color { private _toString?: string; toString(): string { - if (!this._toString) { - this._toString = Color.Format.CSS.format(this); - } + this._toString ??= Color.Format.CSS.format(this); return this._toString; } diff --git a/extensions/notebook-renderers/src/htmlHelper.ts b/extensions/notebook-renderers/src/htmlHelper.ts new file mode 100644 index 0000000000..992a803b23 --- /dev/null +++ b/extensions/notebook-renderers/src/htmlHelper.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const ttPolicy = (typeof window !== 'undefined') ? + window.trustedTypes?.createPolicy('notebookRenderer', { + createHTML: value => value, + createScript: value => value, + }) : undefined; diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index 82f7637c10..cc6009297d 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -4,20 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import type { ActivationFunction, OutputItem, RendererContext } from 'vscode-notebook-renderer'; -import { truncatedArrayOfString } from './textHelper'; - -interface IDisposable { - dispose(): void; -} - -interface HtmlRenderingHook { - /** - * Invoked after the output item has been rendered but before it has been appended to the document. - * - * @return A new `HTMLElement` or `undefined` to continue using the provided element. - */ - postRender(outputItem: OutputItem, element: HTMLElement): HTMLElement | undefined; -} +import { createOutputContent, scrollableClass } from './textHelper'; +import { HtmlRenderingHook, IDisposable, IRichRenderContext, JavaScriptRenderingHook, RenderOptions } from './rendererTypes'; +import { ttPolicy } from './htmlHelper'; function clearContainer(container: HTMLElement) { while (container.firstChild) { @@ -34,8 +23,20 @@ function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable } }; + if (element.firstChild) { + const display = element.firstChild as HTMLElement; + if (display.firstChild && display.firstChild.nodeName === 'IMG' && display.firstChild instanceof HTMLImageElement) { + display.firstChild.src = src; + return disposable; + } + } + const image = document.createElement('img'); image.src = src; + const alt = getAltText(outputInfo); + if (alt) { + image.alt = alt; + } const display = document.createElement('div'); display.classList.add('display'); display.appendChild(image); @@ -44,11 +45,6 @@ function renderImage(outputInfo: OutputItem, element: HTMLElement): IDisposable return disposable; } -const ttPolicy = window.trustedTypes?.createPolicy('notebookRenderer', { - createHTML: value => value, - createScript: value => value, -}); - const preservedScriptAttributes: (keyof HTMLScriptElement)[] = [ 'type', 'src', 'nonce', 'noModule', 'async', ]; @@ -72,34 +68,96 @@ const domEval = (container: Element) => { } }; -function renderHTML(outputInfo: OutputItem, container: HTMLElement, hooks: Iterable): void { +function getAltText(outputInfo: OutputItem) { + const metadata = outputInfo.metadata; + if (typeof metadata === 'object' && metadata && 'vscode_altText' in metadata && typeof metadata.vscode_altText === 'string') { + return metadata.vscode_altText; + } + return undefined; +} + +function injectTitleForSvg(outputInfo: OutputItem, element: HTMLElement) { + if (outputInfo.mime.indexOf('svg') > -1) { + const svgElement = element.querySelector('svg'); + const altText = getAltText(outputInfo); + if (svgElement && altText) { + const title = document.createElement('title'); + title.innerText = altText; + svgElement.prepend(title); + } + } +} + +async function renderHTML(outputInfo: OutputItem, container: HTMLElement, signal: AbortSignal, hooks: Iterable): Promise { clearContainer(container); let element: HTMLElement = document.createElement('div'); const htmlContent = outputInfo.text(); const trustedHtml = ttPolicy?.createHTML(htmlContent) ?? htmlContent; element.innerHTML = trustedHtml as string; + injectTitleForSvg(outputInfo, element); for (const hook of hooks) { - element = hook.postRender(outputInfo, element) ?? element; + element = (await hook.postRender(outputInfo, element, signal)) ?? element; + if (signal.aborted) { + return; + } } container.appendChild(element); domEval(element); } -function renderJavascript(outputInfo: OutputItem, container: HTMLElement): void { - const str = outputInfo.text(); - const scriptVal = ``; +async function renderJavascript(outputInfo: OutputItem, container: HTMLElement, signal: AbortSignal, hooks: Iterable): Promise { + let scriptText = outputInfo.text(); + + for (const hook of hooks) { + scriptText = (await hook.preEvaluate(outputInfo, container, scriptText, signal)) ?? scriptText; + if (signal.aborted) { + return; + } + } + + const script = document.createElement('script'); + script.type = 'module'; + script.textContent = scriptText; + const element = document.createElement('div'); - const trustedHtml = ttPolicy?.createHTML(scriptVal) ?? scriptVal; + const trustedHtml = ttPolicy?.createHTML(script.outerHTML) ?? script.outerHTML; element.innerHTML = trustedHtml as string; container.appendChild(element); domEval(element); } -function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext & { readonly settings: { readonly lineLimit: number } }): void { - const element = document.createElement('div'); - container.appendChild(element); +interface Event { + (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable; +} + +function createDisposableStore(): { push(...disposables: IDisposable[]): void; dispose(): void } { + const localDisposables: IDisposable[] = []; + const disposable = { + push: (...disposables: IDisposable[]) => { + localDisposables.push(...disposables); + }, + dispose: () => { + localDisposables.forEach(d => d.dispose()); + } + }; + + return disposable; +} + +type DisposableStore = ReturnType; + +function renderError( + outputInfo: OutputItem, + outputElement: HTMLElement, + ctx: IRichRenderContext, + trustHTML: boolean +): IDisposable { + const disposableStore = createDisposableStore(); + + clearContainer(outputElement); + type ErrorLike = Partial; let err: ErrorLike; @@ -107,101 +165,255 @@ function renderError(outputInfo: OutputItem, container: HTMLElement, ctx: Render err = JSON.parse(outputInfo.text()); } catch (e) { console.log(e); - return; + return disposableStore; } if (err.stack) { - const stack = document.createElement('pre'); - stack.classList.add('traceback'); - stack.style.margin = '8px 0'; - const element = document.createElement('span'); - truncatedArrayOfString(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, element); - stack.appendChild(element); - container.appendChild(stack); + outputElement.classList.add('traceback'); + + const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); + const content = createOutputContent(outputInfo.id, [err.stack ?? ''], ctx.settings.lineLimit, outputScrolling, trustHTML); + const contentParent = document.createElement('div'); + contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap); + disposableStore.push(ctx.onDidChangeSettings(e => { + contentParent.classList.toggle('word-wrap', e.outputWordWrap); + })); + contentParent.classList.toggle('scrollable', outputScrolling); + + contentParent.appendChild(content); + outputElement.appendChild(contentParent); + initializeScroll(contentParent, disposableStore); } else { const header = document.createElement('div'); const headerMessage = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message; if (headerMessage) { header.innerText = headerMessage; - container.appendChild(header); + outputElement.appendChild(header); } } - container.classList.add('error'); + outputElement.classList.add('error'); + return disposableStore; } -function renderStream(outputInfo: OutputItem, container: HTMLElement, error: boolean, ctx: RendererContext & { readonly settings: { readonly lineLimit: number } }): void { - const outputContainer = container.parentElement; - if (!outputContainer) { - // should never happen +function getPreviousMatchingContentGroup(outputElement: HTMLElement) { + const outputContainer = outputElement.parentElement; + let match: HTMLElement | undefined = undefined; + + let previous = outputContainer?.previousSibling; + while (previous) { + const outputElement = (previous.firstChild as HTMLElement | null); + if (!outputElement || !outputElement.classList.contains('output-stream')) { + break; + } + + match = outputElement.firstChild as HTMLElement; + previous = previous?.previousSibling; + } + + return match; +} + +function onScrollHandler(e: globalThis.Event) { + const target = e.target as HTMLElement; + if (target.scrollTop === 0) { + target.classList.remove('more-above'); + } else { + target.classList.add('more-above'); + } +} + +function onKeypressHandler(e: KeyboardEvent) { + if (e.ctrlKey || e.shiftKey) { return; } - - const prev = outputContainer.previousSibling; - if (prev) { - // OutputItem in the same cell - // check if the previous item is a stream - const outputElement = (prev.firstChild as HTMLElement | null); - if (outputElement && outputElement.getAttribute('output-mime-type') === outputInfo.mime) { - // same stream - const text = outputInfo.text(); - - const element = document.createElement('span'); - truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, element); - outputElement.appendChild(element); - return; - } - } - - const element = document.createElement('span'); - element.classList.add('output-stream'); - - const text = outputInfo.text(); - truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, element); - while (container.firstChild) { - container.removeChild(container.firstChild); - } - container.appendChild(element); - container.setAttribute('output-mime-type', outputInfo.mime); - if (error) { - container.classList.add('error'); + if (e.code === 'ArrowDown' || e.code === 'End' || e.code === 'ArrowUp' || e.code === 'Home') { + // These should change the scroll position, not adjust the selected cell in the notebook + e.stopPropagation(); } } -function renderText(outputInfo: OutputItem, container: HTMLElement, ctx: RendererContext & { readonly settings: { readonly lineLimit: number } }): void { - clearContainer(container); - const contentNode = document.createElement('div'); - contentNode.classList.add('output-plaintext'); - const text = outputInfo.text(); - truncatedArrayOfString(outputInfo.id, [text], ctx.settings.lineLimit, contentNode); - container.appendChild(contentNode); +// if there is a scrollable output, it will be scrolled to the given value if provided or the bottom of the element +function initializeScroll(scrollableElement: HTMLElement, disposables: DisposableStore, scrollTop?: number) { + if (scrollableElement.classList.contains(scrollableClass)) { + const scrollbarVisible = scrollableElement.scrollHeight > scrollableElement.clientHeight; + scrollableElement.classList.toggle('scrollbar-visible', scrollbarVisible); + scrollableElement.scrollTop = scrollTop !== undefined ? scrollTop : scrollableElement.scrollHeight; + if (scrollbarVisible) { + scrollableElement.addEventListener('scroll', onScrollHandler); + disposables.push({ dispose: () => scrollableElement.removeEventListener('scroll', onScrollHandler) }); + scrollableElement.addEventListener('keydown', onKeypressHandler); + disposables.push({ dispose: () => scrollableElement.removeEventListener('keydown', onKeypressHandler) }); + } + } +} +// Find the scrollTop of the existing scrollable output, return undefined if at the bottom or element doesn't exist +function findScrolledHeight(container: HTMLElement): number | undefined { + const scrollableElement = container.querySelector('.' + scrollableClass); + if (scrollableElement && scrollableElement.scrollHeight - scrollableElement.scrollTop - scrollableElement.clientHeight > 2) { + // not scrolled to the bottom + return scrollableElement.scrollTop; + } + return undefined; +} + +function scrollingEnabled(output: OutputItem, options: RenderOptions) { + const metadata = output.metadata; + return (typeof metadata === 'object' && metadata + && 'scrollable' in metadata && typeof metadata.scrollable === 'boolean') ? + metadata.scrollable : options.outputScrolling; +} + +// div.cell_container +// div.output_container +// div.output.output-stream <-- outputElement parameter +// div.scrollable? tabindex="0" <-- contentParent +// div output-item-id="{guid}" <-- content from outputItem parameter +function renderStream(outputInfo: OutputItem, outputElement: HTMLElement, error: boolean, ctx: IRichRenderContext): IDisposable { + const disposableStore = createDisposableStore(); + const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); + + outputElement.classList.add('output-stream'); + + const text = outputInfo.text(); + const newContent = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false); + newContent.setAttribute('output-item-id', outputInfo.id); + if (error) { + newContent.classList.add('error'); + } + + const scrollTop = outputScrolling ? findScrolledHeight(outputElement) : undefined; + + const previousOutputParent = getPreviousMatchingContentGroup(outputElement); + // If the previous output item for the same cell was also a stream, append this output to the previous + if (previousOutputParent) { + const existingContent = previousOutputParent.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; + if (existingContent) { + existingContent.replaceWith(newContent); + + } else { + previousOutputParent.appendChild(newContent); + } + previousOutputParent.classList.toggle('scrollbar-visible', previousOutputParent.scrollHeight > previousOutputParent.clientHeight); + previousOutputParent.scrollTop = scrollTop !== undefined ? scrollTop : previousOutputParent.scrollHeight; + } else { + const existingContent = outputElement.querySelector(`[output-item-id="${outputInfo.id}"]`) as HTMLElement | null; + let contentParent = existingContent?.parentElement; + if (existingContent && contentParent) { + existingContent.replaceWith(newContent); + while (newContent.nextSibling) { + // clear out any stale content if we had previously combined streaming outputs into this one + newContent.nextSibling.remove(); + } + } else { + contentParent = document.createElement('div'); + contentParent.appendChild(newContent); + while (outputElement.firstChild) { + outputElement.removeChild(outputElement.firstChild); + } + outputElement.appendChild(contentParent); + } + + contentParent.classList.toggle('scrollable', outputScrolling); + contentParent.classList.toggle('word-wrap', ctx.settings.outputWordWrap); + disposableStore.push(ctx.onDidChangeSettings(e => { + contentParent!.classList.toggle('word-wrap', e.outputWordWrap); + })); + + initializeScroll(contentParent, disposableStore, scrollTop); + } + + return disposableStore; +} + +function renderText(outputInfo: OutputItem, outputElement: HTMLElement, ctx: IRichRenderContext): IDisposable { + const disposableStore = createDisposableStore(); + clearContainer(outputElement); + + const text = outputInfo.text(); + const outputScrolling = scrollingEnabled(outputInfo, ctx.settings); + const content = createOutputContent(outputInfo.id, [text], ctx.settings.lineLimit, outputScrolling, false); + content.classList.add('output-plaintext'); + if (ctx.settings.outputWordWrap) { + content.classList.add('word-wrap'); + } + + content.classList.toggle('scrollable', outputScrolling); + outputElement.appendChild(content); + initializeScroll(content, disposableStore); + + return disposableStore; } export const activate: ActivationFunction = (ctx) => { const disposables = new Map(); const htmlHooks = new Set(); + const jsHooks = new Set(); - const latestContext = ctx as (RendererContext & { readonly settings: { readonly lineLimit: number } }); + const latestContext = ctx as (RendererContext & { readonly settings: RenderOptions; readonly onDidChangeSettings: Event }); const style = document.createElement('style'); style.textContent = ` + #container div.output.remove-padding { + padding-left: 0; + padding-right: 0; + } .output-plaintext, .output-stream, .traceback { + display: inline-block; + width: 100%; line-height: var(--notebook-cell-output-line-height); font-family: var(--notebook-cell-output-font-family); - white-space: pre-wrap; - word-wrap: break-word; - font-size: var(--notebook-cell-output-font-size); user-select: text; -webkit-user-select: text; -ms-user-select: text; cursor: auto; + word-wrap: break-word; + /* text/stream output container should scroll but preserve newline character */ + white-space: pre; } - span.output-stream { - display: inline-block; + /* When wordwrap turned on, force it to pre-wrap */ + #container div.output_container .word-wrap span { + white-space: pre-wrap; + } + #container div.output>div { + padding-left: var(--notebook-output-node-left-padding); + padding-right: var(--notebook-output-node-padding); + box-sizing: border-box; + border-width: 1px; + border-style: solid; + border-color: transparent; + } + #container div.output>div:focus { + outline: 0; + border-color: var(--theme-input-focus-border-color); + } + #container div.output .scrollable { + overflow-y: scroll; + max-height: var(--notebook-cell-output-max-height); + } + #container div.output .scrollable.scrollbar-visible { + border-color: var(--vscode-editorWidget-border); + } + #container div.output .scrollable.scrollbar-visible:focus { + border-color: var(--theme-input-focus-border-color); + } + #container div.truncation-message { + font-style: italic; + font-family: var(--theme-font-family); + padding-top: 4px; + } + #container div.output .scrollable div { + cursor: text; + } + #container div.output .scrollable div a { + cursor: pointer; + } + #container div.output .scrollable.more-above { + box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset } .output-plaintext .code-bold, .output-stream .code-bold, @@ -227,62 +439,74 @@ export const activate: ActivationFunction = (ctx) => { document.body.appendChild(style); return { - renderOutputItem: (outputInfo, element) => { + renderOutputItem: async (outputInfo, element, signal?: AbortSignal) => { + element.classList.add('remove-padding'); switch (outputInfo.mime) { case 'text/html': - case 'image/svg+xml': - { - if (!ctx.workspace.isTrusted) { - return; - } - - renderHTML(outputInfo, element, htmlHooks); + case 'image/svg+xml': { + if (!ctx.workspace.isTrusted) { + return; } - break; - case 'application/javascript': - { - if (!ctx.workspace.isTrusted) { - return; - } - renderJavascript(outputInfo, element); - } + await renderHTML(outputInfo, element, signal!, htmlHooks); break; + } + case 'application/javascript': { + if (!ctx.workspace.isTrusted) { + return; + } + + renderJavascript(outputInfo, element, signal!, jsHooks); + break; + } case 'image/gif': case 'image/png': case 'image/jpeg': case 'image/git': { + disposables.get(outputInfo.id)?.dispose(); const disposable = renderImage(outputInfo, element); disposables.set(outputInfo.id, disposable); } break; case 'application/vnd.code.notebook.error': { - renderError(outputInfo, element, latestContext); + disposables.get(outputInfo.id)?.dispose(); + const disposable = renderError(outputInfo, element, latestContext, ctx.workspace.isTrusted); + disposables.set(outputInfo.id, disposable); } break; case 'application/vnd.code.notebook.stdout': case 'application/x.notebook.stdout': case 'application/x.notebook.stream': { - renderStream(outputInfo, element, false, latestContext); + disposables.get(outputInfo.id)?.dispose(); + const disposable = renderStream(outputInfo, element, false, latestContext); + disposables.set(outputInfo.id, disposable); } break; case 'application/vnd.code.notebook.stderr': case 'application/x.notebook.stderr': { - renderStream(outputInfo, element, true, latestContext); + disposables.get(outputInfo.id)?.dispose(); + const disposable = renderStream(outputInfo, element, true, latestContext); + disposables.set(outputInfo.id, disposable); } break; case 'text/plain': { - renderText(outputInfo, element, latestContext); + disposables.get(outputInfo.id)?.dispose(); + const disposable = renderText(outputInfo, element, latestContext); + disposables.set(outputInfo.id, disposable); } break; default: break; } + if (element.querySelector('div')) { + element.querySelector('div')!.tabIndex = 0; + } + }, disposeOutputItem: (id: string | undefined) => { if (id) { @@ -298,6 +522,14 @@ export const activate: ActivationFunction = (ctx) => { htmlHooks.delete(hook); } }; + }, + experimental_registerJavaScriptRenderingHook: (hook: JavaScriptRenderingHook): IDisposable => { + jsHooks.add(hook); + return { + dispose: () => { + jsHooks.delete(hook); + } + }; } }; }; diff --git a/extensions/notebook-renderers/src/linkify.ts b/extensions/notebook-renderers/src/linkify.ts index 68d0031920..39a6d409ee 100644 --- a/extensions/notebook-renderers/src/linkify.ts +++ b/extensions/notebook-renderers/src/linkify.ts @@ -6,12 +6,12 @@ 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'); -const WIN_ABSOLUTE_PATH = /(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/; -const WIN_RELATIVE_PATH = /(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/; +const WIN_ABSOLUTE_PATH = /(?<=^|\s)(?:[a-zA-Z]:(?:(?:\\|\/)[\w\.-]*)+)/; +const WIN_RELATIVE_PATH = /(?<=^|\s)(?:(?:\~|\.)(?:(?:\\|\/)[\w\.-]*)+)/; const WIN_PATH = new RegExp(`(${WIN_ABSOLUTE_PATH.source}|${WIN_RELATIVE_PATH.source})`); -const POSIX_PATH = /((?:\~|\.)?(?:\/[\w\.-]*)+)/; +const POSIX_PATH = /(?<=^|\s)((?:\~|\.)?(?:\/[\w\.-]*)+)/; const LINE_COLUMN = /(?:\:([\d]+))?(?:\:([\d]+))?/; -const isWindows = navigator.userAgent.indexOf('Windows') >= 0; +const isWindows = (typeof navigator !== 'undefined') ? navigator.userAgent && navigator.userAgent.indexOf('Windows') >= 0 : false; const PATH_LINK_REGEX = new RegExp(`${isWindows ? WIN_PATH.source : POSIX_PATH.source}${LINE_COLUMN.source}`, 'g'); const MAX_LENGTH = 2000; @@ -64,17 +64,9 @@ export class LinkDetector { container.appendChild(document.createTextNode(part.value)); break; case 'web': + case 'path': container.appendChild(this.createWebLink(part.value)); break; - case 'path': { - container.appendChild(document.createTextNode(part.value)); - - // const path = part.captures[0]; - // const lineNumber = part.captures[1] ? Number(part.captures[1]) : 0; - // const columnNumber = part.captures[2] ? Number(part.captures[2]) : 0; - // container.appendChild(this.createPathLink(part.value, path, lineNumber, columnNumber, workspaceFolder)); - break; - } } } catch (e) { container.appendChild(document.createTextNode(part.value)); @@ -85,7 +77,7 @@ export class LinkDetector { private createWebLink(url: string): Node { const link = this.createLink(url); - + link.href = url; return link; } @@ -127,7 +119,7 @@ export class LinkDetector { // return link; // } - private createLink(text: string): HTMLElement { + private createLink(text: string): HTMLAnchorElement { const link = document.createElement('a'); link.textContent = text; return link; diff --git a/extensions/notebook-renderers/src/rendererTypes.ts b/extensions/notebook-renderers/src/rendererTypes.ts new file mode 100644 index 0000000000..6f17bac54c --- /dev/null +++ b/extensions/notebook-renderers/src/rendererTypes.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { OutputItem, RendererContext } from 'vscode-notebook-renderer'; +import { Event } from 'vscode'; + +export interface IDisposable { + dispose(): void; +} + +export interface HtmlRenderingHook { + /** + * Invoked after the output item has been rendered but before it has been appended to the document. + * + * @return A new `HTMLElement` or `undefined` to continue using the provided element. + */ + postRender(outputItem: OutputItem, element: HTMLElement, signal: AbortSignal): HTMLElement | undefined | Promise; +} + +export interface JavaScriptRenderingHook { + /** + * Invoked before the script is evaluated. + * + * @return A new string of JavaScript or `undefined` to continue using the provided string. + */ + preEvaluate(outputItem: OutputItem, element: HTMLElement, script: string, signal: AbortSignal): string | undefined | Promise; +} + +export interface RenderOptions { + readonly lineLimit: number; + readonly outputScrolling: boolean; + readonly outputWordWrap: boolean; +} + +export type IRichRenderContext = RendererContext & { readonly settings: RenderOptions; readonly onDidChangeSettings: Event }; diff --git a/extensions/notebook-renderers/src/test/index.ts b/extensions/notebook-renderers/src/test/index.ts new file mode 100644 index 0000000000..250793bf82 --- /dev/null +++ b/extensions/notebook-renderers/src/test/index.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as testRunner from '../../../../test/integration/electron/testrunner'; + +const options: import('mocha').MochaOptions = { + ui: 'tdd', + color: true, + timeout: 60000 +}; + +// These integration tests is being run in multiple environments (electron, web, remote) +// so we need to set the suite name based on the environment as the suite name is used +// for the test results file name +let suite = ''; +if (process.env.VSCODE_BROWSER) { + suite = `${process.env.VSCODE_BROWSER} Browser Integration notebook output renderer Tests`; +} else if (process.env.REMOTE_VSCODE) { + suite = 'Remote Integration notebook output renderer Tests'; +} else { + suite = 'Integration notebook output renderer Tests'; +} + +if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + options.reporter = 'mocha-multi-reporters'; + options.reporterOptions = { + reporterEnabled: 'spec, mocha-junit-reporter', + mochaJunitReporterReporterOptions: { + testsuitesTitle: `${suite} ${process.platform}`, + mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) + } + }; +} + +testRunner.configure(options); + +export = testRunner; diff --git a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts new file mode 100644 index 0000000000..bc8b88086a --- /dev/null +++ b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts @@ -0,0 +1,297 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { activate } from '..'; +import { OutputItem, RendererApi } from 'vscode-notebook-renderer'; +import { IDisposable, IRichRenderContext, RenderOptions } from '../rendererTypes'; +import { JSDOM } from "jsdom"; + +const dom = new JSDOM(); +global.document = dom.window.document; + +suite('Notebook builtin output renderer', () => { + + const error = { + name: "NameError", + message: "name 'x' is not defined", + stack: "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m" + + "\n\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)" + + "\nCell \u001b[1;32mIn[3], line 1\u001b[0m" + + "\n\u001b[1;32m----> 1\u001b[0m \u001b[39mprint\u001b[39m(x)" + + "\n\n\u001b[1;31mNameError\u001b[0m: name 'x' is not defined" + }; + + const errorMimeType = 'application/vnd.code.notebook.error'; + + const stdoutMimeType = 'application/vnd.code.notebook.stdout'; + const stderrMimeType = 'application/vnd.code.notebook.stderr'; + + const textLikeMimeTypes = [ + stdoutMimeType, + stderrMimeType, + 'text/plain' + ]; + + type optionalRenderOptions = { [k in keyof RenderOptions]?: RenderOptions[k] }; + + type handler = (e: RenderOptions) => any; + + const settingsChangedHandlers: handler[] = []; + function fireSettingsChange(options: optionalRenderOptions) { + settingsChangedHandlers.forEach((handler) => handler(options as RenderOptions)); + } + + function createContext(settings?: optionalRenderOptions): IRichRenderContext { + settingsChangedHandlers.length = 0; + return { + setState(_value: void) { }, + getState() { return undefined; }, + async getRenderer(_id): Promise { return undefined; }, + settings: { + outputWordWrap: true, + outputScrolling: true, + lineLimit: 30, + ...settings + } as RenderOptions, + onDidChangeSettings(listener: handler, _thisArgs?: any, disposables?: IDisposable[]) { + settingsChangedHandlers.push(listener); + + const dispose = () => { + settingsChangedHandlers.splice(settingsChangedHandlers.indexOf(listener), 1); + }; + + disposables?.push({ dispose }); + return { + dispose + }; + }, + workspace: { + isTrusted: true + } + }; + } + + function createElement(elementType: 'div' | 'span', classes: string[]) { + const el = global.document.createElement(elementType); + classes.forEach((c) => el.classList.add(c)); + return el; + } + + // Helper to generate HTML similar to what is passed to the renderer + //
+ //
+ //
+ class OutputHtml { + private readonly cell = createElement('div', ['cell_container']); + private readonly firstOutput: HTMLElement; + + constructor() { + const outputContainer = createElement('div', ['output_container']); + const outputElement = createElement('div', ['output']); + + this.cell.appendChild(outputContainer); + outputContainer.appendChild(outputElement); + + this.firstOutput = outputElement; + } + + public getFirstOuputElement() { + return this.firstOutput; + } + + public appendOutputElement() { + const outputElement = createElement('div', ['output']); + const outputContainer = createElement('div', ['output_container']); + this.cell.appendChild(outputContainer); + outputContainer.appendChild(outputElement); + + return outputElement; + } + } + + function createOutputItem(text: string, mime: string, id: string = '123'): OutputItem { + return { + id: id, + mime: mime, + text() { + return text; + }, + blob() { + return [] as any; + }, + json() { + return '{ }'; + }, + data() { + return [] as any; + }, + metadata: {} + }; + } + + textLikeMimeTypes.forEach((mimeType) => { + test(`Render with wordwrap and scrolling for mimetype ${mimeType}`, async () => { + const context = createContext({ outputWordWrap: true, outputScrolling: true }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const outputItem = createOutputItem('content', mimeType); + await renderer!.renderOutputItem(outputItem, outputElement); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`); + assert.ok(outputElement.classList.contains('remove-padding'), `Padding should be removed for scrollable outputs ${outputElement.classList}`); + assert.ok(inserted.classList.contains('word-wrap') && inserted.classList.contains('scrollable'), + `output content classList should contain word-wrap and scrollable ${inserted.classList}`); + assert.ok(inserted.innerHTML.indexOf('>content -1, `Content was not added to output element: ${outputElement.innerHTML}`); + }); + + test(`Render without wordwrap or scrolling for mimetype ${mimeType}`, async () => { + const context = createContext({ outputWordWrap: false, outputScrolling: false }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const outputItem = createOutputItem('content', mimeType); + await renderer!.renderOutputItem(outputItem, outputElement); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`); + assert.ok(outputElement.classList.contains('remove-padding'), `Padding should be removed for non-scrollable outputs: ${outputElement.classList}`); + assert.ok(!inserted.classList.contains('word-wrap') && !inserted.classList.contains('scrollable'), + `output content classList should not contain word-wrap and scrollable ${inserted.classList}`); + assert.ok(inserted.innerHTML.indexOf('>content -1, `Content was not added to output element: ${outputElement.innerHTML}`); + }); + + test(`Replace content in element for mimetype ${mimeType}`, async () => { + const context = createContext(); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const outputItem = createOutputItem('content', 'text/plain'); + await renderer!.renderOutputItem(outputItem, outputElement); + const outputItem2 = createOutputItem('replaced content', 'text/plain'); + await renderer!.renderOutputItem(outputItem2, outputElement); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted.innerHTML.indexOf('>contentreplaced content { + const context = createContext({ outputWordWrap: true, outputScrolling: true }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const outputItem = createOutputItem(JSON.stringify(error), errorMimeType); + await renderer!.renderOutputItem(outputItem, outputElement); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`); + assert.ok(outputElement.classList.contains('remove-padding'), 'Padding should be removed for scrollable outputs'); + assert.ok(inserted.classList.contains('word-wrap') && inserted.classList.contains('scrollable'), + `output content classList should contain word-wrap and scrollable ${inserted.classList}`); + assert.ok(inserted.innerHTML.indexOf('>: name \'x\' is not defined -1, `Content was not added to output element:\n ${outputElement.innerHTML}`); + }); + + test(`Replace content in element for error output`, async () => { + const context = createContext(); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const outputItem = createOutputItem(JSON.stringify(error), errorMimeType); + await renderer!.renderOutputItem(outputItem, outputElement); + const error2: typeof error = { ...error, message: 'new message', stack: 'replaced content' }; + const outputItem2 = createOutputItem(JSON.stringify(error2), errorMimeType); + await renderer!.renderOutputItem(outputItem2, outputElement); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted.innerHTML.indexOf('>: name \'x\' is not definedreplaced content { + const context = createContext(); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputHtml = new OutputHtml(); + const outputElement = outputHtml.getFirstOuputElement(); + const outputItem1 = createOutputItem('first stream content', stdoutMimeType, '1'); + const outputItem2 = createOutputItem('second stream content', stdoutMimeType, '2'); + const outputItem3 = createOutputItem('third stream content', stderrMimeType, '3'); + await renderer!.renderOutputItem(outputItem1, outputElement); + await renderer!.renderOutputItem(outputItem2, outputHtml.appendOutputElement()); + await renderer!.renderOutputItem(outputItem3, outputHtml.appendOutputElement()); + + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`); + assert.ok(inserted.innerHTML.indexOf('>first stream content -1, `Content was not added to output element: ${outputElement.innerHTML}`); + assert.ok(inserted.innerHTML.indexOf('>second stream content -1, `Content was not added to output element: ${outputElement.innerHTML}`); + assert.ok(inserted.innerHTML.indexOf('>third stream content -1, `Content was not added to output element: ${outputElement.innerHTML}`); + }); + + test(`Multiple adjacent streaming outputs, rerendering the first should erase the rest`, async () => { + const context = createContext(); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputHtml = new OutputHtml(); + const outputElement = outputHtml.getFirstOuputElement(); + const outputItem1 = createOutputItem('first stream content', stdoutMimeType, '1'); + const outputItem2 = createOutputItem('second stream content', stdoutMimeType, '2'); + const outputItem3 = createOutputItem('third stream content', stderrMimeType, '3'); + await renderer!.renderOutputItem(outputItem1, outputElement); + await renderer!.renderOutputItem(outputItem2, outputHtml.appendOutputElement()); + await renderer!.renderOutputItem(outputItem3, outputHtml.appendOutputElement()); + const newOutputItem1 = createOutputItem('replaced content', stderrMimeType, '1'); + await renderer!.renderOutputItem(newOutputItem1, outputElement); + + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted, `nothing appended to output element: ${outputElement.innerHTML}`); + assert.ok(inserted.innerHTML.indexOf('>replaced content -1, `Content was not added to output element: ${outputElement.innerHTML}`); + assert.ok(inserted.innerHTML.indexOf('>first stream contentsecond stream contentthird stream content { + const context = createContext({ outputWordWrap: false, outputScrolling: true }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + const outputItem = createOutputItem('content', stdoutMimeType); + await renderer!.renderOutputItem(outputItem, outputElement); + fireSettingsChange({ outputWordWrap: true, outputScrolling: true }); + + const inserted = outputElement.firstChild as HTMLElement; + assert.ok(inserted.classList.contains('word-wrap') && inserted.classList.contains('scrollable'), + `output content classList should contain word-wrap and scrollable ${inserted.classList}`); + }); + + test(`Settings event change listeners should not grow if output is re-rendered`, async () => { + const context = createContext({ outputWordWrap: false }); + const renderer = await activate(context); + assert.ok(renderer, 'Renderer not created'); + + const outputElement = new OutputHtml().getFirstOuputElement(); + await renderer!.renderOutputItem(createOutputItem('content', stdoutMimeType), outputElement); + const handlerCount = settingsChangedHandlers.length; + await renderer!.renderOutputItem(createOutputItem('content', stdoutMimeType), outputElement); + + assert.equal(settingsChangedHandlers.length, handlerCount); + }); +}); + diff --git a/extensions/notebook-renderers/src/textHelper.ts b/extensions/notebook-renderers/src/textHelper.ts index f4072e918d..8b2fbf0fa1 100644 --- a/extensions/notebook-renderers/src/textHelper.ts +++ b/extensions/notebook-renderers/src/textHelper.ts @@ -5,47 +5,108 @@ import { handleANSIOutput } from './ansi'; +export const scrollableClass = 'scrollable'; + +/** + * Output is Truncated. View as a [scrollable element] or open in a [text editor]. Adjust cell output [settings...] + */ function generateViewMoreElement(outputId: string) { - const container = document.createElement('span'); + + const container = document.createElement('div'); + container.classList.add('truncation-message'); const first = document.createElement('span'); - first.textContent = 'Output exceeds the '; - const second = document.createElement('a'); - second.textContent = 'size limit'; - second.href = `command:workbench.action.openSettings?["notebook.output.textLineLimit"]`; - const third = document.createElement('span'); - third.textContent = '. Open the full output data'; - const forth = document.createElement('a'); - forth.textContent = ' in a text editor'; - forth.href = `command:workbench.action.openLargeOutput?${outputId}`; + first.textContent = 'Output is truncated. View as a '; container.appendChild(first); + + const viewAsScrollableLink = document.createElement('a'); + viewAsScrollableLink.textContent = 'scrollable element'; + viewAsScrollableLink.href = `command:cellOutput.enableScrolling?${outputId}`; + viewAsScrollableLink.ariaLabel = 'enable scrollable output'; + container.appendChild(viewAsScrollableLink); + + const second = document.createElement('span'); + second.textContent = ' or open in a '; container.appendChild(second); + + const openInTextEditorLink = document.createElement('a'); + openInTextEditorLink.textContent = 'text editor'; + openInTextEditorLink.href = `command:workbench.action.openLargeOutput?${outputId}`; + openInTextEditorLink.ariaLabel = 'open output in text editor'; + container.appendChild(openInTextEditorLink); + + const third = document.createElement('span'); + third.textContent = '. Adjust cell output '; container.appendChild(third); - container.appendChild(forth); + + const layoutSettingsLink = document.createElement('a'); + layoutSettingsLink.textContent = 'settings'; + layoutSettingsLink.href = `command:workbench.action.openSettings?%5B%22%40tag%3AnotebookOutputLayout%22%5D`; + layoutSettingsLink.ariaLabel = 'notebook output settings'; + container.appendChild(layoutSettingsLink); + + const fourth = document.createElement('span'); + fourth.textContent = '...'; + container.appendChild(fourth); + return container; } -export function truncatedArrayOfString(id: string, outputs: string[], linesLimit: number, container: HTMLElement) { - let buffer = outputs.join('\n').split(/\r|\n|\r\n/g); - let lineCount = buffer.length; +function generateNestedViewAllElement(outputId: string) { + const container = document.createElement('div'); - if (lineCount < linesLimit) { - const spanElement = handleANSIOutput(buffer.slice(0, linesLimit).join('\n')); + const link = document.createElement('a'); + link.textContent = '...'; + link.href = `command:workbench.action.openLargeOutput?${outputId}`; + link.ariaLabel = 'Open full output in text editor'; + link.title = 'Open full output in text editor'; + link.style.setProperty('text-decoration', 'none'); + container.appendChild(link); + + return container; +} + +function truncatedArrayOfString(id: string, buffer: string[], linesLimit: number, trustHtml: boolean) { + const container = document.createElement('div'); + const lineCount = buffer.length; + + if (lineCount <= linesLimit) { + const spanElement = handleANSIOutput(buffer.join('\n'), trustHtml); container.appendChild(spanElement); - return; + return container; } + container.appendChild(handleANSIOutput(buffer.slice(0, linesLimit - 5).join('\n'), trustHtml)); + + // truncated piece + const elipses = document.createElement('div'); + elipses.innerText = '...'; + container.appendChild(elipses); + + container.appendChild(handleANSIOutput(buffer.slice(lineCount - 5).join('\n'), trustHtml)); + container.appendChild(generateViewMoreElement(id)); - const div = document.createElement('div'); - container.appendChild(div); - div.appendChild(handleANSIOutput(buffer.slice(0, linesLimit - 5).join('\n'))); - - // view more ... - const viewMoreSpan = document.createElement('span'); - viewMoreSpan.innerText = '...'; - container.appendChild(viewMoreSpan); - - const div2 = document.createElement('div'); - container.appendChild(div2); - div2.appendChild(handleANSIOutput(buffer.slice(lineCount - 5).join('\n'))); + return container; +} + +function scrollableArrayOfString(id: string, buffer: string[], trustHtml: boolean) { + const element = document.createElement('div'); + if (buffer.length > 5000) { + element.appendChild(generateNestedViewAllElement(id)); + } + + element.appendChild(handleANSIOutput(buffer.slice(-5000).join('\n'), trustHtml)); + + return element; +} + +export function createOutputContent(id: string, outputs: string[], linesLimit: number, scrollable: boolean, trustHtml: boolean): HTMLElement { + + const buffer = outputs.join('\n').split(/\r\n|\r|\n/g); + + if (scrollable) { + return scrollableArrayOfString(id, buffer, trustHtml); + } else { + return truncatedArrayOfString(id, buffer, linesLimit, trustHtml); + } } diff --git a/extensions/notebook-renderers/tsconfig.json b/extensions/notebook-renderers/tsconfig.json index 2032bf87b0..3472d5bbaa 100644 --- a/extensions/notebook-renderers/tsconfig.json +++ b/extensions/notebook-renderers/tsconfig.json @@ -8,8 +8,6 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookEditor.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts", + "../../src/vscode-dts/vscode.d.ts" ] } diff --git a/extensions/notebook-renderers/yarn.lock b/extensions/notebook-renderers/yarn.lock index 06ac4e135c..ba3ff6b48d 100644 --- a/extensions/notebook-renderers/yarn.lock +++ b/extensions/notebook-renderers/yarn.lock @@ -2,7 +2,422 @@ # yarn lockfile v1 +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@types/jsdom@^21.1.0": + version "21.1.0" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-21.1.0.tgz#219f15e3370da3f85d18fe02ae86bda7ff66104a" + integrity sha512-leWreJOdnuIxq9Y70tBVm/bvTuh31DSlF/r4l7Cfi4uhVQqLHD0Q4v301GMisEMwwbMgF7ZKxuZ+Jbd4NcdmRw== + dependencies: + "@types/node" "*" + "@types/tough-cookie" "*" + parse5 "^7.0.0" + +"@types/node@*": + version "18.15.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.3.tgz#f0b991c32cfc6a4e7f3399d6cb4b8cf9a0315014" + integrity sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw== + +"@types/tough-cookie@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" + integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== + "@types/vscode-notebook-renderer@^1.60.0": version "1.60.0" resolved "https://registry.yarnpkg.com/@types/vscode-notebook-renderer/-/vscode-notebook-renderer-1.60.0.tgz#8a67d561f48ddf46a95dfa9f712a79c72c7b8f7a" integrity sha512-u7TD2uuEZTVuitx0iijOJdKI0JLiQP6PsSBSRy2XmHXUOXcp5p1S56NrjOEDoF+PIHd3NL3eO6KTRSf5nukDqQ== + +abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +acorn-globals@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" + integrity sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q== + dependencies: + acorn "^8.1.0" + acorn-walk "^8.0.2" + +acorn-walk@^8.0.2: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.1.0, acorn@^8.8.2: + version "8.8.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" + integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +cssstyle@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-3.0.0.tgz#17ca9c87d26eac764bb8cfd00583cff21ce0277a" + integrity sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg== + dependencies: + rrweb-cssom "^0.6.0" + +data-urls@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-4.0.0.tgz#333a454eca6f9a5b7b0f1013ff89074c3f522dd4" + integrity sha512-/mMTei/JXPqvFqQtfyTowxmJVwr2PVAeCcDxyFf6LhoOu/09TX2OX3kb2wzi4DMXcfj4OItwDOnhl5oziPnT6g== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^12.0.0" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + +deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + +entities@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" + integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +jsdom@^21.1.1: + version "21.1.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-21.1.1.tgz#ab796361e3f6c01bcfaeda1fea3c06197ac9d8ae" + integrity sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w== + dependencies: + abab "^2.0.6" + acorn "^8.8.2" + acorn-globals "^7.0.0" + cssstyle "^3.0.0" + data-urls "^4.0.0" + decimal.js "^10.4.3" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.1" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.2" + parse5 "^7.1.2" + rrweb-cssom "^0.6.0" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^4.1.2" + w3c-xmlserializer "^4.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^12.0.1" + ws "^8.13.0" + xml-name-validator "^4.0.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +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== + +nwsapi@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0" + integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw== + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +parse5@^7.0.0, parse5@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" + integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw== + dependencies: + entities "^4.4.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +punycode@^2.1.1, punycode@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + +querystringify@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +rrweb-cssom@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz#ed298055b97cbddcdeb278f904857629dec5e0e1" + integrity sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw== + +"safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tough-cookie@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" + integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.2.0" + url-parse "^1.5.3" + +tr46@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-4.1.1.tgz#281a758dcc82aeb4fe38c7dfe4d11a395aac8469" + integrity sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw== + dependencies: + punycode "^2.3.0" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +universalify@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0" + integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg== + +url-parse@^1.5.3: + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== + dependencies: + querystringify "^2.1.1" + requires-port "^1.0.0" + +w3c-xmlserializer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz#aebdc84920d806222936e3cdce408e32488a3073" + integrity sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw== + dependencies: + xml-name-validator "^4.0.0" + +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^12.0.0, whatwg-url@^12.0.1: + version "12.0.1" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-12.0.1.tgz#fd7bcc71192e7c3a2a97b9a8d6b094853ed8773c" + integrity sha512-Ed/LrqB8EPlGxjS+TrsXcpUond1mhccS3pchLhzSgPCnTimUCKj3IZE75pAs5m6heB2U2TMerKFUXheyHY+VDQ== + dependencies: + tr46 "^4.1.1" + webidl-conversions "^7.0.0" + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +ws@^8.13.0: + version "8.13.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" + integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== diff --git a/extensions/notebook/package.json b/extensions/notebook/package.json index 147cc59f7c..45af9602ba 100644 --- a/extensions/notebook/package.json +++ b/extensions/notebook/package.json @@ -19,9 +19,6 @@ "supported": true } }, - "enabledApiProposals": [ - "notebookEditor" - ], "contributes": { "configuration": { "type": "object", diff --git a/extensions/notebook/resources/dark/new_notebook_inverse.svg b/extensions/notebook/resources/dark/new_notebook_inverse.svg old mode 100755 new mode 100644 diff --git a/extensions/notebook/resources/light/new_notebook.svg b/extensions/notebook/resources/light/new_notebook.svg old mode 100755 new mode 100644 diff --git a/extensions/notebook/src/typings/refs.d.ts b/extensions/notebook/src/typings/refs.d.ts index 487f690eef..420c12b6ad 100644 --- a/extensions/notebook/src/typings/refs.d.ts +++ b/extensions/notebook/src/typings/refs.d.ts @@ -6,5 +6,4 @@ /// /// /// -/// /// diff --git a/extensions/package.json b/extensions/package.json index 31b83174c2..8ab9387529 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,14 +4,14 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^4.8.0-dev.20220614" + "typescript": "5.1.3" }, "scripts": { "postinstall": "node ./postinstall.mjs" }, "devDependencies": { - "@parcel/watcher": "2.0.5", - "esbuild": "^0.11.12", + "@parcel/watcher": "2.1.0", + "esbuild": "0.17.14", "vscode-grammar-updater": "^1.1.0" } } diff --git a/extensions/postinstall.mjs b/extensions/postinstall.mjs index 1dee3ef643..5e44a3058d 100644 --- a/extensions/postinstall.mjs +++ b/extensions/postinstall.mjs @@ -26,7 +26,6 @@ function processRoot() { function processLib() { const toDelete = new Set([ 'tsc.js', - 'tsserverlibrary.js', 'typescriptServices.js', ]); diff --git a/extensions/profiler/package.json b/extensions/profiler/package.json index 8963b19472..9292e846db 100644 --- a/extensions/profiler/package.json +++ b/extensions/profiler/package.json @@ -8,7 +8,7 @@ "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt", "icon": "images/extension.png", "engines": { - "vscode": "0.10.x" + "vscode": "0.10.0" }, "activationEvents": [ "*" diff --git a/extensions/python/cgmanifest.json b/extensions/python/cgmanifest.json index 37a21b2de5..ace7056c99 100644 --- a/extensions/python/cgmanifest.json +++ b/extensions/python/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "MagicStack/MagicPython", "repositoryUrl": "https://github.com/MagicStack/MagicPython", - "commitHash": "c9b3409deb69acec31bbf7913830e93a046b30cc" + "commitHash": "7d0f2b22a5ad8fccbd7341bc7b7a715169283044" } }, "license": "MIT", diff --git a/extensions/python/package.json b/extensions/python/package.json index 6134d05a21..332ff083c2 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -28,7 +28,6 @@ "py" ], "filenames": [ - "Snakefile", "SConstruct", "SConscript" ], @@ -46,7 +45,12 @@ "scopeName": "source.regexp.python", "path": "./syntaxes/MagicRegExp.tmLanguage.json" } - ] + ], + "configurationDefaults": { + "[python]": { + "diffEditor.ignoreTrimWhitespace": false + } + } }, "scripts": { "update-grammar": "node ../node_modules/vscode-grammar-updater/bin MagicStack/MagicPython grammars/MagicPython.tmLanguage ./syntaxes/MagicPython.tmLanguage.json grammars/MagicRegExp.tmLanguage ./syntaxes/MagicRegExp.tmLanguage.json" diff --git a/extensions/python/syntaxes/MagicPython.tmLanguage.json b/extensions/python/syntaxes/MagicPython.tmLanguage.json index 0df9076dfc..91a2c8e756 100644 --- a/extensions/python/syntaxes/MagicPython.tmLanguage.json +++ b/extensions/python/syntaxes/MagicPython.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/MagicStack/MagicPython/commit/b2b4f4ae7b4e6284e80bda8080106b93bd588f9e", + "version": "https://github.com/MagicStack/MagicPython/commit/7d0f2b22a5ad8fccbd7341bc7b7a715169283044", "name": "MagicPython", "scopeName": "source.python", "patterns": [ @@ -270,6 +270,14 @@ { "name": "storage.type.class.python", "match": "\\b(?>= | //= | \\*\\*=\n | \\+= | -= | /= | @=\n | \\*= | %= | ~= | \\^= | &= | \\|=\n | =(?!=)\n" }, "operator": { - "match": "(?x)\n \\b(?> | & | \\| | \\^ | ~) (?# 3)\n\n | (\\*\\* | \\* | \\+ | - | % | // | / | @) (?# 4)\n\n | (!= | == | >= | <= | < | >) (?# 5)\n", + "match": "(?x)\n \\b(?> | & | \\| | \\^ | ~) (?# 3)\n\n | (\\*\\* | \\* | \\+ | - | % | // | / | @) (?# 4)\n\n | (!= | == | >= | <= | < | >) (?# 5)\n\n | (:=) (?# 6)\n", "captures": { "1": { "name": "keyword.operator.logical.python" @@ -563,6 +571,9 @@ }, "5": { "name": "keyword.operator.comparison.python" + }, + "6": { + "name": "keyword.operator.assignment.python" } } }, @@ -687,18 +698,6 @@ }, { "include": "#regexp-double-one-line" - }, - { - "include": "#fregexp-single-three-line" - }, - { - "include": "#fregexp-double-three-line" - }, - { - "include": "#fregexp-single-one-line" - }, - { - "include": "#fregexp-double-one-line" } ] }, @@ -1151,6 +1150,10 @@ }, "contentName": "meta.function.lambda.parameters.python", "patterns": [ + { + "name": "keyword.operator.positional.parameter.python", + "match": "/" + }, { "name": "keyword.operator.unpacking.parameter.python", "match": "(\\*\\*|\\*)" @@ -1305,6 +1308,10 @@ } }, "patterns": [ + { + "name": "keyword.operator.positional.parameter.python", + "match": "/" + }, { "name": "keyword.operator.unpacking.parameter.python", "match": "(\\*\\*|\\*)" @@ -1702,7 +1709,7 @@ "patterns": [ { "name": "support.function.builtin.python", - "match": "(?x)\n (?)\n", - "end": "(\\)|(?=\\'))|((?=(?)\n", - "end": "(\\)|(?=\\'\\'\\'))", - "beginCaptures": { - "1": { - "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.begin.regexp" - }, - "2": { - "name": "entity.name.tag.named.group.regexp" - } - }, - "endCaptures": { - "1": { - "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.end.regexp" - }, - "2": { - "name": "invalid.illegal.newline.python" - } - }, - "patterns": [ - { - "include": "#single-three-fregexp-expression" - }, - { - "include": "#comments-string-single-three" - } - ] - }, - "single-three-fregexp-lookahead": { - "begin": "(\\()\\?=", - "end": "(\\)|(?=\\'\\'\\'))", - "beginCaptures": { - "0": { - "name": "keyword.operator.lookahead.regexp" - }, - "1": { - "name": "punctuation.parenthesis.lookahead.begin.regexp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.operator.lookahead.regexp punctuation.parenthesis.lookahead.end.regexp" - }, - "2": { - "name": "invalid.illegal.newline.python" - } - }, - "patterns": [ - { - "include": "#single-three-fregexp-expression" - }, - { - "include": "#comments-string-single-three" - } - ] - }, - "single-three-fregexp-lookahead-negative": { - "begin": "(\\()\\?!", - "end": "(\\)|(?=\\'\\'\\'))", - "beginCaptures": { - "0": { - "name": "keyword.operator.lookahead.negative.regexp" - }, - "1": { - "name": "punctuation.parenthesis.lookahead.begin.regexp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.operator.lookahead.negative.regexp punctuation.parenthesis.lookahead.end.regexp" - }, - "2": { - "name": "invalid.illegal.newline.python" - } - }, - "patterns": [ - { - "include": "#single-three-fregexp-expression" - }, - { - "include": "#comments-string-single-three" - } - ] - }, - "single-three-fregexp-lookbehind": { - "begin": "(\\()\\?<=", - "end": "(\\)|(?=\\'\\'\\'))", - "beginCaptures": { - "0": { - "name": "keyword.operator.lookbehind.regexp" - }, - "1": { - "name": "punctuation.parenthesis.lookbehind.begin.regexp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.operator.lookbehind.regexp punctuation.parenthesis.lookbehind.end.regexp" - }, - "2": { - "name": "invalid.illegal.newline.python" - } - }, - "patterns": [ - { - "include": "#single-three-fregexp-expression" - }, - { - "include": "#comments-string-single-three" - } - ] - }, - "single-three-fregexp-lookbehind-negative": { - "begin": "(\\()\\?)\n", - "end": "(\\)|(?=\"))|((?=(?)\n", - "end": "(\\)|(?=\"\"\"))", - "beginCaptures": { - "1": { - "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.begin.regexp" - }, - "2": { - "name": "entity.name.tag.named.group.regexp" - } - }, - "endCaptures": { - "1": { - "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.end.regexp" - }, - "2": { - "name": "invalid.illegal.newline.python" - } - }, - "patterns": [ - { - "include": "#double-three-fregexp-expression" - }, - { - "include": "#comments-string-double-three" - } - ] - }, - "double-three-fregexp-lookahead": { - "begin": "(\\()\\?=", - "end": "(\\)|(?=\"\"\"))", - "beginCaptures": { - "0": { - "name": "keyword.operator.lookahead.regexp" - }, - "1": { - "name": "punctuation.parenthesis.lookahead.begin.regexp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.operator.lookahead.regexp punctuation.parenthesis.lookahead.end.regexp" - }, - "2": { - "name": "invalid.illegal.newline.python" - } - }, - "patterns": [ - { - "include": "#double-three-fregexp-expression" - }, - { - "include": "#comments-string-double-three" - } - ] - }, - "double-three-fregexp-lookahead-negative": { - "begin": "(\\()\\?!", - "end": "(\\)|(?=\"\"\"))", - "beginCaptures": { - "0": { - "name": "keyword.operator.lookahead.negative.regexp" - }, - "1": { - "name": "punctuation.parenthesis.lookahead.begin.regexp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.operator.lookahead.negative.regexp punctuation.parenthesis.lookahead.end.regexp" - }, - "2": { - "name": "invalid.illegal.newline.python" - } - }, - "patterns": [ - { - "include": "#double-three-fregexp-expression" - }, - { - "include": "#comments-string-double-three" - } - ] - }, - "double-three-fregexp-lookbehind": { - "begin": "(\\()\\?<=", - "end": "(\\)|(?=\"\"\"))", - "beginCaptures": { - "0": { - "name": "keyword.operator.lookbehind.regexp" - }, - "1": { - "name": "punctuation.parenthesis.lookbehind.begin.regexp" - } - }, - "endCaptures": { - "1": { - "name": "keyword.operator.lookbehind.regexp punctuation.parenthesis.lookbehind.end.regexp" - }, - "2": { - "name": "invalid.illegal.newline.python" - } - }, - "patterns": [ - { - "include": "#double-three-fregexp-expression" - }, - { - "include": "#comments-string-double-three" - } - ] - }, - "double-three-fregexp-lookbehind-negative": { - "begin": "(\\()\\?=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )(?=})\n", + "name": "storage.type.format.python", + "match": "(=?![rsa])(?=})" + }, + { + "match": "(?x)\n ( (?: =?) (?: ![rsa])? )\n ( : \\w? [<>=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )(?=})\n", "captures": { "1": { "name": "storage.type.format.python" @@ -5061,7 +3938,7 @@ ] }, "fstring-terminator-single-tail": { - "begin": "(![rsa])?(:)(?=.*?{)", + "begin": "((?:=?)(?:![rsa])?)(:)(?=.*?{)", "end": "(?=})|(?=\\n)", "beginCaptures": { "1": { @@ -5190,7 +4067,7 @@ }, "fstring-raw-quoted-multi-line": { "name": "meta.fstring.python", - "begin": "(\\b(?:[R][fF]|[fF][R]))('''|\"\"\")", + "begin": "(\\b(?:[rR][fF]|[fF][rR]))('''|\"\"\")", "end": "(\\2)", "beginCaptures": { "1": { @@ -5258,10 +4135,14 @@ "patterns": [ { "name": "storage.type.format.python", - "match": "(![rsa])(?=})" + "match": "(=(![rsa])?)(?=})" }, { - "match": "(?x)\n (![rsa])?\n ( : \\w? [<>=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )(?=})\n", + "name": "storage.type.format.python", + "match": "(=?![rsa])(?=})" + }, + { + "match": "(?x)\n ( (?: =?) (?: ![rsa])? )\n ( : \\w? [<>=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )(?=})\n", "captures": { "1": { "name": "storage.type.format.python" @@ -5277,7 +4158,7 @@ ] }, "fstring-terminator-multi-tail": { - "begin": "(![rsa])?(:)(?=.*?{)", + "begin": "((?:=?)(?:![rsa])?)(:)(?=.*?{)", "end": "(?=})", "beginCaptures": { "1": { diff --git a/extensions/python/test/colorize-fixtures/test-freeze-56377.py b/extensions/python/test/colorize-fixtures/test-freeze-56377.py deleted file mode 100644 index 62c25e4685..0000000000 --- a/extensions/python/test/colorize-fixtures/test-freeze-56377.py +++ /dev/null @@ -1,4 +0,0 @@ -record = { - "headers": {k: str(v) for k, v in self.request.META.items() if k.startswith('HTTP_')} -} -cmd = "git-clang-format --style=\"{{BasedOnStyle: Google, ColumnLimit: 100, IndentWidth: 2, " \ "AlignConsecutiveAssignments: true}}\" {COMMIT_SHA} -- ./**/*.proto > {OUTPUT}".format( \ No newline at end of file diff --git a/extensions/python/test/colorize-fixtures/test.py b/extensions/python/test/colorize-fixtures/test.py deleted file mode 100644 index c92f2c70a8..0000000000 --- a/extensions/python/test/colorize-fixtures/test.py +++ /dev/null @@ -1,97 +0,0 @@ -from banana import * - -class Monkey: - # Bananas the monkey can eat. - capacity = 10 - def eat(self, N): - '''Make the monkey eat N bananas!''' - capacity = capacity - N*banana.size - - def feeding_frenzy(self): - eat(9.25) - return "Yum yum" - - def some_func(a: - lambda x=None: - {key: val - for key, val in - (x if x is not None else []) - }=42): - pass - -if 1900 < year < 2100 and 1 <= month <= 12 \ - and 1 <= day <= 31 and 0 <= hour < 24 \ - and 0 <= minute < 60 and 0 <= second < 60: # Looks like a valid date - return 1 - -def firstn(g, n): - for i in range(n): - yield g.next() - -reduce(lambda x,y: x+y, [47,11,42,13]) -woerter = {"house" : "Haus", "cat":"Katze", "black":"schwarz"} - -mydictionary = { - 'foo': 23, #comment - 'bar': "hello" #sqadsad -} - -def steuern(einkommen): - """Berechnung der zu zahlenden Steuern fuer ein zu versteuerndes Einkommen von x""" - if einkommen <= 8004: - steuer = 0 - elif einkommen <= 13469: - y = (einkommen -8004.0)/10000.0 - steuer = (912.17 * y + 1400)*y - else: - steuer = einkommen * 0.44 - 15694 - return steuer - -def beliebig(x, y, *mehr): - print "x=", x, ", x=", y - print "mehr: ", mehr - -class Memoize: - def __init__(self, fn): - self.fn = fn - self.memo = {} - def __call__(self, *args): - if args not in self.memo: - self.memo[args] = self.fn(*args) - return self.memo[args] - -res = re.search(r"([0-9-]*)\s*([A-Za-z]+),\s+(.*)", i) - -while True: - try: - n = raw_input("Number: ") - n = int(n) - break - except ValueError: - print("Not a number") - -async with EXPR as VAR: - BLOCK - -# Comments in dictionary items should be colorized accordingly -my_dictionary = { - 'foo':23, # this should be colorized as comment - 'bar':"foobar" #this should be colorized as comment -} - -# test raw strings -text = r""" -interval ``[1,2)`` leads to -""" -highlight_error = True - -# highlight doctests -r'''Module docstring - - Some text followed by code sample: - >>> for a in foo(2, b=1, - ... c=3): - ... print(a) - 0 - 1 -''' diff --git a/extensions/python/test/colorize-results/test-freeze-56377_py.json b/extensions/python/test/colorize-results/test-freeze-56377_py.json deleted file mode 100644 index 3eb0f85a2a..0000000000 --- a/extensions/python/test/colorize-results/test-freeze-56377_py.json +++ /dev/null @@ -1,651 +0,0 @@ -[ - { - "c": "record ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "{", - "t": "source.python punctuation.definition.dict.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "headers", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "{", - "t": "source.python punctuation.definition.dict.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "k", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "str", - "t": "source.python meta.function-call.python support.type.python", - "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": "(", - "t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "v", - "t": "source.python meta.function-call.python meta.function-call.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "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.python keyword.control.flow.python", - "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": " k", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " v ", - "t": "source.python", - "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.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python variable.language.special.self.python", - "r": { - "dark_plus": "variable.language: #569CD6", - "light_plus": "variable.language: #0000FF", - "dark_vs": "variable.language: #569CD6", - "light_vs": "variable.language: #0000FF", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "request", - "t": "source.python meta.member.access.python meta.attribute.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "META", - "t": "source.python meta.member.access.python constant.other.caps.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "items", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "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.python keyword.control.flow.python", - "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": " k", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "startswith", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "'", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "HTTP_", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "'", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ")", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "}", - "t": "source.python punctuation.definition.dict.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "}", - "t": "source.python punctuation.definition.dict.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "cmd ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "git-clang-format --style=", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\\\"", - "t": "source.python string.quoted.single.python constant.character.escape.python", - "r": { - "dark_plus": "constant.character.escape: #D7BA7D", - "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "constant.character: #569CD6" - } - }, - { - "c": "{{", - "t": "source.python string.quoted.single.python meta.format.brace.python constant.character.format.placeholder.other.python", - "r": { - "dark_plus": "constant.character: #569CD6", - "light_plus": "constant.character: #0000FF", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "constant.character: #569CD6" - } - }, - { - "c": "BasedOnStyle: Google, ColumnLimit: 100, IndentWidth: 2, ", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\\", - "t": "source.python punctuation.separator.continuation.line.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"AlignConsecutiveAssignments: true}}\\\" {COMMIT_SHA} -- ./**/*.proto > {OUTPUT}\".format(", - "t": "source.python invalid.illegal.line.continuation.python", - "r": { - "dark_plus": "invalid: #F44747", - "light_plus": "invalid: #CD3131", - "dark_vs": "invalid: #F44747", - "light_vs": "invalid: #CD3131", - "hc_black": "invalid: #F44747" - } - } -] \ No newline at end of file diff --git a/extensions/python/test/colorize-results/test_py.json b/extensions/python/test/colorize-results/test_py.json deleted file mode 100644 index c0a1445364..0000000000 --- a/extensions/python/test/colorize-results/test_py.json +++ /dev/null @@ -1,6800 +0,0 @@ -[ - { - "c": "from", - "t": "source.python keyword.control.import.python", - "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": " banana ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "import", - "t": "source.python keyword.control.import.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "*", - "t": "source.python keyword.operator.arithmetic.python", - "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": "class", - "t": "source.python meta.class.python storage.type.class.python", - "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.python meta.class.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "Monkey", - "t": "source.python meta.class.python entity.name.type.class.python", - "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.python meta.class.python punctuation.section.class.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "#", - "t": "source.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " Bananas the monkey can eat.", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "\tcapacity ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "10", - "t": "source.python constant.numeric.dec.python", - "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", - "t": "source.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "def", - "t": "source.python meta.function.python storage.type.function.python", - "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.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "eat", - "t": "source.python meta.function.python entity.name.function.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python variable.parameter.function.language.special.self.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ",", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "N", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ")", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python punctuation.section.function.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "'''", - "t": "source.python string.quoted.docstring.multi.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "Make the monkey eat N bananas!", - "t": "source.python string.quoted.docstring.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "'''", - "t": "source.python string.quoted.docstring.multi.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\t\tcapacity ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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": " capacity ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "-", - "t": "source.python keyword.operator.arithmetic.python", - "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": " N", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "*", - "t": "source.python keyword.operator.arithmetic.python", - "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": "banana", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "size", - "t": "source.python meta.member.access.python meta.attribute.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t", - "t": "source.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "def", - "t": "source.python meta.function.python storage.type.function.python", - "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.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "feeding_frenzy", - "t": "source.python meta.function.python entity.name.function.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python variable.parameter.function.language.special.self.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ")", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python punctuation.section.function.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "eat", - "t": "source.python meta.function-call.python meta.function-call.generic.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "9.25", - "t": "source.python meta.function-call.python meta.function-call.arguments.python constant.numeric.float.python", - "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.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t", - "t": "source.python", - "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.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "Yum yum", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\t", - "t": "source.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "def", - "t": "source.python meta.function.python storage.type.function.python", - "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.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "some_func", - "t": "source.python meta.function.python entity.name.function.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.begin.python", - "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.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ":", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.annotation.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t\t\t\t", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "lambda", - "t": "source.python meta.function.python meta.function.parameters.python meta.lambda-function.python storage.type.function.lambda.python", - "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.python meta.function.python meta.function.parameters.python meta.lambda-function.python meta.function.lambda.parameters.python", - "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.python meta.function.python meta.function.parameters.python meta.lambda-function.python meta.function.lambda.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": "=", - "t": "source.python meta.function.python meta.function.parameters.python meta.lambda-function.python meta.function.lambda.parameters.python keyword.operator.python", - "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": "None", - "t": "source.python meta.function.python meta.function.parameters.python meta.lambda-function.python meta.function.lambda.parameters.python constant.language.python", - "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.python meta.function.python meta.function.parameters.python meta.lambda-function.python punctuation.section.function.lambda.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t\t\t\t", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "{", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.dict.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "key", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " val", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t\t\t\t\t", - "t": "source.python meta.function.python meta.function.parameters.python", - "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.python meta.function.python meta.function.parameters.python keyword.control.flow.python", - "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": " key", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " val ", - "t": "source.python meta.function.python meta.function.parameters.python", - "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.python meta.function.python meta.function.parameters.python keyword.control.flow.python", - "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\t\t\t\t\t\t", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.parenthesis.begin.python", - "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.python meta.function.python meta.function.parameters.python", - "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.python meta.function.python meta.function.parameters.python keyword.control.flow.python", - "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": " x ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "is", - "t": "source.python meta.function.python meta.function.parameters.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "not", - "t": "source.python meta.function.python meta.function.parameters.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "None", - "t": "source.python meta.function.python meta.function.parameters.python constant.language.python", - "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.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "else", - "t": "source.python meta.function.python meta.function.parameters.python keyword.control.flow.python", - "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.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "[", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.list.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "]", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.list.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.parenthesis.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t\t\t\t", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "}", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.dict.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python meta.function.python meta.function.parameters.python keyword.operator.assignment.python", - "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": "42", - "t": "source.python meta.function.python meta.function.parameters.python constant.numeric.dec.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python punctuation.section.function.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "pass", - "t": "source.python keyword.control.flow.python", - "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": "if", - "t": "source.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "1900", - "t": "source.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.python keyword.operator.comparison.python", - "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": " year ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.python keyword.operator.comparison.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "2100", - "t": "source.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "and", - "t": "source.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python", - "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.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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": " month ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "12", - "t": "source.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\\", - "t": "source.python punctuation.separator.continuation.line.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "and", - "t": "source.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python", - "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.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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": " day ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "31", - "t": "source.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "and", - "t": "source.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python", - "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.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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": " hour ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.python keyword.operator.comparison.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "24", - "t": "source.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\\", - "t": "source.python punctuation.separator.continuation.line.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "and", - "t": "source.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python", - "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.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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": " minute ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.python keyword.operator.comparison.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "60", - "t": "source.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "and", - "t": "source.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python", - "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.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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": " second ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<", - "t": "source.python keyword.operator.comparison.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "60", - "t": "source.python constant.numeric.dec.python", - "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.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "#", - "t": "source.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " Looks like a valid date", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " ", - "t": "source.python", - "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.python keyword.control.flow.python", - "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.python", - "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.python constant.numeric.dec.python", - "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": "def", - "t": "source.python meta.function.python storage.type.function.python", - "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.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "firstn", - "t": "source.python meta.function.python entity.name.function.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "g", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ",", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "n", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ")", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python punctuation.section.function.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t", - "t": "source.python", - "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.python keyword.control.flow.python", - "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": " i ", - "t": "source.python", - "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.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "range", - "t": "source.python meta.function-call.python support.function.builtin.python", - "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.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "n", - "t": "source.python meta.function-call.python meta.function-call.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\t\t", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "yield", - "t": "source.python keyword.control.flow.python", - "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": " g", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "next", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "reduce", - "t": "source.python meta.function-call.python variable.legacy.builtin.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": "(", - "t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "lambda", - "t": "source.python meta.function-call.python meta.function-call.arguments.python meta.lambda-function.python storage.type.function.lambda.python", - "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.python meta.function-call.python meta.function-call.arguments.python meta.lambda-function.python meta.function.lambda.parameters.python", - "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.python meta.function-call.python meta.function-call.arguments.python meta.lambda-function.python meta.function.lambda.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ",", - "t": "source.python meta.function-call.python meta.function-call.arguments.python meta.lambda-function.python meta.function.lambda.parameters.python punctuation.separator.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "y", - "t": "source.python meta.function-call.python meta.function-call.arguments.python meta.lambda-function.python meta.function.lambda.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ":", - "t": "source.python meta.function-call.python meta.function-call.arguments.python meta.lambda-function.python punctuation.section.function.lambda.begin.python", - "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.python meta.function-call.python meta.function-call.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "+", - "t": "source.python meta.function-call.python meta.function-call.arguments.python keyword.operator.arithmetic.python", - "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.python meta.function-call.python meta.function-call.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function-call.python meta.function-call.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "[", - "t": "source.python meta.function-call.python meta.function-call.arguments.python punctuation.definition.list.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "47", - "t": "source.python meta.function-call.python meta.function-call.arguments.python constant.numeric.dec.python", - "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.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "11", - "t": "source.python meta.function-call.python meta.function-call.arguments.python constant.numeric.dec.python", - "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.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "42", - "t": "source.python meta.function-call.python meta.function-call.arguments.python constant.numeric.dec.python", - "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.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "13", - "t": "source.python meta.function-call.python meta.function-call.arguments.python constant.numeric.dec.python", - "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.python meta.function-call.python meta.function-call.arguments.python punctuation.definition.list.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "woerter ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "{", - "t": "source.python punctuation.definition.dict.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "house", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "Haus", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ",", - "t": "source.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "cat", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "Katze", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ",", - "t": "source.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "black", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "schwarz", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "}", - "t": "source.python punctuation.definition.dict.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "mydictionary ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "{", - "t": "source.python punctuation.definition.dict.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "'", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "foo", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "'", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "23", - "t": "source.python constant.numeric.dec.python", - "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.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "#", - "t": "source.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "comment", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "'", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "bar", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "'", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "hello", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "#", - "t": "source.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "sqadsad", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "}", - "t": "source.python punctuation.definition.dict.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "def", - "t": "source.python meta.function.python storage.type.function.python", - "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.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "steuern", - "t": "source.python meta.function.python entity.name.function.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "einkommen", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ")", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python punctuation.section.function.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"\"\"", - "t": "source.python string.quoted.docstring.multi.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "Berechnung der zu zahlenden Steuern fuer ein zu versteuerndes Einkommen von x", - "t": "source.python string.quoted.docstring.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"\"\"", - "t": "source.python string.quoted.docstring.multi.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " ", - "t": "source.python", - "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.python keyword.control.flow.python", - "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": " einkommen ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "8004", - "t": "source.python constant.numeric.dec.python", - "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.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " steuer ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "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.python constant.numeric.dec.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "elif", - "t": "source.python keyword.control.flow.python", - "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": " einkommen ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "<=", - "t": "source.python keyword.operator.comparison.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "13469", - "t": "source.python constant.numeric.dec.python", - "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.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " y ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python punctuation.parenthesis.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "einkommen ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "-", - "t": "source.python keyword.operator.arithmetic.python", - "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": "8004.0", - "t": "source.python constant.numeric.float.python", - "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.python punctuation.parenthesis.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "/", - "t": "source.python keyword.operator.arithmetic.python", - "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": "10000.0", - "t": "source.python constant.numeric.float.python", - "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": " steuer ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python punctuation.parenthesis.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "912.17", - "t": "source.python constant.numeric.float.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "*", - "t": "source.python keyword.operator.arithmetic.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "+", - "t": "source.python keyword.operator.arithmetic.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "1400", - "t": "source.python constant.numeric.dec.python", - "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.python punctuation.parenthesis.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "*", - "t": "source.python keyword.operator.arithmetic.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "else", - "t": "source.python keyword.control.flow.python", - "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.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " steuer ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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": " einkommen ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "*", - "t": "source.python keyword.operator.arithmetic.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "0.44", - "t": "source.python constant.numeric.float.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "-", - "t": "source.python keyword.operator.arithmetic.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "15694", - "t": "source.python constant.numeric.dec.python", - "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.python", - "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.python keyword.control.flow.python", - "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": " steuer", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "def", - "t": "source.python meta.function.python storage.type.function.python", - "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.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "beliebig", - "t": "source.python meta.function.python entity.name.function.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.begin.python", - "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.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ",", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "y", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ",", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "*", - "t": "source.python meta.function.python meta.function.parameters.python keyword.operator.unpacking.parameter.python", - "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": "mehr", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ")", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python punctuation.section.function.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "print", - "t": "source.python support.function.builtin.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "x=", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ",", - "t": "source.python punctuation.separator.element.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ",", - "t": "source.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ", x=", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ",", - "t": "source.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " y", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "print", - "t": "source.python support.function.builtin.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "mehr: ", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ",", - "t": "source.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " mehr", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "class", - "t": "source.python meta.class.python storage.type.class.python", - "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.python meta.class.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "Memoize", - "t": "source.python meta.class.python entity.name.type.class.python", - "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.python meta.class.python punctuation.section.class.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "def", - "t": "source.python meta.function.python storage.type.function.python", - "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.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "__init__", - "t": "source.python meta.function.python support.function.magic.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python variable.parameter.function.language.special.self.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ",", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "fn", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ")", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python punctuation.section.function.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python variable.language.special.self.python", - "r": { - "dark_plus": "variable.language: #569CD6", - "light_plus": "variable.language: #0000FF", - "dark_vs": "variable.language: #569CD6", - "light_vs": "variable.language: #0000FF", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "fn", - "t": "source.python meta.member.access.python meta.attribute.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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": " fn", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python variable.language.special.self.python", - "r": { - "dark_plus": "variable.language: #569CD6", - "light_plus": "variable.language: #0000FF", - "dark_vs": "variable.language: #569CD6", - "light_vs": "variable.language: #0000FF", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "memo", - "t": "source.python meta.member.access.python meta.attribute.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "{", - "t": "source.python punctuation.definition.dict.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "}", - "t": "source.python punctuation.definition.dict.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "def", - "t": "source.python meta.function.python storage.type.function.python", - "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.python meta.function.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "__call__", - "t": "source.python meta.function.python support.function.magic.python", - "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.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python variable.parameter.function.language.special.self.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ",", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.separator.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python meta.function.python meta.function.parameters.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "*", - "t": "source.python meta.function.python meta.function.parameters.python keyword.operator.unpacking.parameter.python", - "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": "args", - "t": "source.python meta.function.python meta.function.parameters.python variable.parameter.function.language.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ")", - "t": "source.python meta.function.python meta.function.parameters.python punctuation.definition.parameters.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python meta.function.python punctuation.section.function.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "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.python keyword.control.flow.python", - "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": " args ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "not", - "t": "source.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python", - "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.python keyword.operator.logical.python", - "r": { - "dark_plus": "keyword.operator.logical.python: #569CD6", - "light_plus": "keyword.operator.logical.python: #0000FF", - "dark_vs": "keyword.operator.logical.python: #569CD6", - "light_vs": "keyword.operator.logical.python: #0000FF", - "hc_black": "keyword.operator.logical.python: #569CD6" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python variable.language.special.self.python", - "r": { - "dark_plus": "variable.language: #569CD6", - "light_plus": "variable.language: #0000FF", - "dark_vs": "variable.language: #569CD6", - "light_vs": "variable.language: #0000FF", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "memo", - "t": "source.python meta.member.access.python meta.attribute.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python variable.language.special.self.python", - "r": { - "dark_plus": "variable.language: #569CD6", - "light_plus": "variable.language: #0000FF", - "dark_vs": "variable.language: #569CD6", - "light_vs": "variable.language: #0000FF", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "memo", - "t": "source.python meta.member.access.python meta.item-access.python meta.indexed-name.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "[", - "t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "args", - "t": "source.python meta.member.access.python meta.item-access.python meta.item-access.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "]", - "t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python variable.language.special.self.python", - "r": { - "dark_plus": "variable.language: #569CD6", - "light_plus": "variable.language: #0000FF", - "dark_vs": "variable.language: #569CD6", - "light_vs": "variable.language: #0000FF", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "fn", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "*", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python keyword.operator.unpacking.arguments.python", - "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": "args", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "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.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "self", - "t": "source.python variable.language.special.self.python", - "r": { - "dark_plus": "variable.language: #569CD6", - "light_plus": "variable.language: #0000FF", - "dark_vs": "variable.language: #569CD6", - "light_vs": "variable.language: #0000FF", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "memo", - "t": "source.python meta.member.access.python meta.item-access.python meta.indexed-name.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "[", - "t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "args", - "t": "source.python meta.member.access.python meta.item-access.python meta.item-access.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "]", - "t": "source.python meta.member.access.python meta.item-access.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "res ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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": " re", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python punctuation.separator.period.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "search", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.generic.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "(", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "r", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python storage.type.string.python", - "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.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "(", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp", - "r": { - "dark_plus": "support.other.parenthesis.regexp: #CE9178", - "light_plus": "support.other.parenthesis.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "[", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp", - "r": { - "dark_plus": "punctuation.character.set.begin.regexp: #CE9178", - "light_plus": "punctuation.character.set.begin.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "0-9-", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp", - "r": { - "dark_plus": "constant.character.set.regexp: #D16969", - "light_plus": "constant.character.set.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "constant.character: #569CD6" - } - }, - { - "c": "]", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp", - "r": { - "dark_plus": "punctuation.character.set.end.regexp: #CE9178", - "light_plus": "punctuation.character.set.end.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "*", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp", - "r": { - "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D", - "light_plus": "keyword.operator.quantifier.regexp: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": ")", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp", - "r": { - "dark_plus": "support.other.parenthesis.regexp: #CE9178", - "light_plus": "support.other.parenthesis.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "\\s", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "*", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp", - "r": { - "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D", - "light_plus": "keyword.operator.quantifier.regexp: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "(", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp", - "r": { - "dark_plus": "support.other.parenthesis.regexp: #CE9178", - "light_plus": "support.other.parenthesis.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "[", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp", - "r": { - "dark_plus": "punctuation.character.set.begin.regexp: #CE9178", - "light_plus": "punctuation.character.set.begin.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "A-Za-z", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp constant.character.set.regexp", - "r": { - "dark_plus": "constant.character.set.regexp: #D16969", - "light_plus": "constant.character.set.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "constant.character: #569CD6" - } - }, - { - "c": "]", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python meta.character.set.regexp punctuation.character.set.end.regexp constant.other.set.regexp", - "r": { - "dark_plus": "punctuation.character.set.end.regexp: #CE9178", - "light_plus": "punctuation.character.set.end.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "+", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp", - "r": { - "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D", - "light_plus": "keyword.operator.quantifier.regexp: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": ")", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp", - "r": { - "dark_plus": "support.other.parenthesis.regexp: #CE9178", - "light_plus": "support.other.parenthesis.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": ",", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "\\s", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.escape.special.regexp", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "+", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp", - "r": { - "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D", - "light_plus": "keyword.operator.quantifier.regexp: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": "(", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.begin.regexp", - "r": { - "dark_plus": "support.other.parenthesis.regexp: #CE9178", - "light_plus": "support.other.parenthesis.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": ".", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.match.any.regexp", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "*", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python keyword.operator.quantifier.regexp", - "r": { - "dark_plus": "keyword.operator.quantifier.regexp: #D7BA7D", - "light_plus": "keyword.operator.quantifier.regexp: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4" - } - }, - { - "c": ")", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python support.other.parenthesis.regexp punctuation.parenthesis.end.regexp", - "r": { - "dark_plus": "support.other.parenthesis.regexp: #CE9178", - "light_plus": "support.other.parenthesis.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "\"", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python string.regexp.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": ",", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python punctuation.separator.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " i", - "t": "source.python meta.member.access.python meta.function-call.python meta.function-call.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.member.access.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "while", - "t": "source.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "True", - "t": "source.python constant.language.python", - "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.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "try", - "t": "source.python keyword.control.flow.python", - "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.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " n ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "raw_input", - "t": "source.python meta.function-call.python variable.legacy.builtin.python", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE" - } - }, - { - "c": "(", - "t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "Number: ", - "t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ")", - "t": "source.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " n ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "int", - "t": "source.python meta.function-call.python support.type.python", - "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": "(", - "t": "source.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "n", - "t": "source.python meta.function-call.python meta.function-call.arguments.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ")", - "t": "source.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "break", - "t": "source.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "except", - "t": "source.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "ValueError", - "t": "source.python support.type.exception.python", - "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": ":", - "t": "source.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "print", - "t": "source.python meta.function-call.python support.function.builtin.python", - "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.python meta.function-call.python punctuation.definition.arguments.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "Not a number", - "t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python meta.function-call.python meta.function-call.arguments.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ")", - "t": "source.python meta.function-call.python punctuation.definition.arguments.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "async", - "t": "source.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "with", - "t": "source.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "EXPR", - "t": "source.python constant.other.caps.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "as", - "t": "source.python keyword.control.flow.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "VAR", - "t": "source.python constant.other.caps.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.colon.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "BLOCK", - "t": "source.python constant.other.caps.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "#", - "t": "source.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " Comments in dictionary items should be colorized accordingly", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "my_dictionary ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "{", - "t": "source.python punctuation.definition.dict.begin.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "'", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "foo", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "'", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "23", - "t": "source.python constant.numeric.dec.python", - "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.python punctuation.separator.element.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "#", - "t": "source.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " this should be colorized as comment", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "'", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "bar", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "'", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ":", - "t": "source.python punctuation.separator.dict.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "foobar", - "t": "source.python string.quoted.single.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "\"", - "t": "source.python string.quoted.single.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "#", - "t": "source.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "this should be colorized as comment", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "}", - "t": "source.python punctuation.definition.dict.end.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "#", - "t": "source.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " test raw strings", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "text ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "r", - "t": "source.python string.regexp.quoted.multi.python storage.type.string.python", - "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.python string.regexp.quoted.multi.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "interval ``", - "t": "source.python string.regexp.quoted.multi.python", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "[", - "t": "source.python string.regexp.quoted.multi.python meta.character.set.regexp punctuation.character.set.begin.regexp constant.other.set.regexp", - "r": { - "dark_plus": "punctuation.character.set.begin.regexp: #CE9178", - "light_plus": "punctuation.character.set.begin.regexp: #D16969", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "1,2)`` leads to", - "t": "source.python string.regexp.quoted.multi.python meta.character.set.regexp constant.character.set.regexp", - "r": { - "dark_plus": "constant.character.set.regexp: #D16969", - "light_plus": "constant.character.set.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "constant.character: #569CD6" - } - }, - { - "c": "\"\"\"", - "t": "source.python string.regexp.quoted.multi.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string.regexp: #D16969", - "light_plus": "string.regexp: #811F3F", - "dark_vs": "string.regexp: #D16969", - "light_vs": "string.regexp: #811F3F", - "hc_black": "string.regexp: #D16969" - } - }, - { - "c": "highlight_error ", - "t": "source.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "=", - "t": "source.python keyword.operator.assignment.python", - "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.python", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF" - } - }, - { - "c": "True", - "t": "source.python constant.language.python", - "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.python comment.line.number-sign.python punctuation.definition.comment.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": " highlight doctests", - "t": "source.python comment.line.number-sign.python", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668" - } - }, - { - "c": "r", - "t": "source.python string.quoted.docstring.raw.multi.python storage.type.string.python", - "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.python string.quoted.docstring.raw.multi.python punctuation.definition.string.begin.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "Module docstring", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " Some text followed by code sample:", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " ", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": ">>> ", - "t": "source.python string.quoted.docstring.raw.multi.python keyword.control.flow.python", - "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": "for a in foo(2, b=1,", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " ", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "... ", - "t": "source.python string.quoted.docstring.raw.multi.python keyword.control.flow.python", - "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": " c=3):", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " ", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "... ", - "t": "source.python string.quoted.docstring.raw.multi.python keyword.control.flow.python", - "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": " print(a)", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " 0", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": " 1", - "t": "source.python string.quoted.docstring.raw.multi.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - }, - { - "c": "'''", - "t": "source.python string.quoted.docstring.raw.multi.python punctuation.definition.string.end.python", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178" - } - } -] \ No newline at end of file diff --git a/extensions/r/language-configuration.json b/extensions/r/language-configuration.json index dd691e2a6d..3a2e2f34f5 100644 --- a/extensions/r/language-configuration.json +++ b/extensions/r/language-configuration.json @@ -11,13 +11,16 @@ ["{", "}"], ["[", "]"], ["(", ")"], + ["`", "`"], { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "'", "close": "'", "notIn": ["string"] } + { "open": "'", "close": "'", "notIn": ["string", "comment"] }, + { "open": "%", "close": "%", "notIn": ["string", "comment"] } ], "surroundingPairs": [ ["{", "}"], ["[", "]"], ["(", ")"], + ["`", "`"], ["\"", "\""], ["'", "'"] ] diff --git a/extensions/resource-deployment/tsconfig.json b/extensions/resource-deployment/tsconfig.json index 636afa724f..8729a9dfbe 100644 --- a/extensions/resource-deployment/tsconfig.json +++ b/extensions/resource-deployment/tsconfig.json @@ -6,6 +6,7 @@ }, "include": [ "src/**/*", - "../../src/vscode-dts/vscode.d.ts" + "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts", ] } diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index 1e122b9537..43192889e2 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -15,8 +15,14 @@ const CopyWebpackPlugin = require('copy-webpack-plugin'); const { NLSBundlePlugin } = require('vscode-nls-dev/lib/webpack-bundler'); const { DefinePlugin, optimize } = require('webpack'); -function withNodeDefaults(/**@type WebpackConfig*/extConfig) { - /** @type WebpackConfig */ +const tsLoaderOptions = { + compilerOptions: { + 'sourceMap': true, + }, + onlyCompileBundledFiles: true, +}; + +function withNodeDefaults(/**@type WebpackConfig & { context: string }*/extConfig) { const defaultConfig = { mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') target: 'node', // extensions run in a node context @@ -42,19 +48,22 @@ function withNodeDefaults(/**@type WebpackConfig*/extConfig) { // configure TypeScript loader: // * enable sources maps for end-to-end source maps loader: 'ts-loader', + options: tsLoaderOptions + }, { + loader: path.resolve(__dirname, 'mangle-loader.js'), options: { - compilerOptions: { - 'sourceMap': true, - } - } - }] + configFile: path.join(extConfig.context, 'tsconfig.json') + }, + },] }] }, externals: { 'vscode': 'commonjs vscode', // ignored because it doesn't exist 'azdata': 'commonjs azdata', 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module - '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing' // ignored because we don't ship this module + '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', // ignored because we don't ship this module + '@opentelemetry/instrumentation': 'commonjs @opentelemetry/instrumentation', // ignored because we don't ship this module + '@azure/opentelemetry-instrumentation-azure-sdk': 'commonjs @azure/opentelemetry-instrumentation-azure-sdk', // ignored because we don't ship this module }, output: { // all output goes into `dist`. @@ -71,6 +80,10 @@ function withNodeDefaults(/**@type WebpackConfig*/extConfig) { return merge(defaultConfig, extConfig); } +/** + * + * @param {string} context + */ function nodePlugins(context) { // Need to find the top-most `package.json` file const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0]; @@ -92,7 +105,7 @@ function nodePlugins(context) { * }} AdditionalBrowserConfig */ -function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type AdditionalBrowserConfig */ additionalOptions = {}) { +function withBrowserDefaults(/**@type WebpackConfig & { context: string }*/extConfig, /** @type AdditionalBrowserConfig */ additionalOptions = {}) { /** @type WebpackConfig */ const defaultConfig = { mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') @@ -109,23 +122,31 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type Additi rules: [{ test: /\.ts$/, exclude: /node_modules/, - use: [{ - // configure TypeScript loader: - // * enable sources maps for end-to-end source maps - loader: 'ts-loader', - options: { - compilerOptions: { - 'sourceMap': true, + use: [ + { + // configure TypeScript loader: + // * enable sources maps for end-to-end source maps + loader: 'ts-loader', + options: { + ...tsLoaderOptions, + ...(additionalOptions ? {} : { configFile: additionalOptions.configFile }), + } + }, + { + loader: path.resolve(__dirname, 'mangle-loader.js'), + options: { + configFile: path.join(extConfig.context, additionalOptions?.configFile ?? 'tsconfig.json') }, - ...(additionalOptions ? {} : { configFile: additionalOptions.configFile }) - } - }] + }, + ] }] }, externals: { 'vscode': 'commonjs vscode', // ignored because it doesn't exist, 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module - '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing' // ignored because we don't ship this module + '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', // ignored because we don't ship this module + '@opentelemetry/instrumentation': 'commonjs @opentelemetry/instrumentation', // ignored because we don't ship this module + '@azure/opentelemetry-instrumentation-azure-sdk': 'commonjs @azure/opentelemetry-instrumentation-azure-sdk', // ignored because we don't ship this module }, performance: { hints: false @@ -139,30 +160,40 @@ function withBrowserDefaults(/**@type WebpackConfig*/extConfig, /** @type Additi }, // yes, really source maps devtool: 'source-map', - plugins: browserPlugins + plugins: browserPlugins(extConfig.context) }; return merge(defaultConfig, extConfig); } -const browserPlugins = [ - new optimize.LimitChunkCountPlugin({ - maxChunks: 1 - }), - new CopyWebpackPlugin({ - patterns: [ - { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } - ] - }), - new DefinePlugin({ - 'process.platform': JSON.stringify('web'), - 'process.env': JSON.stringify({}), - 'process.env.BROWSER_ENV': JSON.stringify('true') - }) -]; - - - +/** + * + * @param {string} context + */ +function browserPlugins(context) { + // Need to find the top-most `package.json` file + // const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0]; + // const pkgPath = path.join(__dirname, folderName, 'package.json'); + // const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + // const id = `${pkg.publisher}.${pkg.name}`; + return [ + new optimize.LimitChunkCountPlugin({ + maxChunks: 1 + }), + new CopyWebpackPlugin({ + patterns: [ + { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } + ] + }), + new DefinePlugin({ + 'process.platform': JSON.stringify('web'), + 'process.env': JSON.stringify({}), + 'process.env.BROWSER_ENV': JSON.stringify('true') + }), + // TODO: bring this back once vscode-nls-dev supports browser + // new NLSBundlePlugin(id) + ]; +} module.exports = withNodeDefaults; module.exports.node = withNodeDefaults; diff --git a/extensions/simple-browser/esbuild-preview.js b/extensions/simple-browser/esbuild-preview.js index cd836f56c4..841bb2dc5e 100644 --- a/extensions/simple-browser/esbuild-preview.js +++ b/extensions/simple-browser/esbuild-preview.js @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // @ts-check const path = require('path'); -const fs = require('fs'); const esbuild = require('esbuild'); const args = process.argv.slice(2); @@ -21,18 +20,14 @@ const srcDir = path.join(__dirname, 'preview-src'); const outDir = path.join(outputRoot, 'media'); async function build() { - fs.copyFileSync( - path.join(__dirname, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'), - path.join(outDir, 'codicon.css')); - - fs.copyFileSync( - path.join(__dirname, 'node_modules', 'vscode-codicons', 'dist', 'codicon.ttf'), - path.join(outDir, 'codicon.ttf')); - await esbuild.build({ - entryPoints: [ - path.join(srcDir, 'index.ts') - ], + entryPoints: { + 'index': path.join(srcDir, 'index.ts'), + 'codicon': path.join(__dirname, 'node_modules', 'vscode-codicons', 'dist', 'codicon.css'), + }, + loader: { + '.ttf': 'dataurl', + }, bundle: true, minify: true, sourcemap: false, diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index 08344e234f..79abe8e57c 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -11,7 +11,7 @@ "license": "MIT", "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "engines": { - "vscode": "^1.53.0" + "vscode": "^1.70.0" }, "main": "./out/extension", "browser": "./dist/browser/extension", @@ -23,7 +23,6 @@ "workspace" ], "activationEvents": [ - "onCommand:simpleBrowser.show", "onCommand:simpleBrowser.api.open", "onOpenExternalUri:http", "onOpenExternalUri:https", @@ -67,8 +66,7 @@ "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, "dependencies": { - "@vscode/extension-telemetry": "0.6.2", - "vscode-nls": "^5.0.0" + "@vscode/extension-telemetry": "0.7.5" }, "devDependencies": { "@types/vscode-webview": "^1.57.0", diff --git a/extensions/simple-browser/src/extension.ts b/extensions/simple-browser/src/extension.ts index 48b3721786..def0716f57 100644 --- a/extensions/simple-browser/src/extension.ts +++ b/extensions/simple-browser/src/extension.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; import { SimpleBrowserManager } from './simpleBrowserManager'; import { SimpleBrowserView } from './simpleBrowserView'; @@ -13,8 +12,6 @@ declare class URL { hostname: string; } -const localize = nls.loadMessageBundle(); - const openApiCommand = 'simpleBrowser.api.open'; const showCommand = 'simpleBrowser.show'; @@ -23,13 +20,13 @@ const enabledHosts = new Set([ // localhost IPv4 '127.0.0.1', // localhost IPv6 - '0:0:0:0:0:0:0:1', - '::1', + '[0:0:0:0:0:0:0:1]', + '[::1]', // all interfaces IPv4 '0.0.0.0', // all interfaces IPv6 - '0:0:0:0:0:0:0:0', - '::' + '[0:0:0:0:0:0:0:0]', + '[::]' ]); const openerId = 'simpleBrowser.open'; @@ -48,8 +45,8 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand(showCommand, async (url?: string) => { if (!url) { url = await vscode.window.showInputBox({ - placeHolder: localize('simpleBrowser.show.placeholder', "https://example.com"), - prompt: localize('simpleBrowser.show.prompt', "Enter url to visit") + placeHolder: vscode.l10n.t("https://example.com"), + prompt: vscode.l10n.t("Enter url to visit") }); } @@ -62,12 +59,13 @@ export function activate(context: vscode.ExtensionContext) { preserveFocus?: boolean; viewColumn: vscode.ViewColumn; }) => { - manager.show(url.toString(), showOptions); + manager.show(url, showOptions); })); context.subscriptions.push(vscode.window.registerExternalUriOpener(openerId, { canOpenExternalUri(uri: vscode.Uri) { - const originalUri = new URL(uri.toString()); + // We have to replace the IPv6 hosts with IPv4 because URL can't handle IPv6. + const originalUri = new URL(uri.toString(true)); if (enabledHosts.has(originalUri.hostname)) { return isWeb() ? vscode.ExternalUriOpenerPriority.Default @@ -77,13 +75,13 @@ export function activate(context: vscode.ExtensionContext) { return vscode.ExternalUriOpenerPriority.None; }, openExternalUri(resolveUri: vscode.Uri) { - return manager.show(resolveUri.toString(), { + return manager.show(resolveUri, { viewColumn: vscode.window.activeTextEditor ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active }); } }, { schemes: ['http', 'https'], - label: localize('openTitle', "Open in simple browser"), + label: vscode.l10n.t("Open in simple browser"), })); } diff --git a/extensions/simple-browser/src/simpleBrowserManager.ts b/extensions/simple-browser/src/simpleBrowserManager.ts index 5814e6468d..021c2b224a 100644 --- a/extensions/simple-browser/src/simpleBrowserManager.ts +++ b/extensions/simple-browser/src/simpleBrowserManager.ts @@ -19,7 +19,8 @@ export class SimpleBrowserManager { this._activeView = undefined; } - public show(url: string, options?: ShowOptions): void { + public show(inputUri: string | vscode.Uri, options?: ShowOptions): void { + const url = typeof inputUri === 'string' ? inputUri : inputUri.toString(true); if (this._activeView) { this._activeView.show(url, options); } else { @@ -34,7 +35,7 @@ export class SimpleBrowserManager { const url = state?.url ?? ''; const view = SimpleBrowserView.restore(this.extensionUri, url, panel); this.registerWebviewListeners(view); - return; + this._activeView ??= view; } private registerWebviewListeners(view: SimpleBrowserView) { @@ -46,4 +47,3 @@ export class SimpleBrowserManager { } } - diff --git a/extensions/simple-browser/src/simpleBrowserView.ts b/extensions/simple-browser/src/simpleBrowserView.ts index 8dfadb7acd..941cd51e18 100644 --- a/extensions/simple-browser/src/simpleBrowserView.ts +++ b/extensions/simple-browser/src/simpleBrowserView.ts @@ -4,10 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; import { Disposable } from './dispose'; -const localize = nls.loadMessageBundle(); export interface ShowOptions { readonly preserveFocus?: boolean; @@ -17,7 +15,21 @@ export interface ShowOptions { export class SimpleBrowserView extends Disposable { public static readonly viewType = 'simpleBrowser.view'; - private static readonly title = localize('view.title', "Simple Browser"); + private static readonly title = vscode.l10n.t("Simple Browser"); + + private static getWebviewLocalResourceRoots(extensionUri: vscode.Uri): readonly vscode.Uri[] { + return [ + vscode.Uri.joinPath(extensionUri, 'media') + ]; + } + + private static getWebviewOptions(extensionUri: vscode.Uri): vscode.WebviewOptions { + return { + enableScripts: true, + enableForms: true, + localResourceRoots: SimpleBrowserView.getWebviewLocalResourceRoots(extensionUri), + }; + } private readonly _webviewPanel: vscode.WebviewPanel; @@ -33,12 +45,8 @@ export class SimpleBrowserView extends Disposable { viewColumn: showOptions?.viewColumn ?? vscode.ViewColumn.Active, preserveFocus: showOptions?.preserveFocus }, { - enableScripts: true, - enableForms: true, retainContextWhenHidden: true, - localResourceRoots: [ - vscode.Uri.joinPath(extensionUri, 'media') - ] + ...SimpleBrowserView.getWebviewOptions(extensionUri) }); return new SimpleBrowserView(extensionUri, url, webview); } @@ -46,9 +54,9 @@ export class SimpleBrowserView extends Disposable { public static restore( extensionUri: vscode.Uri, url: string, - webview: vscode.WebviewPanel, + webviewPanel: vscode.WebviewPanel, ): SimpleBrowserView { - return new SimpleBrowserView(extensionUri, url, webview); + return new SimpleBrowserView(extensionUri, url, webviewPanel); } private constructor( @@ -59,6 +67,7 @@ export class SimpleBrowserView extends Disposable { super(); this._webviewPanel = this._register(webviewPanel); + this._webviewPanel.webview.options = SimpleBrowserView.getWebviewOptions(extensionUri); this._register(this._webviewPanel.webview.onDidReceiveMessage(e => { switch (e.type) { @@ -116,7 +125,7 @@ export class SimpleBrowserView extends Disposable { @@ -150,12 +159,12 @@ export class SimpleBrowserView extends Disposable {
-
${localize('view.iframe-focused', "Focus Lock")}
+
${vscode.l10n.t("Focus Lock")}
@@ -173,7 +182,6 @@ function escapeAttribute(value: string | vscode.Uri): string { return value.toString().replace(/"/g, '"'); } - function getNonce() { let text = ''; const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; diff --git a/extensions/simple-browser/yarn.lock b/extensions/simple-browser/yarn.lock index 5faf627618..58a6ddf8cd 100644 --- a/extensions/simple-browser/yarn.lock +++ b/extensions/simple-browser/yarn.lock @@ -2,61 +2,352 @@ # yarn lockfile v1 -"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7" - integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g== +"@azure/abort-controller@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@azure/abort-controller/-/abort-controller-1.1.0.tgz#788ee78457a55af8a1ad342acb182383d2119249" + integrity sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw== dependencies: - "@microsoft/applicationinsights-core-js" "2.8.4" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + tslib "^2.2.0" -"@microsoft/1ds-post-js@^3.2.3": - version "3.2.3" - resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216" - integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ== +"@azure/core-auth@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.4.0.tgz#6fa9661c1705857820dbc216df5ba5665ac36a9e" + integrity sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ== dependencies: - "@microsoft/1ds-core-js" "3.2.3" - "@microsoft/applicationinsights-shims" "^2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" -"@microsoft/applicationinsights-core-js@2.8.4": - version "2.8.4" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596" - integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ== +"@azure/core-rest-pipeline@^1.10.0": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.1.tgz#348290847ca31b9eecf9cf5de7519aaccdd30968" + integrity sha512-Kji9k6TOFRDB5ZMTw8qUf2IJ+CeJtsuMdAHox9eqpTf1cefiNMpzrfnF6sINEBZJsaVaWgQ0o48B6kcUH68niA== dependencies: - "@microsoft/applicationinsights-shims" "2.0.1" - "@microsoft/dynamicproto-js" "^1.1.6" + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.0.0" + "@azure/logger" "^1.0.0" + form-data "^4.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + tslib "^2.2.0" + uuid "^8.3.0" -"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd" - integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ== +"@azure/core-tracing@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" + integrity sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw== + dependencies: + tslib "^2.2.0" -"@microsoft/dynamicproto-js@^1.1.6": - version "1.1.6" - resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc" - integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg== +"@azure/core-util@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.1.1.tgz#8f87b3dd468795df0f0849d9f096c3e7b29452c1" + integrity sha512-A4TBYVQCtHOigFb2ETiiKFDocBoI1Zk2Ui1KpI42aJSIDexF7DHQFpnjonltXAIU/ceH+1fsZAWWgvX6/AKzog== + dependencies: + "@azure/abort-controller" "^1.0.0" + tslib "^2.2.0" + +"@azure/logger@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.0.3.tgz#6e36704aa51be7d4a1bae24731ea580836293c96" + integrity sha512-aK4s3Xxjrx3daZr3VylxejK3vG5ExXck5WOHDJ8in/k9AqlfIyFMMT1uG7u8mNjX+QRILTIn0/Xgschfh/dQ9g== + dependencies: + tslib "^2.2.0" + +"@microsoft/1ds-core-js@3.2.8", "@microsoft/1ds-core-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.8.tgz#1b6b7d9bb858238c818ccf4e4b58ece7aeae5760" + integrity sha512-9o9SUAamJiTXIYwpkQDuueYt83uZfXp8zp8YFix1IwVPwC9RmE36T2CX9gXOeq1nDckOuOduYpA8qHvdh5BGfQ== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/1ds-post-js@^3.2.8": + version "3.2.8" + resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.8.tgz#46793842cca161bf7a2a5b6053c349f429e55110" + integrity sha512-SjlRoNcXcXBH6WQD/5SkkaCHIVqldH3gDu+bI7YagrOVJ5APxwT1Duw9gm3L1FjFa9S2i81fvJ3EVSKpp9wULA== + dependencies: + "@microsoft/1ds-core-js" "3.2.8" + "@microsoft/applicationinsights-shims" "^2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-channel-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-channel-js/-/applicationinsights-channel-js-2.8.9.tgz#840656f3c716de8b3eb0a98c122aa1b92bb8ebfb" + integrity sha512-fMBsAEB7pWtPn43y72q9Xy5E5y55r6gMuDQqRRccccVoQDPXyS57VCj5IdATblctru0C6A8XpL2vRyNmEsu0Vg== + dependencies: + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-common@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-common/-/applicationinsights-common-2.8.9.tgz#a75e4a3143a7fd797687830c0ddd2069fd900827" + integrity sha512-mObn1moElyxZaGIRF/IU3cOaeKMgxghXnYEoHNUCA2e+rNwBIgxjyKkblFIpmGuHf4X7Oz3o3yBWpaC6AoMpig== + dependencies: + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-core-js@2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.9.tgz#0e5d207acfae6986a6fc97249eeb6117e523bf1b" + integrity sha512-HRuIuZ6aOWezcg/G5VyFDDWGL8hDNe/ljPP01J7ImH2kRPEgbtcfPSUMjkamGMefgdq81GZsSoC/NNGTP4pp2w== + dependencies: + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-shims@2.0.2", "@microsoft/applicationinsights-shims@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.2.tgz#92b36a09375e2d9cb2b4203383b05772be837085" + integrity sha512-PoHEgsnmcqruLNHZ/amACqdJ6YYQpED0KSRe6J7gIJTtpZC1FfFU9b1fmDKDKtFoUSrPzEh1qzO3kmRZP0betg== + +"@microsoft/applicationinsights-web-basic@^2.8.9": + version "2.8.9" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-basic/-/applicationinsights-web-basic-2.8.9.tgz#eed2f3d1e19069962ed2155915c1656e6936e1d5" + integrity sha512-CH0J8JFOy7MjK8JO4pXXU+EML+Ilix+94PMZTX5EJlBU1in+mrik74/8qSg3UC4ekPi12KwrXaHCQSVC3WseXQ== + dependencies: + "@microsoft/applicationinsights-channel-js" "2.8.9" + "@microsoft/applicationinsights-common" "2.8.9" + "@microsoft/applicationinsights-core-js" "2.8.9" + "@microsoft/applicationinsights-shims" "2.0.2" + "@microsoft/dynamicproto-js" "^1.1.7" + +"@microsoft/applicationinsights-web-snippet@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-web-snippet/-/applicationinsights-web-snippet-1.0.1.tgz#6bb788b2902e48bf5d460c38c6bb7fedd686ddd7" + integrity sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ== + +"@microsoft/dynamicproto-js@^1.1.7": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz#ede48dd3f85af14ee369c805e5ed5b84222b9fe2" + integrity sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA== + +"@opentelemetry/api@^1.0.4": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.2.0.tgz#89ef99401cde6208cff98760b67663726ef26686" + integrity sha512-0nBr+VZNKm9tvNDZFstI3Pq1fCTEDK5OZTnVKNvBNAKgd0yIvmwsP4m61rEv7ZP+tOUjWJhROpxK5MsnlF911g== + +"@opentelemetry/core@1.7.0", "@opentelemetry/core@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.7.0.tgz#83bdd1b7a4ceafcdffd6590420657caec5f7b34c" + integrity sha512-AVqAi5uc8DrKJBimCTFUT4iFI+5eXpo4sYmGbQ0CypG0piOTHE2g9c5aSoTGYXu3CzOmJZf7pT6Xh+nwm5d6yQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/resources@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.7.0.tgz#90ccd3a6a86b4dfba4e833e73944bd64958d78c5" + integrity sha512-u1M0yZotkjyKx8dj+46Sg5thwtOTBmtRieNXqdCRiWUp6SfFiIP0bI+1XK3LhuXqXkBXA1awJZaTqKduNMStRg== + dependencies: + "@opentelemetry/core" "1.7.0" + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/sdk-trace-base@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.7.0.tgz#b498424e0c6340a9d80de63fd408c5c2130a60a5" + integrity sha512-Iz84C+FVOskmauh9FNnj4+VrA+hG5o+tkMzXuoesvSfunVSioXib0syVFeNXwOm4+M5GdWCuW632LVjqEXStIg== + dependencies: + "@opentelemetry/core" "1.7.0" + "@opentelemetry/resources" "1.7.0" + "@opentelemetry/semantic-conventions" "1.7.0" + +"@opentelemetry/semantic-conventions@1.7.0", "@opentelemetry/semantic-conventions@^1.0.1": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.7.0.tgz#af80a1ef7cf110ea3a68242acd95648991bcd763" + integrity sha512-FGBx/Qd09lMaqQcogCHyYrFEpTx4cAjeS+48lMIR12z7LdH+zofGDVQSubN59nL6IpubfKqTeIDu9rNO28iHVA== + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== "@types/vscode-webview@^1.57.0": version "1.57.0" resolved "https://registry.yarnpkg.com/@types/vscode-webview/-/vscode-webview-1.57.0.tgz#bad5194d45ae8d03afc1c0f67f71ff5e7a243bbf" integrity sha512-x3Cb/SMa1IwRHfSvKaZDZOTh4cNoG505c3NjTqGlMC082m++x/ETUmtYniDsw6SSmYzZXO8KBNhYxR0+VqymqA== -"@vscode/extension-telemetry@0.6.2": - version "0.6.2" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e" - integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w== +"@vscode/extension-telemetry@0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.7.5.tgz#bf965731816e08c3f146f96d901ec67954fc913b" + integrity sha512-fJ5y3TcpqqkFYHneabYaoB4XAhDdVflVm+TDKshw9VOs77jkgNS4UA7LNXrWeO0eDne3Sh3JgURf+xzc1rk69w== dependencies: - "@microsoft/1ds-core-js" "^3.2.3" - "@microsoft/1ds-post-js" "^3.2.3" + "@microsoft/1ds-core-js" "^3.2.8" + "@microsoft/1ds-post-js" "^3.2.8" + "@microsoft/applicationinsights-web-basic" "^2.8.9" + applicationinsights "2.4.1" + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +applicationinsights@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-2.4.1.tgz#4de4c4dd3c7c4a44445cfbf3d15808fc0dcc423d" + integrity sha512-0n0Ikd0gzSm460xm+M0UTWIwXrhrH/0bqfZatcJjYObWyefxfAxapGEyNnSGd1Tg90neHz+Yhf+Ff/zgvPiQYA== + dependencies: + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.10.0" + "@microsoft/applicationinsights-web-snippet" "^1.0.1" + "@opentelemetry/api" "^1.0.4" + "@opentelemetry/core" "^1.0.1" + "@opentelemetry/sdk-trace-base" "^1.0.1" + "@opentelemetry/semantic-conventions" "^1.0.1" + cls-hooked "^4.2.2" + continuation-local-storage "^3.2.1" + diagnostic-channel "1.1.0" + diagnostic-channel-publishers "1.0.5" + +async-hook-jl@^1.7.6: + version "1.7.6" + resolved "https://registry.yarnpkg.com/async-hook-jl/-/async-hook-jl-1.7.6.tgz#4fd25c2f864dbaf279c610d73bf97b1b28595e68" + integrity sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg== + dependencies: + stack-chain "^1.3.7" + +async-listener@^0.6.0: + version "0.6.10" + resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" + integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== + dependencies: + semver "^5.3.0" + shimmer "^1.1.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +cls-hooked@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/cls-hooked/-/cls-hooked-4.2.2.tgz#ad2e9a4092680cdaffeb2d3551da0e225eae1908" + integrity sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw== + dependencies: + async-hook-jl "^1.7.6" + emitter-listener "^1.0.1" + semver "^5.4.1" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +continuation-local-storage@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" + integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== + dependencies: + async-listener "^0.6.0" + emitter-listener "^1.1.1" + +debug@4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +diagnostic-channel-publishers@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-1.0.5.tgz#df8c317086c50f5727fdfb5d2fce214d2e4130ae" + integrity sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg== + +diagnostic-channel@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-1.1.0.tgz#6985e9dfedfbc072d91dc4388477e4087147756e" + integrity sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ== + dependencies: + semver "^5.3.0" + +emitter-listener@^1.0.1, emitter-listener@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" + integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== + dependencies: + shimmer "^1.2.0" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +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== + +semver@^5.3.0, semver@^5.4.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +shimmer@^1.1.0, shimmer@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + +stack-chain@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== + +tslib@^2.2.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e" + integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA== + +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== vscode-codicons@^0.0.14: version "0.0.14" resolved "https://registry.yarnpkg.com/vscode-codicons/-/vscode-codicons-0.0.14.tgz#e0d05418e2e195564ff6f6a2199d70415911c18f" integrity sha512-6CEH5KT9ct5WMw7n5dlX7rB8ya4CUI2FSq1Wk36XaW+c5RglFtAanUV0T+gvZVVFhl/WxfjTvFHq06Hz9c1SLA== - -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== diff --git a/extensions/sql-database-projects/src/models/dataSources/dataSourceJson.ts b/extensions/sql-database-projects/src/models/dataSources/dataSourceJson.ts index eb69200946..f6e985eaf7 100644 --- a/extensions/sql-database-projects/src/models/dataSources/dataSourceJson.ts +++ b/extensions/sql-database-projects/src/models/dataSources/dataSourceJson.ts @@ -6,7 +6,7 @@ /** * JSON format for datasources.json */ -interface DataSourceFileJson { +export interface DataSourceFileJson { version: string; datasources: DataSourceJson[]; } @@ -14,7 +14,7 @@ interface DataSourceFileJson { /** * JSON format for a datasource entry in datasources.json */ -interface DataSourceJson { +export interface DataSourceJson { name: string; type: string; version: string; diff --git a/extensions/sql-database-projects/src/models/dataSources/sqlConnectionStringSource.ts b/extensions/sql-database-projects/src/models/dataSources/sqlConnectionStringSource.ts index 8f002c7778..07bd0ccadf 100644 --- a/extensions/sql-database-projects/src/models/dataSources/sqlConnectionStringSource.ts +++ b/extensions/sql-database-projects/src/models/dataSources/sqlConnectionStringSource.ts @@ -6,6 +6,7 @@ import type * as azdataType from 'azdata'; import * as vscodeMssql from 'vscode-mssql'; import { DataSource } from './dataSources'; +import { DataSourceJson } from './dataSourceJson'; import * as constants from '../../common/constants'; /** diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index aa48d9724f..4202bdd7d2 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -7,14 +7,21 @@ } }, { - "scope": ["meta.embedded", "source.groovy.embedded"], + "scope": [ + "meta.embedded", + "source.groovy.embedded", + "string meta.image.inline.markdown" + ], "settings": { "foreground": "#6688cc" } }, { - "name": "Comment", - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "foreground": "#384887" } @@ -405,6 +412,7 @@ // "activityBar.foreground": "", // "activityBarBadge.background": "", // "activityBarBadge.foreground": "", + "activityBarItem.profilesBackground": "#082877", // Workbench: Panel // "panel.background": "", diff --git a/extensions/html/yarn.lock b/extensions/theme-carbon/yarn.lock similarity index 100% rename from extensions/html/yarn.lock rename to extensions/theme-carbon/yarn.lock diff --git a/extensions/theme-defaults/package.json b/extensions/theme-defaults/package.json index 95ee03eeca..bf84a4d4d5 100644 --- a/extensions/theme-defaults/package.json +++ b/extensions/theme-defaults/package.json @@ -13,6 +13,42 @@ }, "contributes": { "themes": [ + { + "id": "Default Dark+", + "label": "%darkPlusColorThemeLabel%", + "uiTheme": "vs-dark", + "path": "./themes/dark_plus.json" + }, + { + "id": "Default Dark Modern", + "label": "%darkModernThemeLabel%", + "uiTheme": "vs-dark", + "path": "./themes/dark_modern.json" + }, + { + "id": "Default Light+", + "label": "%lightPlusColorThemeLabel%", + "uiTheme": "vs", + "path": "./themes/light_plus.json" + }, + { + "id": "Default Light Modern", + "label": "%lightModernThemeLabel%", + "uiTheme": "vs", + "path": "./themes/light_modern.json" + }, + { + "id": "Visual Studio Dark", + "label": "%darkColorThemeLabel%", + "uiTheme": "vs-dark", + "path": "./themes/dark_vs.json" + }, + { + "id": "Visual Studio Light", + "label": "%lightColorThemeLabel%", + "uiTheme": "vs", + "path": "./themes/light_vs.json" + }, { "id": "Default High Contrast", "label": "%hcColorThemeLabel%", @@ -25,17 +61,17 @@ "uiTheme": "hc-light", "path": "./themes/hc_light.json" } - ] - }, - "iconThemes": [ - { - "id": "vs-minimal", - "label": "Minimal (Azure Data Studio)", - "path": "./fileicons/vs_minimal-icon-theme.json" - } - ], - "repository": { - "type": "git", - "url": "https://github.com/microsoft/azuredatastudio.git" - } + ], + "iconThemes": [ + { + "id": "vs-minimal", + "label": "%minimalIconThemeLabel%", + "path": "./fileicons/vs_minimal-icon-theme.json" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } } diff --git a/extensions/theme-defaults/package.nls.json b/extensions/theme-defaults/package.nls.json index b98bd7fb5b..cacbd6b8d9 100644 --- a/extensions/theme-defaults/package.nls.json +++ b/extensions/theme-defaults/package.nls.json @@ -1,8 +1,10 @@ { "displayName": "Default Themes", "description": "The default Visual Studio light and dark themes", - "darkPlusColorThemeLabel": "Dark+ (default dark)", - "lightPlusColorThemeLabel": "Light+ (default light)", + "darkPlusColorThemeLabel": "Dark+", + "darkModernThemeLabel": "Dark Modern", + "lightPlusColorThemeLabel": "Light+", + "lightModernThemeLabel": "Light Modern", "darkColorThemeLabel": "Dark (Visual Studio)", "lightColorThemeLabel": "Light (Visual Studio)", "hcColorThemeLabel": "Dark High Contrast", diff --git a/extensions/theme-defaults/themes/dark_modern.json b/extensions/theme-defaults/themes/dark_modern.json new file mode 100644 index 0000000000..0d55d6a20a --- /dev/null +++ b/extensions/theme-defaults/themes/dark_modern.json @@ -0,0 +1,140 @@ +{ + "$schema": "vscode://schemas/color-theme", + "name": "Default Dark Modern", + "include": "./dark_plus.json", + "colors": { + "activityBar.activeBorder": "#0078d4", + "activityBar.background": "#181818", + "activityBar.border": "#ffffff15", + "activityBar.foreground": "#d7d7d7", + "activityBar.inactiveForeground": "#ffffff80", + "activityBarBadge.background": "#0078d4", + "activityBarBadge.foreground": "#ffffff", + "badge.background": "#0078d4", + "badge.foreground": "#ffffff", + "button.background": "#0078d4", + "button.border": "#ffffff12", + "button.foreground": "#ffffff", + "button.hoverBackground": "#0078d4e6", + "button.secondaryBackground": "#FFFFFF0F", + "button.secondaryForeground": "#cccccc", + "button.secondaryHoverBackground": "#ffffff15", + "checkbox.background": "#313131", + "checkbox.border": "#ffffff1f", + "debugToolBar.background": "#181818", + "descriptionForeground": "#8b949e", + "diffEditor.insertedLineBackground": "#23863633", + "diffEditor.insertedTextBackground": "#2386364d", + "diffEditor.removedLineBackground": "#da363333", + "diffEditor.removedTextBackground": "#da36334d", + "dropdown.background": "#313131", + "dropdown.border": "#ffffff1f", + "dropdown.foreground": "#cccccc", + "dropdown.listBackground": "#313131", + "editor.background": "#1f1f1f", + "editor.findMatchBackground": "#9e6a03", + "editor.foreground": "#cccccc", + "editorGroup.border": "#ffffff17", + "editorGroupHeader.tabsBackground": "#181818", + "editorGroupHeader.tabsBorder": "#ffffff15", + "editorGutter.addedBackground": "#2ea043", + "editorGutter.deletedBackground": "#f85149", + "editorGutter.modifiedBackground": "#0078d4", + "editorInlayHint.background": "#8b949e33", + "editorInlayHint.foreground": "#8b949e", + "editorInlayHint.typeBackground": "#8b949e33", + "editorInlayHint.typeForeground": "#8b949e", + "editorLineNumber.activeForeground": "#cccccc", + "editorLineNumber.foreground": "#6e7681", + "editorOverviewRuler.border": "#010409", + "editorWidget.background": "#1f1f1f", + "errorForeground": "#f85149", + "focusBorder": "#0078d4", + "foreground": "#cccccc", + "icon.foreground": "#cccccc", + "input.background": "#2a2a2a", + "input.border": "#ffffff1f", + "input.foreground": "#cccccc", + "input.placeholderForeground": "#ffffff79", + "inputOption.activeBackground": "#2489db82", + "inputOption.activeBorder": "#2488db", + "keybindingLabel.foreground": "#cccccc", + "list.activeSelectionBackground": "#323232", + "list.activeSelectionIconForeground": "#ffffff", + "list.activeSelectionForeground": "#ffffff", + "menu.background": "#1f1f1f", + "notificationCenterHeader.background": "#1f1f1f", + "notificationCenterHeader.foreground": "#cccccc", + "notifications.background": "#1f1f1f", + "notifications.border": "#ffffff15", + "notifications.foreground": "#cccccc", + "panel.background": "#181818", + "panel.border": "#ffffff15", + "panelInput.border": "#ffffff15", + "panelTitle.activeBorder": "#0078d4", + "panelTitle.activeForeground": "#cccccc", + "panelTitle.inactiveForeground": "#8b949e", + "peekViewEditor.background": "#1f1f1f", + "peekViewEditor.matchHighlightBackground": "#bb800966", + "peekViewResult.background": "#1f1f1f", + "peekViewResult.matchHighlightBackground": "#bb800966", + "pickerGroup.border": "#ffffff15", + "pickerGroup.foreground": "#8b949e", + "progressBar.background": "#0078d4", + "quickInput.background": "#1f1f1f", + "quickInput.foreground": "#cccccc", + "scrollbar.shadow": "#484f5833", + "scrollbarSlider.activeBackground": "#6e768187", + "scrollbarSlider.background": "#6e768133", + "scrollbarSlider.hoverBackground": "#6e768145", + "settings.dropdownBackground": "#313131", + "settings.dropdownBorder": "#ffffff1f", + "settings.headerForeground": "#ffffff", + "settings.modifiedItemIndicator": "#bb800966", + "sideBar.background": "#181818", + "sideBar.border": "#ffffff15", + "sideBar.foreground": "#cccccc", + "sideBarSectionHeader.background": "#181818", + "sideBarSectionHeader.border": "#ffffff15", + "sideBarSectionHeader.foreground": "#cccccc", + "sideBarTitle.foreground": "#cccccc", + "statusBar.background": "#181818", + "statusBar.border": "#ffffff15", + "statusBar.debuggingBackground": "#0078d4", + "statusBar.debuggingForeground": "#ffffff", + "statusBar.focusBorder": "#0078d4", + "statusBar.foreground": "#cccccc", + "statusBar.noFolderBackground": "#1f1f1f", + "statusBarItem.focusBorder": "#0078d4", + "statusBarItem.prominentBackground": "#6e768166", + "statusBarItem.remoteBackground": "#0078d4", + "statusBarItem.remoteForeground": "#ffffff", + "tab.activeBackground": "#1f1f1f", + "tab.activeBorder": "#1f1f1f", + "tab.activeBorderTop": "#0078d4", + "tab.activeForeground": "#ffffff", + "tab.border": "#ffffff15", + "tab.hoverBackground": "#1f1f1f", + "tab.inactiveBackground": "#181818", + "tab.inactiveForeground": "#ffffff80", + "tab.unfocusedActiveBorder": "#1f1f1f", + "tab.unfocusedActiveBorderTop": "#ffffff15", + "tab.unfocusedHoverBackground": "#6e76811a", + "terminal.foreground": "#cccccc", + "terminal.tab.activeBorder": "#0078d4", + "textBlockQuote.background": "#010409", + "textBlockQuote.border": "#ffffff14", + "textCodeBlock.background": "#6e768166", + "textLink.activeForeground": "#40A6FF", + "textLink.foreground": "#40A6FF", + "textSeparator.foreground": "#21262d", + "titleBar.activeBackground": "#181818", + "titleBar.activeForeground": "#cccccc", + "titleBar.border": "#ffffff15", + "titleBar.inactiveBackground": "#1f1f1f", + "titleBar.inactiveForeground": "#8b949e", + "welcomePage.tileBackground": "#ffffff0f", + "welcomePage.progress.foreground": "#0078d4", + "widget.border": "#ffffff15", + }, +} diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index 3183099481..ed80b1785f 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -1,6 +1,6 @@ { "$schema": "vscode://schemas/color-theme", - "name": "Dark+ (default dark)", + "name": "Dark+", "include": "./dark_vs.json", "tokenColors": [ { @@ -173,7 +173,10 @@ } }, { - "scope": "constant.character", + "scope": [ + "constant.character", + "constant.other.option" + ], "settings": { "foreground": "#569cd6" } diff --git a/extensions/theme-defaults/themes/dark_vs.json b/extensions/theme-defaults/themes/dark_vs.json index 8072b0bdd6..04e33e0706 100644 --- a/extensions/theme-defaults/themes/dark_vs.json +++ b/extensions/theme-defaults/themes/dark_vs.json @@ -2,6 +2,7 @@ "$schema": "vscode://schemas/color-theme", "name": "Dark (Visual Studio)", "colors": { + "checkbox.border": "#6B6B6B", "editor.background": "#1E1E1E", "editor.foreground": "#D4D4D4", "editor.inactiveSelectionBackground": "#3A3D41", @@ -12,21 +13,26 @@ "activityBarBadge.background": "#007ACC", "sideBarTitle.foreground": "#BBBBBB", "input.placeholderForeground": "#A6A6A6", - "menu.background": "#303031", + "menu.background": "#252526", "menu.foreground": "#CCCCCC", + "menu.separatorBackground": "#454545", + "menu.border": "#454545", "statusBarItem.remoteForeground": "#FFF", "statusBarItem.remoteBackground": "#16825D", "ports.iconRunningProcessForeground": "#369432", "sideBarSectionHeader.background": "#0000", "sideBarSectionHeader.border": "#ccc3", "tab.lastPinnedBorder": "#ccc3", - "list.activeSelectionIconForeground": "#FFF" + "list.activeSelectionIconForeground": "#FFF", + "terminal.inactiveSelectionBackground": "#3A3D41", + "widget.border": "#303031", }, "tokenColors": [ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown", ], "settings": { "foreground": "#D4D4D4" @@ -51,7 +57,11 @@ } }, { - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "foreground": "#6A9955" } diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 7a257515d8..dbc3d2808c 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -17,7 +17,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#FFFFFF" @@ -42,7 +43,11 @@ } }, { - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "foreground": "#7ca668" } diff --git a/extensions/theme-defaults/themes/hc_light.json b/extensions/theme-defaults/themes/hc_light.json index 83a4083f90..5a06c116a1 100644 --- a/extensions/theme-defaults/themes/hc_light.json +++ b/extensions/theme-defaults/themes/hc_light.json @@ -27,7 +27,11 @@ } }, { - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "foreground": "#515151" } diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json new file mode 100644 index 0000000000..8ca74d4e02 --- /dev/null +++ b/extensions/theme-defaults/themes/light_modern.json @@ -0,0 +1,152 @@ +{ + "$schema": "vscode://schemas/color-theme", + "name": "Default Light Modern", + "include": "./light_plus.json", + "colors": { + "activityBar.activeBorder": "#005FB8", + "activityBar.background": "#f8f8f8", + "activityBar.border": "#0000001a", + "activityBar.foreground": "#1f1f1f", + "activityBar.inactiveForeground": "#0000009e", + "activityBarBadge.background": "#005FB8", + "activityBarBadge.foreground": "#ffffff", + "badge.background": "#005FB8", + "badge.foreground": "#ffffff", + "button.background": "#005FB8", + "button.border": "#0000001a", + "button.foreground": "#ffffff", + "button.hoverBackground": "#005FB8e6", + "button.secondaryBackground": "#0000001a", + "button.secondaryForeground": "#3b3b3b", + "button.secondaryHoverBackground": "#00000034", + "checkbox.background": "#f8f8f8", + "checkbox.border": "#CECECE", + "descriptionForeground": "#3b3b3b", + "diffEditor.insertedLineBackground": "#23863633", + "diffEditor.insertedTextBackground": "#2386364d", + "diffEditor.removedLineBackground": "#da363333", + "diffEditor.removedTextBackground": "#da36334d", + "dropdown.background": "#ffffff", + "dropdown.border": "#CECECE", + "dropdown.foreground": "#3b3b3b", + "dropdown.listBackground": "#ffffff", + "editor.background": "#ffffff", + "editor.foreground": "#3b3b3b", + "editor.inactiveSelectionBackground": "#E5EBF1", + "editor.selectionHighlightBackground": "#ADD6FF80", + "editorGroup.border": "#0000001a", + "editorGroupHeader.tabsBackground": "#f8f8f8", + "editorGroupHeader.tabsBorder": "#0000001a", + "editorGutter.addedBackground": "#2ea043", + "editorGutter.deletedBackground": "#f85149", + "editorGutter.modifiedBackground": "#005FB8", + "editorIndentGuide.background": "#D3D3D3", + "editorInlayHint.background": "#8b949e33", + "editorInlayHint.foreground": "#8b949e", + "editorInlayHint.typeBackground": "#8b949e33", + "editorInlayHint.typeForeground": "#8b949e", + "editorLineNumber.activeForeground": "#171184", + "editorLineNumber.foreground": "#6e7681", + "editorOverviewRuler.border": "#0000001a", + "editorSuggestWidget.background": "#f8f8f8", + "editorWidget.background": "#ffffff", + "errorForeground": "#f85149", + "focusBorder": "#005FB8", + "foreground": "#3b3b3b", + "icon.foreground": "#3b3b3b", + "input.background": "#ffffff", + "input.border": "#CECECE", + "input.foreground": "#3b3b3b", + "input.placeholderForeground": "#00000079", + "inputOption.activeBackground": "#005fb841", + "inputOption.activeBorder": "#005FB8", + "inputOption.activeForeground": "#000000", + "keybindingLabel.foreground": "#3b3b3b", + "list.activeSelectionBackground": "#e8e8e8", + "list.activeSelectionForeground": "#000000", + "list.activeSelectionIconForeground": "#000000", + "list.hoverBackground": "#f2f2f2", + "menu.border": "#D4D4D4", + "notebook.cellBorderColor": "#E8E8E8", + "notebook.selectedCellBackground": "#c8ddf150", + "notificationCenterHeader.background": "#ffffff", + "notificationCenterHeader.foreground": "#3b3b3b", + "notifications.background": "#ffffff", + "notifications.border": "#0000001a", + "notifications.foreground": "#3b3b3b", + "panel.background": "#f8f8f8", + "panel.border": "#0000001a", + "panelInput.border": "#0000001a", + "panelTitle.activeBorder": "#005FB8", + "panelTitle.activeForeground": "#3b3b3b", + "panelTitle.inactiveForeground": "#3b3b3b", + "peekViewEditor.matchHighlightBackground": "#bb800966", + "peekViewResult.background": "#ffffff", + "peekViewResult.matchHighlightBackground": "#bb800966", + "pickerGroup.border": "#0000001a", + "pickerGroup.foreground": "#8b949e", + "ports.iconRunningProcessForeground": "#369432", + "progressBar.background": "#005FB8", + "quickInput.background": "#ffffff", + "quickInput.foreground": "#3b3b3b", + "scrollbar.shadow": "#484f5833", + "scrollbarSlider.activeBackground": "#6e768187", + "scrollbarSlider.background": "#6e768133", + "scrollbarSlider.hoverBackground": "#6e768145", + "searchEditor.textInputBorder": "#CECECE", + "settings.dropdownBackground": "#ffffff", + "settings.dropdownBorder": "#CECECE", + "settings.headerForeground": "#1f1f1f", + "settings.modifiedItemIndicator": "#bb800966", + "settings.numberInputBorder": "#CECECE", + "settings.textInputBorder": "#CECECE", + "sideBar.background": "#f8f8f8", + "sideBar.border": "#0000001a", + "sideBar.foreground": "#3b3b3b", + "sideBarSectionHeader.background": "#f8f8f8", + "sideBarSectionHeader.border": "#0000001a", + "sideBarSectionHeader.foreground": "#3b3b3b", + "sideBarTitle.foreground": "#3b3b3b", + "statusBar.background": "#f8f8f8", + "statusBar.foreground": "#3b3b3b", + "statusBar.border": "#0000001a", + "statusBar.debuggingBackground": "#fd716c", + "statusBar.debuggingForeground": "#000000", + "statusBar.focusBorder": "#005FB8", + "statusBar.noFolderBackground": "#f8f8f8", + "statusBarItem.errorBackground": "#c72e0f", + "statusBarItem.focusBorder": "#005FB8", + "statusBarItem.prominentBackground": "#6e768166", + "statusBarItem.remoteBackground": "#005FB8", + "statusBarItem.remoteForeground": "#ffffff", + "tab.activeBackground": "#ffffff", + "tab.activeBorder": "#f8f8f8", + "tab.activeBorderTop": "#005FB8", + "tab.activeForeground": "#3b3b3b", + "tab.border": "#0000001a", + "tab.hoverBackground": "#ffffff", + "tab.inactiveBackground": "#f8f8f8", + "tab.inactiveForeground": "#00000080", + "tab.lastPinnedBorder": "#3b3b3b30", + "tab.unfocusedActiveBorder": "#f8f8f8", + "tab.unfocusedActiveBorderTop": "#00000015", + "tab.unfocusedHoverBackground": "#6e76811a", + "terminalCursor.foreground": "#005FB8", + "terminal.foreground": "#3b3b3b", + "terminal.inactiveSelectionBackground": "#E5EBF1", + "terminal.tab.activeBorder": "#005fb8", + "textBlockQuote.background": "#f8f8f8", + "textBlockQuote.border": "#0000001a", + "textCodeBlock.background": "#f2f2f2", + "textLink.activeForeground": "#005FB8", + "textLink.foreground": "#005FB8", + "textSeparator.foreground": "#21262d", + "titleBar.activeBackground": "#f8f8f8", + "titleBar.activeForeground": "#1e1e1e", + "titleBar.border": "#0000001a", + "titleBar.inactiveBackground": "#f8f8f8", + "titleBar.inactiveForeground": "#8b949e", + "welcomePage.tileBackground": "#f3f3f3", + "widget.border": "#0000001a", + }, +} diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index 9a0d0d6dea..f73b79579f 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -1,6 +1,6 @@ { "$schema": "vscode://schemas/color-theme", - "name": "Light+ (default light)", + "name": "Light+", "include": "./light_vs.json", "tokenColors": [ // adds rules to the light vs rules { @@ -174,7 +174,10 @@ } }, { - "scope": "constant.character", + "scope": [ + "constant.character", + "constant.other.option" + ], "settings": { "foreground": "#0000ff" } diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index ae3ab6645b..2cfe0ec044 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -2,6 +2,7 @@ "$schema": "vscode://schemas/color-theme", "name": "Light (Visual Studio)", "colors": { + "checkbox.border": "#919191", "editor.background": "#FFFFFF", "editor.foreground": "#000000", "editor.inactiveSelectionBackground": "#E5EBF1", @@ -12,6 +13,7 @@ "activityBarBadge.background": "#007ACC", "sideBarTitle.foreground": "#6F6F6F", "list.hoverBackground": "#E8E8E8", + "menu.border": "#D4D4D4", "input.placeholderForeground": "#767676", "searchEditor.textInputBorder": "#CECECE", "settings.textInputBorder": "#CECECE", @@ -26,13 +28,16 @@ "notebook.selectedCellBackground": "#c8ddf150", "statusBarItem.errorBackground": "#c72e0f", "list.activeSelectionIconForeground": "#FFF", - "list.focusAndSelectionOutline": "#90C2F9" + "list.focusAndSelectionOutline": "#90C2F9", + "terminal.inactiveSelectionBackground": "#E5EBF1", + "widget.border": "#d4d4d4" }, "tokenColors": [ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#000000ff" @@ -57,7 +62,11 @@ } }, { - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "foreground": "#008000" } @@ -101,7 +110,7 @@ { "scope": "entity.other.attribute-name", "settings": { - "foreground": "#ff0000" + "foreground": "#e50000" } }, { @@ -326,7 +335,7 @@ "source.coffee.embedded" ], "settings": { - "foreground": "#ff0000" + "foreground": "#e50000" } }, { 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 bc56665a86..b8f95bf86a 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -32,6 +32,7 @@ "ports.iconRunningProcessForeground": "#369432", "activityBar.background": "#221a0f", "activityBar.foreground": "#d3af86", + "activityBarItem.profilesBackground": "#47351d", "sideBar.background": "#362712", "menu.background": "#362712", "menu.foreground": "#CCCCCC", @@ -60,7 +61,11 @@ } }, { - "scope": ["meta.embedded", "source.groovy.embedded"], + "scope": [ + "meta.embedded", + "source.groovy.embedded", + "string meta.image.inline.markdown" + ], "settings": { "foreground": "#d3af86" } @@ -76,7 +81,8 @@ "name": "Comments", "scope": [ "comment", - "punctuation.definition.comment" + "punctuation.definition.comment", + "string.quoted.docstring" ], "settings": { "foreground": "#a57a4c" diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index 27b9277f72..2ed1bcc96e 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -59,7 +59,8 @@ "terminal.ansiBrightBlue": "#819aff", // hue shifted #AE81FF "terminal.ansiBrightMagenta": "#AE81FF", "terminal.ansiBrightCyan": "#66D9EF", - "terminal.ansiBrightWhite": "#f8f8f2" + "terminal.ansiBrightWhite": "#f8f8f2", + "terminal.inactiveSelectionBackground": "#676b7140" }, "tokenColors": [ { @@ -77,8 +78,11 @@ } }, { - "name": "Comment", - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "fontStyle": "", "foreground": "#9A9B99" diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index 11e7caa3c3..2fddaa5972 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -61,7 +61,7 @@ "pickerGroup.foreground": "#75715E", "input.background": "#414339", "inputOption.activeBorder": "#75715E", - "focusBorder": "#75715E", + "focusBorder": "#99947c", "editorWidget.background": "#1e1f1c", "debugToolBar.background": "#1e1f1c", "diffEditor.insertedTextBackground": "#4b661680", // middle of #272822 and #a6e22e @@ -110,15 +110,19 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#F8F8F2" } }, { - "name": "Comment", - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "foreground": "#88846f" } diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 753116e27d..a01c73d3ec 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -9,7 +9,8 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#333333" @@ -19,7 +20,8 @@ "name": "Comments", "scope": [ "comment", - "punctuation.definition.comment" + "punctuation.definition.comment", + "string.quoted.docstring" ], "settings": { "fontStyle": "italic", @@ -475,7 +477,7 @@ } ], "colors": { - "focusBorder": "#A6B39B", + "focusBorder": "#9769dc", "pickerGroup.foreground": "#A6B39B", "pickerGroup.border": "#749351", "list.activeSelectionForeground": "#6c6c6c", @@ -499,7 +501,7 @@ "inputOption.activeBorder": "#adafb7", "dropdown.background": "#F5F5F5", "editor.findMatchBackground": "#BF9CAC", - "editor.findMatchHighlightBackground": "#edc9d8", + "editor.findMatchHighlightBackground": "#edc9d899", "peekViewEditor.matchHighlightBackground": "#C2DFE3", "peekViewTitle.background": "#F2F8FC", "peekViewEditor.background": "#F2F8FC", diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index f5bd596d10..cf0f69316b 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -69,15 +69,19 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#F8F8F8" } }, { - "name": "Comment", - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "fontStyle": "italic", "foreground": "#e7c0c0ff" diff --git a/extensions/theme-seti/build/update-icon-theme.js b/extensions/theme-seti/build/update-icon-theme.js index 412472af75..c3a550f50d 100644 --- a/extensions/theme-seti/build/update-icon-theme.js +++ b/extensions/theme-seti/build/update-icon-theme.js @@ -44,10 +44,15 @@ const nonBuiltInLanguages = { // { fileNames, extensions } // list of languagesId that inherit the icon from another language const inheritIconFromLanguage = { "jsonc": 'json', + "jsonl": 'json', "postcss": 'css', "django-html": 'html', "blade": 'php' -} +}; + +const ignoreExtAssociation = { + "properties": true +}; const FROM_DISK = true; // set to true to take content from a repo checked out next to the vscode repo @@ -399,7 +404,7 @@ exports.update = function () { if (!nonBuiltInLanguages[lang] && !inheritIconFromLanguage[lang]) { for (let i2 = 0; i2 < exts.length; i2++) { // remove the extension association, unless it is different from the preferred - if (ext2Def[exts[i2]] === preferredDef) { + if (ext2Def[exts[i2]] === preferredDef || ignoreExtAssociation[exts[i2]]) { delete ext2Def[exts[i2]]; } } diff --git a/extensions/theme-seti/cgmanifest.json b/extensions/theme-seti/cgmanifest.json index 919b27b7c9..b2b3694441 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": "2d10473b7575ec00c47eda751ea9caeec6b0b606" + "commitHash": "fd20793e5a75b350eab8d489165fb9b420df3f62" } }, "version": "0.1.0" diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index f0e4748699..88cb75699e 100644 Binary files a/extensions/theme-seti/icons/seti.woff and b/extensions/theme-seti/icons/seti.woff differ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 9c9ac36bfe..7c17d2f916 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -1609,8 +1609,6 @@ "cfm": "_coldfusion", "litcoffee": "_coffee", "config": "_config", - "cfg": "_config", - "conf": "_config", "cr": "_crystal", "ecr": "_crystal_embedded", "slang": "_crystal_embedded", @@ -1765,7 +1763,6 @@ "webp": "_image", "sublime-project": "_sublime", "sublime-workspace": "_sublime", - "fish": "_shell", "mov": "_video", "ogv": "_video", "webm": "_video", @@ -1808,7 +1805,6 @@ "direnv": "_config", "env": "_config", "static": "_config", - "editorconfig": "_config", "slugignore": "_config", "tmp": "_clock_1", "htaccess": "_config", @@ -1872,14 +1868,19 @@ "yarn.lock": "_yarn", "webpack.config.js": "_webpack", "webpack.config.cjs": "_webpack", + "webpack.config.ts": "_webpack", "webpack.config.build.js": "_webpack", "webpack.config.build.cjs": "_webpack", + "webpack.config.build.ts": "_webpack", "webpack.common.js": "_webpack", "webpack.common.cjs": "_webpack", + "webpack.common.ts": "_webpack", "webpack.dev.js": "_webpack", "webpack.dev.cjs": "_webpack", + "webpack.dev.ts": "_webpack", "webpack.prod.js": "_webpack", "webpack.prod.cjs": "_webpack", + "webpack.prod.ts": "_webpack", "license": "_license", "licence": "_license", "license.txt": "_license", @@ -1934,7 +1935,7 @@ "groovy": "_grails", "handlebars": "_mustache", "html": "_html_3", - "properties": "_java", + "properties": "_config", "java": "_java", "javascriptreact": "_react", "javascript": "_javascript", @@ -1992,6 +1993,7 @@ "scmp": "scmp_dark", "vala": "_vala", "vue": "_vue", + "jsonl": "_json", "postcss": "_css", "django-html": "_html_3", "blade": "_php" @@ -2026,8 +2028,6 @@ "cfm": "_coldfusion_light", "litcoffee": "_coffee_light", "config": "_config_light", - "cfg": "_config_light", - "conf": "_config_light", "cr": "_crystal_light", "ecr": "_crystal_embedded_light", "slang": "_crystal_embedded_light", @@ -2182,7 +2182,6 @@ "webp": "_image_light", "sublime-project": "_sublime_light", "sublime-workspace": "_sublime_light", - "fish": "_shell_light", "mov": "_video_light", "ogv": "_video_light", "webm": "_video_light", @@ -2225,7 +2224,6 @@ "direnv": "_config_light", "env": "_config_light", "static": "_config_light", - "editorconfig": "_config_light", "slugignore": "_config_light", "tmp": "_clock_1_light", "htaccess": "_config_light", @@ -2259,7 +2257,7 @@ "groovy": "_grails_light", "handlebars": "_mustache_light", "html": "_html_3_light", - "properties": "_java_light", + "properties": "_config_light", "java": "_java_light", "javascriptreact": "_react_light", "javascript": "_javascript_light", @@ -2316,6 +2314,7 @@ "notebook": "notebook", "scmp": "scmp", "vue": "_vue_light", + "jsonl": "_json_light", "postcss": "_css_light", "django-html": "_html_3_light", "blade": "_php_light" @@ -2370,14 +2369,19 @@ "yarn.lock": "_yarn_light", "webpack.config.js": "_webpack_light", "webpack.config.cjs": "_webpack_light", + "webpack.config.ts": "_webpack_light", "webpack.config.build.js": "_webpack_light", "webpack.config.build.cjs": "_webpack_light", + "webpack.config.build.ts": "_webpack_light", "webpack.common.js": "_webpack_light", "webpack.common.cjs": "_webpack_light", + "webpack.common.ts": "_webpack_light", "webpack.dev.js": "_webpack_light", "webpack.dev.cjs": "_webpack_light", + "webpack.dev.ts": "_webpack_light", "webpack.prod.js": "_webpack_light", "webpack.prod.cjs": "_webpack_light", + "webpack.prod.ts": "_webpack_light", "license": "_license_light", "licence": "_license_light", "license.txt": "_license_light", @@ -2403,5 +2407,5 @@ "Schema Compare": "scmp" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/2d10473b7575ec00c47eda751ea9caeec6b0b606" + "version": "https://github.com/jesseweed/seti-ui/commit/fd20793e5a75b350eab8d489165fb9b420df3f62" } 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 2febfd9bca..3abb94bd42 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -9,15 +9,19 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#839496" } }, { - "name": "Comment", - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "fontStyle": "italic", "foreground": "#586E75" 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 d13c42c0bb..19ccf4fc92 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -9,15 +9,19 @@ { "scope": [ "meta.embedded", - "source.groovy.embedded" + "source.groovy.embedded", + "string meta.image.inline.markdown" ], "settings": { "foreground": "#657B83" } }, { - "name": "Comment", - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "fontStyle": "italic", "foreground": "#93A1A1" @@ -326,7 +330,7 @@ "colors": { // Base // "foreground": "", - "focusBorder": "#D3AF86", + "focusBorder": "#b49471", // "contrastActiveBorder": "", // "contrastBorder": "", // "widget.shadow": "", 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 b37fd6b008..5591d39f1a 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 @@ -15,8 +15,8 @@ "editor.foreground": "#ffffff", "editor.selectionBackground": "#003f8e", "minimap.selectionHighlight": "#003f8e", - "editor.lineHighlightBackground": "#00346e", - "editorLineNumber.activeForeground": "#949494", + "editor.lineHighlightBackground": "#00346e", + "editorLineNumber.activeForeground": "#949494", "editorCursor.foreground": "#ffffff", "editorWhitespace.foreground": "#404f7d", "editorWidget.background": "#001c40", @@ -36,6 +36,7 @@ "statusBar.noFolderBackground": "#001126", "statusBar.debuggingBackground": "#001126", "activityBar.background": "#001733", + "activityBarItem.profilesBackground": "#003271", "progressBar.background": "#bbdaffcc", "badge.background": "#bbdaffcc", "badge.foreground": "#001733", @@ -65,15 +66,23 @@ } }, { - "scope": ["meta.embedded", "source.groovy.embedded", "meta.jsx.children"], + "scope": [ + "meta.embedded", + "source.groovy.embedded", + "meta.jsx.children", + "string meta.image.inline.markdown" + ], "settings": { //"background": "#002451", "foreground": "#FFFFFF" } }, { - "name": "Comment", - "scope": "comment", + "name": "Comments", + "scope": [ + "comment", + "string.quoted.docstring" + ], "settings": { "foreground": "#7285B7" } diff --git a/extensions/tsconfig.base.json b/extensions/tsconfig.base.json index 5651d57263..2e4a6009a0 100644 --- a/extensions/tsconfig.base.json +++ b/extensions/tsconfig.base.json @@ -20,7 +20,10 @@ "ES2020.Promise", "ES2020.String", "ES2020.Symbol.WellKnown", - "ES2020.Intl" + "ES2020.Intl", + "ES2021.Promise", + "ES2021.String", + "ES2021.WeakRef" ], "module": "commonjs", "strict": true, diff --git a/extensions/types/vscode-mssql.d.ts b/extensions/types/vscode-mssql.d.ts index 4e2c166450..d31f0987d0 100644 --- a/extensions/types/vscode-mssql.d.ts +++ b/extensions/types/vscode-mssql.d.ts @@ -1,6 +1,6 @@ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. + * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ diff --git a/extensions/typescript-language-features/.eslintrc.json b/extensions/typescript-language-features/.eslintrc.json deleted file mode 100644 index ce28ab7a81..0000000000 --- a/extensions/typescript-language-features/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rules": { - "@typescript-eslint/explicit-function-return-type": ["off"], - "@typescript-eslint/await-thenable": ["off"], - "@typescript-eslint/no-unsafe-assignment": "off" - } -} diff --git a/extensions/typescript-language-features/src/experimentationService.ts b/extensions/typescript-language-features/src/experimentationService.ts deleted file mode 100644 index a0693a56b7..0000000000 --- a/extensions/typescript-language-features/src/experimentationService.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import VsCodeTelemetryReporter from '@vscode/extension-telemetry'; -import * as tas from 'vscode-tas-client'; - -interface ExperimentTypes { - // None for now. -} - -export class ExperimentationService implements vscode.Disposable { - private _experimentationServicePromise: Promise; - private _telemetryReporter: ExperimentTelemetryReporter; - - constructor(private readonly _extensionContext: vscode.ExtensionContext) { - this._telemetryReporter = new ExperimentTelemetryReporter(_extensionContext); - this._experimentationServicePromise = this.createExperimentationService(); - } - - public async getTreatmentVariable(name: K, defaultValue: ExperimentTypes[K]): Promise { - const experimentationService = await this._experimentationServicePromise; - try { - const treatmentVariable = experimentationService.getTreatmentVariableAsync('vscode', name, /*checkCache*/ true) as ExperimentTypes[K]; - return treatmentVariable; - } catch { - return defaultValue; - } - } - - private async createExperimentationService(): Promise { - let targetPopulation: tas.TargetPopulation; - switch (vscode.env.uriScheme) { - case 'vscode': - targetPopulation = tas.TargetPopulation.Public; - case 'vscode-insiders': - targetPopulation = tas.TargetPopulation.Insiders; - case 'vscode-exploration': - targetPopulation = tas.TargetPopulation.Internal; - case 'code-oss': - targetPopulation = tas.TargetPopulation.Team; - default: - targetPopulation = tas.TargetPopulation.Public; - } - - const id = this._extensionContext.extension.id; - const version = this._extensionContext.extension.packageJSON.version || ''; - const experimentationService = tas.getExperimentationService(id, version, targetPopulation, this._telemetryReporter, this._extensionContext.globalState); - await experimentationService.initialFetch; - return experimentationService; - } - - - /** - * @inheritdoc - */ - public dispose() { - this._telemetryReporter.dispose(); - } -} - -export class ExperimentTelemetryReporter - implements tas.IExperimentationTelemetry, vscode.Disposable { - private _sharedProperties: Record = {}; - private _reporter: VsCodeTelemetryReporter; - constructor(ctxt: vscode.ExtensionContext) { - const extension = ctxt.extension; - const packageJSON = extension.packageJSON; - this._reporter = new VsCodeTelemetryReporter( - extension.id, - packageJSON.version || '', - packageJSON.aiKey || ''); - - } - - setSharedProperty(name: string, value: string): void { - this._sharedProperties[name] = value; - } - - postEvent(eventName: string, props: Map): void { - const propsObject = { - ...this._sharedProperties, - ...Object.fromEntries(props), - }; - this._reporter.sendTelemetryEvent(eventName, propsObject); - } - - dispose() { - this._reporter.dispose(); - } -} diff --git a/extensions/vscode-api-tests/.eslintrc.json b/extensions/vscode-api-tests/.eslintrc.json deleted file mode 100644 index ce28ab7a81..0000000000 --- a/extensions/vscode-api-tests/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rules": { - "@typescript-eslint/explicit-function-return-type": ["off"], - "@typescript-eslint/await-thenable": ["off"], - "@typescript-eslint/no-unsafe-assignment": "off" - } -} diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts deleted file mode 100644 index 2b9effc8ff..0000000000 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactiveWindow.test.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import 'mocha'; -import * as vscode from 'vscode'; -import { disposeAll } from '../utils'; -import { Kernel, saveAllFilesAndCloseAll } from './notebook.test'; - -export type INativeInteractiveWindow = { notebookUri: vscode.Uri; inputUri: vscode.Uri; notebookEditor: vscode.NotebookEditor }; - -async function createInteractiveWindow(kernel: Kernel) { - const { notebookEditor } = (await vscode.commands.executeCommand( - 'interactive.open', - // Keep focus on the owning file if there is one - { viewColumn: vscode.ViewColumn.Beside, preserveFocus: false }, - undefined, - `vscode.vscode-api-tests/${kernel.controller.id}`, - undefined - )) as unknown as INativeInteractiveWindow; - - return notebookEditor; -} - -async function addCell(code: string, notebook: vscode.NotebookDocument) { - const cell = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, code, 'typescript'); - const edit = vscode.NotebookEdit.insertCells(notebook.cellCount, [cell]); - const workspaceEdit = new vscode.WorkspaceEdit(); - workspaceEdit.set(notebook.uri, [edit]); - await vscode.workspace.applyEdit(workspaceEdit); - return notebook.cellAt(notebook.cellCount - 1); -} - -async function addCellAndRun(code: string, notebook: vscode.NotebookDocument, i: number) { - const cell = await addCell(code, notebook); - await vscode.commands.executeCommand('notebook.cell.execute', { start: i, end: i + 1 }); - assert.strictEqual(cell.outputs.length, 1, 'execute failed'); - return cell; -} - - -(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Interactive Window', function () { - - const testDisposables: vscode.Disposable[] = []; - let defaultKernel: Kernel; - let secondKernel: Kernel; - - setup(async function () { - defaultKernel = new Kernel('mainKernel', 'Notebook Default Kernel', 'interactive'); - secondKernel = new Kernel('secondKernel', 'Notebook Secondary Kernel', 'interactive'); - testDisposables.push(defaultKernel.controller); - testDisposables.push(secondKernel.controller); - await saveAllFilesAndCloseAll(); - }); - - teardown(async function () { - disposeAll(testDisposables); - testDisposables.length = 0; - await saveAllFilesAndCloseAll(); - }); - - test('Can open an interactive window', async () => { - assert.ok(vscode.workspace.workspaceFolders); - const notebookEditor = await createInteractiveWindow(defaultKernel); - assert.ok(notebookEditor); - - // Try adding a cell and running it. - await addCell('print foo', notebookEditor.notebook); - - assert.strictEqual(notebookEditor.notebook.cellCount, 1); - assert.strictEqual(notebookEditor.notebook.cellAt(0).kind, vscode.NotebookCellKind.Code); - }); - - test('Interactive window scrolls after execute', async () => { - assert.ok(vscode.workspace.workspaceFolders); - const notebookEditor = await createInteractiveWindow(defaultKernel); - assert.ok(notebookEditor); - - // Run and add a bunch of cells - for (let i = 0; i < 10; i++) { - await addCellAndRun(`print ${i}`, notebookEditor.notebook, i); - } - - // Verify visible range has the last cell - assert.strictEqual(notebookEditor.visibleRanges[notebookEditor.visibleRanges.length - 1].end, notebookEditor.notebook.cellCount, `Last cell is not visible`); - - }); - - test('Interactive window has the correct kernel', async () => { - assert.ok(vscode.workspace.workspaceFolders); - const notebookEditor = await createInteractiveWindow(defaultKernel); - assert.ok(notebookEditor); - - await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); - - // Create a new interactive window with a different kernel - const notebookEditor2 = await createInteractiveWindow(secondKernel); - assert.ok(notebookEditor2); - - // Verify the kernel is the secondary one - await addCellAndRun(`print`, notebookEditor2.notebook, 0); - - assert.strictEqual(secondKernel.associatedNotebooks.has(notebookEditor2.notebook.uri.toString()), true, `Secondary kernel was not set as the kernel for the interactive window`); - - }); -}); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.watcher.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.watcher.test.ts deleted file mode 100644 index 48b692eaad..0000000000 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.watcher.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as vscode from 'vscode'; -import { TestFS } from '../memfs'; -import { assertNoRpc } from '../utils'; - -suite('vscode API - workspace-watcher', () => { - - interface IWatchRequest { - uri: vscode.Uri; - options: { recursive: boolean; excludes: string[] }; - } - - class WatcherTestFs extends TestFS { - - private _onDidWatch = new vscode.EventEmitter(); - readonly onDidWatch = this._onDidWatch.event; - - override watch(uri: vscode.Uri, options: { recursive: boolean; excludes: string[] }): vscode.Disposable { - this._onDidWatch.fire({ uri, options }); - - return super.watch(uri, options); - } - } - - teardown(assertNoRpc); - - test('createFileSystemWatcher', async function () { - const fs = new WatcherTestFs('watcherTest', false); - vscode.workspace.registerFileSystemProvider('watcherTest', fs); - - function onDidWatchPromise() { - const onDidWatchPromise = new Promise(resolve => { - fs.onDidWatch(request => resolve(request)); - }); - - return onDidWatchPromise; - } - - // Non-recursive - let watchUri = vscode.Uri.from({ scheme: 'watcherTest', path: '/somePath/folder' }); - const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(watchUri, '*.txt')); - let request = await onDidWatchPromise(); - - assert.strictEqual(request.uri.toString(), watchUri.toString()); - assert.strictEqual(request.options.recursive, false); - - watcher.dispose(); - - // Recursive - watchUri = vscode.Uri.from({ scheme: 'watcherTest', path: '/somePath/folder' }); - vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(watchUri, '**/*.txt')); - request = await onDidWatchPromise(); - - assert.strictEqual(request.uri.toString(), watchUri.toString()); - assert.strictEqual(request.options.recursive, true); - }); -}); diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/COMMIT_EDITMSG b/extensions/vscode-colorize-tests/test/colorize-fixtures/COMMIT_EDITMSG deleted file mode 100644 index e81d55d0ed..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/COMMIT_EDITMSG +++ /dev/null @@ -1,13 +0,0 @@ -This is the summary line. It can't be too long. -After I can write a much more detailed description without quite the same restrictions on length. - -# Please enter the commit message for your changes. Lines starting -# with '#' will be ignored, and an empty message aborts the commit. -# On branch master -# Your branch is up-to-date with 'origin/master'. -# -# Changes to be committed: -# deleted: README.md -# modified: index.less -# new file: spec/COMMIT_EDITMSG -# \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/git-rebase-todo b/extensions/vscode-colorize-tests/test/colorize-fixtures/git-rebase-todo deleted file mode 100644 index 3efe501eb9..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/git-rebase-todo +++ /dev/null @@ -1,15 +0,0 @@ -pick 1fc6c95 Patch A -squash fa39187 Something to add to patch A -pick 7b36971 Something to move before patch B -pick 6b2481b Patch B -fixup c619268 A fix for Patch B -edit dd1475d Something I want to split -reword 4ca2acc i cant' typ goods - -# Commands: -# p, pick = use commit -# r, reword = use commit, but edit the commit message -# e, edit = use commit, but stop for amending -# s, squash = use commit, but meld into previous commit -# f, fixup = like "squash", but discard this commit's log message -# x, exec = run command (the rest of the line) using shell diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.bib b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.bib deleted file mode 100644 index 11e84a4741..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.bib +++ /dev/null @@ -1,22 +0,0 @@ -% a sample bibliography file -% - -@article{small, -author = {Freely, I.P.}, -title = {A small paper}, -journal = {The journal of small papers}, -year = 1997, -volume = {-1}, -note = {to appear}, -} - -@article{big, -author = {Jass, Hugh}, -title = {A big paper}, -journal = {The journal of big papers}, -year = 7991, -volume = {MCMXCVII}, -} - -% The authors mentioned here are almost, but not quite, -% entirely unrelated to Matt Groening. diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.diff b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.diff deleted file mode 100644 index 256b719212..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.diff +++ /dev/null @@ -1,7 +0,0 @@ -diff --git a/helloworld.txt b/helloworld.txt -index e4f37c4..557db03 100644 ---- a/helloworld.txt -+++ b/helloworld.txt -@@ -1 +1 @@ --Hello world -+Hello World \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.rst b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.rst deleted file mode 100644 index 749bf14f80..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.rst +++ /dev/null @@ -1,98 +0,0 @@ -*italics*, **bold**, ``literal``. - -1. A list -2. With items - - With sub-lists ... - - ... of things. -3. Other things - -definition list - A list of terms and their definition - -Literal block:: - - x = 2 + 3 - - -Section separators are all interchangeable. - -===== -Title -===== - --------- -Subtitle --------- - -Section 1 -========= - -Section 2 ---------- - -Section 3 -~~~~~~~~~ - -| Keeping line -| breaks. - - -+-------------+--------------+ -| Fancy table | with columns | -+=============+==============+ -| row 1, col 1| row 1, col 2 | -+-------------+--------------+ - -============ ============ -Simple table with columns -============ ============ -row 1, col1 row 1, col 2 -============ ============ - -Block quote is indented. - - This space intentionally not important. - -Doctest block - ->>> 2 +3 -5 - -A footnote [#note]_. - -.. [#note] https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#footnotes - - -Citation [cite]_. - -.. [cite] https://bing.com - -a simple link_. - -A `fancier link`_ . - -.. _link: https://docutils.sourceforge.io/ -.. _fancier link: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html - - -An `inline link `__ . - -.. image:: https://code.visualstudio.com/assets/images/code-stable.png - -.. function: example() - :module: mod - - -:sub:`subscript` -:sup:`superscript` - -.. This is a comment. - -.. - And a bigger, - longer comment. - - -A |subst| of something. - -.. |subst| replace:: substitution diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.sty b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.sty deleted file mode 100644 index 9db6615496..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.sty +++ /dev/null @@ -1,20 +0,0 @@ -\message{Document style option `aer.sty' (29 May 1993) for LaTeX 2.09.} -\textwidth=28pc -\textheight=46pc - -\def\bysame{\leavevmode\hbox to\leftmargin{\leaders\hrule height 3pt depth -2.5pt\hfill\,\,}} - -\def\thebibliography#1{\section*{\refname\@mkboth - {\uppercase{\refname}}{\uppercase{\refname}}}\list - {\@biblabel{\arabic{enumiv}}}{\labelwidth=12pt - \labelsep=0pt - \leftmargin\labelwidth - \advance\leftmargin\labelsep - \itemsep=0pt\parsep=0pt - \usecounter{enumiv}% - \let\p@enumiv\@empty - \def\theenumiv{\arabic{enumiv}}}% - \def\newblock{\hskip .11em plus.33em minus.07em}% - \sloppy\clubpenalty4000\widowpenalty4000 - \raggedright - \sfcode`\.=1000\relax} \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.tex b/extensions/vscode-colorize-tests/test/colorize-fixtures/test.tex deleted file mode 100644 index 39e75e9d7d..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-fixtures/test.tex +++ /dev/null @@ -1,20 +0,0 @@ -\documentclass[12pt]{article} -\usepackage{lingmacros} -\usepackage{tree-dvips} -\begin{document} - -\section*{Notes for My Paper} - -Don't forget to include examples of topicalization. -They look like this: - -{\small -\enumsentence{Topicalization from sentential subject:\\ -\shortex{7}{a John$_i$ [a & kltukl & [el & - {\bf l-}oltoir & er & ngii$_i$ & a Mary]]} -{ & {\bf R-}clear & {\sc comp} & - {\bf IR}.{\sc 3s}-love & P & him & } -{John, (it's) clear that Mary loves (him).}} -} - -\end{document} \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/COMMIT_EDITMSG.json b/extensions/vscode-colorize-tests/test/colorize-results/COMMIT_EDITMSG.json deleted file mode 100644 index a3310a9c1d..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-results/COMMIT_EDITMSG.json +++ /dev/null @@ -1,278 +0,0 @@ -[ - { - "c": "This is the summary line. It can't be too long.", - "t": "text.git-commit meta.scope.message.git-commit meta.scope.subject.git-commit", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "After I can write a much more detailed description without quite the same restrictions on length.", - "t": "text.git-commit meta.scope.message.git-commit", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " Please enter the commit message for your changes. Lines starting", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " with '#' will be ignored, and an empty message aborts the commit.", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " On branch master", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " Your branch is up-to-date with 'origin/master'.", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " Changes to be committed:", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "\t", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "deleted: README.md", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit markup.deleted.git-commit", - "r": { - "dark_plus": "markup.deleted: #CE9178", - "light_plus": "markup.deleted: #A31515", - "dark_vs": "markup.deleted: #CE9178", - "light_vs": "markup.deleted: #A31515", - "hc_black": "markup.deleted: #CE9178", - "hc_light": "markup.deleted: #5A5A5A" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "\t", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "modified: index.less", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit markup.changed.git-commit", - "r": { - "dark_plus": "markup.changed: #569CD6", - "light_plus": "markup.changed: #0451A5", - "dark_vs": "markup.changed: #569CD6", - "light_vs": "markup.changed: #0451A5", - "hc_black": "markup.changed: #569CD6", - "hc_light": "markup.changed: #0451A5" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "\t", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "new file: spec/COMMIT_EDITMSG", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit markup.inserted.git-commit", - "r": { - "dark_plus": "markup.inserted: #B5CEA8", - "light_plus": "markup.inserted: #098658", - "dark_vs": "markup.inserted: #B5CEA8", - "light_vs": "markup.inserted: #098658", - "hc_black": "markup.inserted: #B5CEA8", - "hc_light": "markup.inserted: #096D48" - } - }, - { - "c": "#", - "t": "text.git-commit meta.scope.metadata.git-commit comment.line.number-sign.git-commit punctuation.definition.comment.git-commit", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - } -] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/git-rebase-todo.json b/extensions/vscode-colorize-tests/test/colorize-results/git-rebase-todo.json deleted file mode 100644 index fcfd8c30f9..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-results/git-rebase-todo.json +++ /dev/null @@ -1,590 +0,0 @@ -[ - { - "c": "pick", - "t": "text.git-rebase meta.commit-command.git-rebase support.function.git-rebase", - "r": { - "dark_plus": "support.function.git-rebase: #9CDCFE", - "light_plus": "support.function.git-rebase: #0451A5", - "dark_vs": "support.function.git-rebase: #9CDCFE", - "light_vs": "support.function.git-rebase: #0451A5", - "hc_black": "support.function.git-rebase: #D4D4D4", - "hc_light": "support.function.git-rebase: #0451A5" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "1fc6c95", - "t": "text.git-rebase meta.commit-command.git-rebase constant.sha.git-rebase", - "r": { - "dark_plus": "constant.sha.git-rebase: #B5CEA8", - "light_plus": "constant.sha.git-rebase: #098658", - "dark_vs": "constant.sha.git-rebase: #B5CEA8", - "light_vs": "constant.sha.git-rebase: #098658", - "hc_black": "constant.sha.git-rebase: #B5CEA8", - "hc_light": "constant.sha.git-rebase: #096D48" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Patch A", - "t": "text.git-rebase meta.commit-command.git-rebase meta.commit-message.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "squash", - "t": "text.git-rebase meta.commit-command.git-rebase support.function.git-rebase", - "r": { - "dark_plus": "support.function.git-rebase: #9CDCFE", - "light_plus": "support.function.git-rebase: #0451A5", - "dark_vs": "support.function.git-rebase: #9CDCFE", - "light_vs": "support.function.git-rebase: #0451A5", - "hc_black": "support.function.git-rebase: #D4D4D4", - "hc_light": "support.function.git-rebase: #0451A5" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "fa39187", - "t": "text.git-rebase meta.commit-command.git-rebase constant.sha.git-rebase", - "r": { - "dark_plus": "constant.sha.git-rebase: #B5CEA8", - "light_plus": "constant.sha.git-rebase: #098658", - "dark_vs": "constant.sha.git-rebase: #B5CEA8", - "light_vs": "constant.sha.git-rebase: #098658", - "hc_black": "constant.sha.git-rebase: #B5CEA8", - "hc_light": "constant.sha.git-rebase: #096D48" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Something to add to patch A", - "t": "text.git-rebase meta.commit-command.git-rebase meta.commit-message.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "pick", - "t": "text.git-rebase meta.commit-command.git-rebase support.function.git-rebase", - "r": { - "dark_plus": "support.function.git-rebase: #9CDCFE", - "light_plus": "support.function.git-rebase: #0451A5", - "dark_vs": "support.function.git-rebase: #9CDCFE", - "light_vs": "support.function.git-rebase: #0451A5", - "hc_black": "support.function.git-rebase: #D4D4D4", - "hc_light": "support.function.git-rebase: #0451A5" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "7b36971", - "t": "text.git-rebase meta.commit-command.git-rebase constant.sha.git-rebase", - "r": { - "dark_plus": "constant.sha.git-rebase: #B5CEA8", - "light_plus": "constant.sha.git-rebase: #098658", - "dark_vs": "constant.sha.git-rebase: #B5CEA8", - "light_vs": "constant.sha.git-rebase: #098658", - "hc_black": "constant.sha.git-rebase: #B5CEA8", - "hc_light": "constant.sha.git-rebase: #096D48" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Something to move before patch B", - "t": "text.git-rebase meta.commit-command.git-rebase meta.commit-message.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "pick", - "t": "text.git-rebase meta.commit-command.git-rebase support.function.git-rebase", - "r": { - "dark_plus": "support.function.git-rebase: #9CDCFE", - "light_plus": "support.function.git-rebase: #0451A5", - "dark_vs": "support.function.git-rebase: #9CDCFE", - "light_vs": "support.function.git-rebase: #0451A5", - "hc_black": "support.function.git-rebase: #D4D4D4", - "hc_light": "support.function.git-rebase: #0451A5" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "6b2481b", - "t": "text.git-rebase meta.commit-command.git-rebase constant.sha.git-rebase", - "r": { - "dark_plus": "constant.sha.git-rebase: #B5CEA8", - "light_plus": "constant.sha.git-rebase: #098658", - "dark_vs": "constant.sha.git-rebase: #B5CEA8", - "light_vs": "constant.sha.git-rebase: #098658", - "hc_black": "constant.sha.git-rebase: #B5CEA8", - "hc_light": "constant.sha.git-rebase: #096D48" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Patch B", - "t": "text.git-rebase meta.commit-command.git-rebase meta.commit-message.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "fixup", - "t": "text.git-rebase meta.commit-command.git-rebase support.function.git-rebase", - "r": { - "dark_plus": "support.function.git-rebase: #9CDCFE", - "light_plus": "support.function.git-rebase: #0451A5", - "dark_vs": "support.function.git-rebase: #9CDCFE", - "light_vs": "support.function.git-rebase: #0451A5", - "hc_black": "support.function.git-rebase: #D4D4D4", - "hc_light": "support.function.git-rebase: #0451A5" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "c619268", - "t": "text.git-rebase meta.commit-command.git-rebase constant.sha.git-rebase", - "r": { - "dark_plus": "constant.sha.git-rebase: #B5CEA8", - "light_plus": "constant.sha.git-rebase: #098658", - "dark_vs": "constant.sha.git-rebase: #B5CEA8", - "light_vs": "constant.sha.git-rebase: #098658", - "hc_black": "constant.sha.git-rebase: #B5CEA8", - "hc_light": "constant.sha.git-rebase: #096D48" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "A fix for Patch B", - "t": "text.git-rebase meta.commit-command.git-rebase meta.commit-message.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "edit", - "t": "text.git-rebase meta.commit-command.git-rebase support.function.git-rebase", - "r": { - "dark_plus": "support.function.git-rebase: #9CDCFE", - "light_plus": "support.function.git-rebase: #0451A5", - "dark_vs": "support.function.git-rebase: #9CDCFE", - "light_vs": "support.function.git-rebase: #0451A5", - "hc_black": "support.function.git-rebase: #D4D4D4", - "hc_light": "support.function.git-rebase: #0451A5" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "dd1475d", - "t": "text.git-rebase meta.commit-command.git-rebase constant.sha.git-rebase", - "r": { - "dark_plus": "constant.sha.git-rebase: #B5CEA8", - "light_plus": "constant.sha.git-rebase: #098658", - "dark_vs": "constant.sha.git-rebase: #B5CEA8", - "light_vs": "constant.sha.git-rebase: #098658", - "hc_black": "constant.sha.git-rebase: #B5CEA8", - "hc_light": "constant.sha.git-rebase: #096D48" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Something I want to split", - "t": "text.git-rebase meta.commit-command.git-rebase meta.commit-message.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "reword", - "t": "text.git-rebase meta.commit-command.git-rebase support.function.git-rebase", - "r": { - "dark_plus": "support.function.git-rebase: #9CDCFE", - "light_plus": "support.function.git-rebase: #0451A5", - "dark_vs": "support.function.git-rebase: #9CDCFE", - "light_vs": "support.function.git-rebase: #0451A5", - "hc_black": "support.function.git-rebase: #D4D4D4", - "hc_light": "support.function.git-rebase: #0451A5" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "4ca2acc", - "t": "text.git-rebase meta.commit-command.git-rebase constant.sha.git-rebase", - "r": { - "dark_plus": "constant.sha.git-rebase: #B5CEA8", - "light_plus": "constant.sha.git-rebase: #098658", - "dark_vs": "constant.sha.git-rebase: #B5CEA8", - "light_vs": "constant.sha.git-rebase: #098658", - "hc_black": "constant.sha.git-rebase: #B5CEA8", - "hc_light": "constant.sha.git-rebase: #096D48" - } - }, - { - "c": " ", - "t": "text.git-rebase meta.commit-command.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "i cant' typ goods", - "t": "text.git-rebase meta.commit-command.git-rebase meta.commit-message.git-rebase", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "#", - "t": "text.git-rebase comment.line.number-sign.git-rebase punctuation.definition.comment.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " Commands:", - "t": "text.git-rebase comment.line.number-sign.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-rebase comment.line.number-sign.git-rebase punctuation.definition.comment.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " p, pick = use commit", - "t": "text.git-rebase comment.line.number-sign.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-rebase comment.line.number-sign.git-rebase punctuation.definition.comment.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " r, reword = use commit, but edit the commit message", - "t": "text.git-rebase comment.line.number-sign.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-rebase comment.line.number-sign.git-rebase punctuation.definition.comment.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " e, edit = use commit, but stop for amending", - "t": "text.git-rebase comment.line.number-sign.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-rebase comment.line.number-sign.git-rebase punctuation.definition.comment.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " s, squash = use commit, but meld into previous commit", - "t": "text.git-rebase comment.line.number-sign.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-rebase comment.line.number-sign.git-rebase punctuation.definition.comment.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " f, fixup = like \"squash\", but discard this commit's log message", - "t": "text.git-rebase comment.line.number-sign.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "#", - "t": "text.git-rebase comment.line.number-sign.git-rebase punctuation.definition.comment.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " x, exec = run command (the rest of the line) using shell", - "t": "text.git-rebase comment.line.number-sign.git-rebase", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - } -] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_bib.json b/extensions/vscode-colorize-tests/test/colorize-results/test_bib.json deleted file mode 100644 index 5cce8c92d7..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_bib.json +++ /dev/null @@ -1,1202 +0,0 @@ -[ - { - "c": "% a sample bibliography file", - "t": "text.bibtex comment.block.bibtex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "%", - "t": "text.bibtex comment.block.bibtex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "@", - "t": "text.bibtex meta.entry.braces.bibtex keyword.other.entry-type.bibtex punctuation.definition.keyword.bibtex", - "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85" - } - }, - { - "c": "article", - "t": "text.bibtex meta.entry.braces.bibtex keyword.other.entry-type.bibtex", - "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex punctuation.section.entry.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "small", - "t": "text.bibtex meta.entry.braces.bibtex entity.name.type.entry-key.bibtex", - "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", - "hc_light": "entity.name.type: #185E73" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "author", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Freely, I.P.", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "title", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "A small paper", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "journal", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "The journal of small papers", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "year", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "1997", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex constant.numeric.bibtex", - "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", - "hc_light": "constant.numeric: #096D48" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "volume", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "-1", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "note", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "to appear", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex punctuation.section.entry.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "@", - "t": "text.bibtex meta.entry.braces.bibtex keyword.other.entry-type.bibtex punctuation.definition.keyword.bibtex", - "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85" - } - }, - { - "c": "article", - "t": "text.bibtex meta.entry.braces.bibtex keyword.other.entry-type.bibtex", - "r": { - "dark_plus": "keyword: #569CD6", - "light_plus": "keyword: #0000FF", - "dark_vs": "keyword: #569CD6", - "light_vs": "keyword: #0000FF", - "hc_black": "keyword: #569CD6", - "hc_light": "keyword: #0F4A85" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex punctuation.section.entry.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "big", - "t": "text.bibtex meta.entry.braces.bibtex entity.name.type.entry-key.bibtex", - "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", - "hc_light": "entity.name.type: #185E73" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "author", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Jass, Hugh", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "title", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "A big paper", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "journal", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "The journal of big papers", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "year", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "7991", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex constant.numeric.bibtex", - "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", - "hc_light": "constant.numeric: #096D48" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "volume", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex support.function.key.bibtex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.separator.key-value.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.begin.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "MCMXCVII", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex meta.key-assignment.bibtex punctuation.definition.string.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ",", - "t": "text.bibtex meta.entry.braces.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.bibtex meta.entry.braces.bibtex punctuation.section.entry.end.bibtex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "% The authors mentioned here are almost, but not quite,", - "t": "text.bibtex comment.block.bibtex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "% entirely unrelated to Matt Groening.", - "t": "text.bibtex comment.block.bibtex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - } -] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_diff.json b/extensions/vscode-colorize-tests/test/colorize-results/test_diff.json deleted file mode 100644 index 45be213c86..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_diff.json +++ /dev/null @@ -1,182 +0,0 @@ -[ - { - "c": "diff --git a/helloworld.txt b/helloworld.txt", - "t": "source.diff meta.diff.header.git", - "r": { - "dark_plus": "meta.diff.header: #569CD6", - "light_plus": "meta.diff.header: #000080", - "dark_vs": "meta.diff.header: #569CD6", - "light_vs": "meta.diff.header: #000080", - "hc_black": "meta.diff.header: #000080", - "hc_light": "meta.diff.header: #062F4A" - } - }, - { - "c": "index e4f37c4..557db03 100644", - "t": "source.diff meta.diff.index.git", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "---", - "t": "source.diff meta.diff.header.from-file punctuation.definition.from-file.diff", - "r": { - "dark_plus": "meta.diff.header: #569CD6", - "light_plus": "meta.diff.header: #000080", - "dark_vs": "meta.diff.header: #569CD6", - "light_vs": "meta.diff.header: #000080", - "hc_black": "meta.diff.header: #000080", - "hc_light": "meta.diff.header: #062F4A" - } - }, - { - "c": " a/helloworld.txt", - "t": "source.diff meta.diff.header.from-file", - "r": { - "dark_plus": "meta.diff.header: #569CD6", - "light_plus": "meta.diff.header: #000080", - "dark_vs": "meta.diff.header: #569CD6", - "light_vs": "meta.diff.header: #000080", - "hc_black": "meta.diff.header: #000080", - "hc_light": "meta.diff.header: #062F4A" - } - }, - { - "c": "+++", - "t": "source.diff meta.diff.header.to-file punctuation.definition.to-file.diff", - "r": { - "dark_plus": "meta.diff.header: #569CD6", - "light_plus": "meta.diff.header: #000080", - "dark_vs": "meta.diff.header: #569CD6", - "light_vs": "meta.diff.header: #000080", - "hc_black": "meta.diff.header: #000080", - "hc_light": "meta.diff.header: #062F4A" - } - }, - { - "c": " b/helloworld.txt", - "t": "source.diff meta.diff.header.to-file", - "r": { - "dark_plus": "meta.diff.header: #569CD6", - "light_plus": "meta.diff.header: #000080", - "dark_vs": "meta.diff.header: #569CD6", - "light_vs": "meta.diff.header: #000080", - "hc_black": "meta.diff.header: #000080", - "hc_light": "meta.diff.header: #062F4A" - } - }, - { - "c": "@@", - "t": "source.diff meta.diff.range.unified punctuation.definition.range.diff", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "source.diff meta.diff.range.unified", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "-1 +1", - "t": "source.diff meta.diff.range.unified meta.toc-list.line-number.diff", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "source.diff meta.diff.range.unified", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "@@", - "t": "source.diff meta.diff.range.unified punctuation.definition.range.diff", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "-", - "t": "source.diff markup.deleted.diff punctuation.definition.deleted.diff", - "r": { - "dark_plus": "markup.deleted: #CE9178", - "light_plus": "markup.deleted: #A31515", - "dark_vs": "markup.deleted: #CE9178", - "light_vs": "markup.deleted: #A31515", - "hc_black": "markup.deleted: #CE9178", - "hc_light": "markup.deleted: #5A5A5A" - } - }, - { - "c": "Hello world", - "t": "source.diff markup.deleted.diff", - "r": { - "dark_plus": "markup.deleted: #CE9178", - "light_plus": "markup.deleted: #A31515", - "dark_vs": "markup.deleted: #CE9178", - "light_vs": "markup.deleted: #A31515", - "hc_black": "markup.deleted: #CE9178", - "hc_light": "markup.deleted: #5A5A5A" - } - }, - { - "c": "+", - "t": "source.diff markup.inserted.diff punctuation.definition.inserted.diff", - "r": { - "dark_plus": "markup.inserted: #B5CEA8", - "light_plus": "markup.inserted: #098658", - "dark_vs": "markup.inserted: #B5CEA8", - "light_vs": "markup.inserted: #098658", - "hc_black": "markup.inserted: #B5CEA8", - "hc_light": "markup.inserted: #096D48" - } - }, - { - "c": "Hello World", - "t": "source.diff markup.inserted.diff", - "r": { - "dark_plus": "markup.inserted: #B5CEA8", - "light_plus": "markup.inserted: #098658", - "dark_vs": "markup.inserted: #B5CEA8", - "light_vs": "markup.inserted: #098658", - "hc_black": "markup.inserted: #B5CEA8", - "hc_light": "markup.inserted: #096D48" - } - } -] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_rst.json b/extensions/vscode-colorize-tests/test/colorize-results/test_rst.json deleted file mode 100644 index 83de018f0e..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_rst.json +++ /dev/null @@ -1,1298 +0,0 @@ -[ - { - "c": "*italics*", - "t": "source.rst markup.italic", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ", ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "**bold**", - "t": "source.rst markup.bold", - "r": { - "dark_plus": "markup.bold: #569CD6", - "light_plus": "markup.bold: #000080", - "dark_vs": "markup.bold: #569CD6", - "light_vs": "markup.bold: #000080", - "hc_black": "default: #FFFFFF", - "hc_light": "markup.bold: #000080" - } - }, - { - "c": ", ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "``literal``", - "t": "source.rst string.interpolated", - "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", - "dark_vs": "string: #CE9178", - "light_vs": "string: #A31515", - "hc_black": "string: #CE9178", - "hc_light": "string: #0F4A85" - } - }, - { - "c": ".", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "1. ", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "A list", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "2. ", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "With items", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " - ", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "With sub-lists ...", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " - ", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "... of things.", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "3. ", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "Other things", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "definition list", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " A list of terms and their definition", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Literal block", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "::", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " x = 2 + 3", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Section separators are all interchangeable.", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=====", - "t": "source.rst markup.heading", - "r": { - "dark_plus": "markup.heading: #569CD6", - "light_plus": "markup.heading: #800000", - "dark_vs": "markup.heading: #569CD6", - "light_vs": "markup.heading: #800000", - "hc_black": "markup.heading: #6796E6", - "hc_light": "markup.heading: #0F4A85" - } - }, - { - "c": "Title", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=====", - "t": "source.rst markup.heading", - "r": { - "dark_plus": "markup.heading: #569CD6", - "light_plus": "markup.heading: #800000", - "dark_vs": "markup.heading: #569CD6", - "light_vs": "markup.heading: #800000", - "hc_black": "markup.heading: #6796E6", - "hc_light": "markup.heading: #0F4A85" - } - }, - { - "c": "--------", - "t": "source.rst markup.heading", - "r": { - "dark_plus": "markup.heading: #569CD6", - "light_plus": "markup.heading: #800000", - "dark_vs": "markup.heading: #569CD6", - "light_vs": "markup.heading: #800000", - "hc_black": "markup.heading: #6796E6", - "hc_light": "markup.heading: #0F4A85" - } - }, - { - "c": "Subtitle", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "--------", - "t": "source.rst markup.heading", - "r": { - "dark_plus": "markup.heading: #569CD6", - "light_plus": "markup.heading: #800000", - "dark_vs": "markup.heading: #569CD6", - "light_vs": "markup.heading: #800000", - "hc_black": "markup.heading: #6796E6", - "hc_light": "markup.heading: #0F4A85" - } - }, - { - "c": "Section 1", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "=========", - "t": "source.rst markup.heading", - "r": { - "dark_plus": "markup.heading: #569CD6", - "light_plus": "markup.heading: #800000", - "dark_vs": "markup.heading: #569CD6", - "light_vs": "markup.heading: #800000", - "hc_black": "markup.heading: #6796E6", - "hc_light": "markup.heading: #0F4A85" - } - }, - { - "c": "Section 2", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "---------", - "t": "source.rst markup.heading", - "r": { - "dark_plus": "markup.heading: #569CD6", - "light_plus": "markup.heading: #800000", - "dark_vs": "markup.heading: #569CD6", - "light_vs": "markup.heading: #800000", - "hc_black": "markup.heading: #6796E6", - "hc_light": "markup.heading: #0F4A85" - } - }, - { - "c": "Section 3", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "~~~~~~~~~", - "t": "source.rst markup.heading", - "r": { - "dark_plus": "markup.heading: #569CD6", - "light_plus": "markup.heading: #800000", - "dark_vs": "markup.heading: #569CD6", - "light_vs": "markup.heading: #800000", - "hc_black": "markup.heading: #6796E6", - "hc_light": "markup.heading: #0F4A85" - } - }, - { - "c": "| ", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "Keeping line", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "| ", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "breaks.", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "+-------------+--------------+", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "|", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " Fancy table ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "|", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " with columns ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "|", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "+=============+==============+", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "|", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " row 1, col 1", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "|", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " row 1, col 2 ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "|", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "+-------------+--------------+", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "============ ============", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "Simple table with columns", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "============ ============", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "row 1, col1 row 1, col 2", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "============ ============", - "t": "source.rst keyword.control.table", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "Block quote is indented.", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " This space intentionally not important.", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Doctest block", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ">>>", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "2", - "t": "source.rst constant.numeric.dec.python", - "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", - "hc_light": "constant.numeric: #096D48" - } - }, - { - "c": " ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "+", - "t": "source.rst keyword.operator.arithmetic.python", - "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", - "hc_light": "keyword.operator: #000000" - } - }, - { - "c": "3", - "t": "source.rst constant.numeric.dec.python", - "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", - "hc_light": "constant.numeric: #096D48" - } - }, - { - "c": "5", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "A footnote [#note]_.", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ".. [#note] ", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": "https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#footnotes", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Citation ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "[cite]_", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": ".", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ".. [cite] ", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": "https://bing.com", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "a simple ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "link_", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": ".", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "A ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "`fancier link`_", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": " .", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ".. _link: ", - "t": "source.rst entity.name.tag.anchor", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": "https://docutils.sourceforge.io/", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ".. _fancier link: ", - "t": "source.rst entity.name.tag.anchor", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": "https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "An ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "`inline link `__", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": " .", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": ".. image::", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " https://code.visualstudio.com/assets/images/code-stable.png", - "t": "source.rst variable", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE", - "hc_light": "variable: #001080" - } - }, - { - "c": ".. function: example()", - "t": "source.rst comment.block", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " :module: mod", - "t": "source.rst comment.block", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": ":sub:", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "`subscript`", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": ":sup:", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "`superscript`", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": ".. This is a comment.", - "t": "source.rst comment.block", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "..", - "t": "source.rst comment.block", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " And a bigger,", - "t": "source.rst comment.block", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " longer comment.", - "t": "source.rst comment.block", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": "A ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "|subst|", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": " of something.", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "..", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "|subst|", - "t": "source.rst entity.name.tag", - "r": { - "dark_plus": "entity.name.tag: #569CD6", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag: #569CD6", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag: #569CD6", - "hc_light": "entity.name.tag: #0F4A85" - } - }, - { - "c": " ", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "replace::", - "t": "source.rst keyword.control", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": " substitution", - "t": "source.rst", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - } -] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_sty.json b/extensions/vscode-colorize-tests/test/colorize-results/test_sty.json deleted file mode 100644 index 5f23652638..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_sty.json +++ /dev/null @@ -1,1778 +0,0 @@ -[ - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "message", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{Document style option `aer.sty' (29 May 1993) for LaTeX 2.09.}", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "textwidth", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "=28pc", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "textheight", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "=46pc", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "def", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "bysame", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "leavevmode", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "hbox", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " to", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "leftmargin", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "leaders", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "hrule", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " height 3pt depth -2.5pt", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "hfill", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": ",", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": ",", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "}}", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "def", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "thebibliography", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "#1{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "section", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "*{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "refname", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "@mkboth", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " {", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "uppercase", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "refname", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "}}{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "uppercase", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "refname", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "}}}", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "list", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " {", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "@biblabel", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "arabic", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{enumiv}}}{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "labelwidth", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "=12pt", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "labelsep", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "=0pt", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "leftmargin", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "labelwidth", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "advance", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "leftmargin", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "labelsep", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "itemsep", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "=0pt", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "parsep", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "=0pt", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "usecounter", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{enumiv}", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "%", - "t": "text.tex comment.line.percentage.tex punctuation.definition.comment.tex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "let", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "p@enumiv", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "@empty", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "def", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "theenumiv", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "arabic", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{enumiv}}}", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "%", - "t": "text.tex comment.line.percentage.tex punctuation.definition.comment.tex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "def", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "newblock", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "hskip", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " .11em plus.33em minus.07em}", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "%", - "t": "text.tex comment.line.percentage.tex punctuation.definition.comment.tex", - "r": { - "dark_plus": "comment: #6A9955", - "light_plus": "comment: #008000", - "dark_vs": "comment: #6A9955", - "light_vs": "comment: #008000", - "hc_black": "comment: #7CA668", - "hc_light": "comment: #515151" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "sloppy", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "clubpenalty", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "4000", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "widowpenalty", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "4000", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "raggedright", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "sfcode", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "`", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex constant.character.escape.tex punctuation.definition.keyword.tex", - "r": { - "dark_plus": "constant.character.escape: #D7BA7D", - "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "constant.character: #569CD6", - "hc_light": "constant.character.escape: #EE0000" - } - }, - { - "c": ".", - "t": "text.tex constant.character.escape.tex", - "r": { - "dark_plus": "constant.character.escape: #D7BA7D", - "light_plus": "constant.character.escape: #EE0000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "constant.character: #569CD6", - "hc_light": "constant.character.escape: #EE0000" - } - }, - { - "c": "=1000", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "relax", - "t": "text.tex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "}", - "t": "text.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - } -] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_tex.json b/extensions/vscode-colorize-tests/test/colorize-results/test_tex.json deleted file mode 100644 index 04549af34d..0000000000 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_tex.json +++ /dev/null @@ -1,1034 +0,0 @@ -[ - { - "c": "\\", - "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex punctuation.definition.function.latex", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "documentclass", - "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "[", - "t": "text.tex.latex meta.preamble.latex meta.parameter.optional.latex punctuation.definition.optional.arguments.begin.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "12pt", - "t": "text.tex.latex meta.preamble.latex meta.parameter.optional.latex variable.parameter.function.latex", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE", - "hc_light": "variable: #001080" - } - }, - { - "c": "]", - "t": "text.tex.latex meta.preamble.latex meta.parameter.optional.latex punctuation.definition.optional.arguments.end.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.tex.latex meta.preamble.latex punctuation.definition.arguments.begin.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "article", - "t": "text.tex.latex meta.preamble.latex support.class.latex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "}", - "t": "text.tex.latex meta.preamble.latex punctuation.definition.arguments.end.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex punctuation.definition.function.latex", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "usepackage", - "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "{", - "t": "text.tex.latex meta.preamble.latex punctuation.definition.arguments.begin.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "lingmacros", - "t": "text.tex.latex meta.preamble.latex support.class.latex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "}", - "t": "text.tex.latex meta.preamble.latex punctuation.definition.arguments.end.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex punctuation.definition.function.latex", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "usepackage", - "t": "text.tex.latex meta.preamble.latex keyword.control.preamble.latex", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "{", - "t": "text.tex.latex meta.preamble.latex punctuation.definition.arguments.begin.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "tree-dvips", - "t": "text.tex.latex meta.preamble.latex support.class.latex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "}", - "t": "text.tex.latex meta.preamble.latex punctuation.definition.arguments.end.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex meta.function.begin-document.latex support.function.be.latex punctuation.definition.function.latex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "begin", - "t": "text.tex.latex meta.function.begin-document.latex support.function.be.latex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex.latex meta.function.begin-document.latex punctuation.definition.arguments.begin.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "document", - "t": "text.tex.latex meta.function.begin-document.latex variable.parameter.function.latex", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE", - "hc_light": "variable: #001080" - } - }, - { - "c": "}", - "t": "text.tex.latex meta.function.begin-document.latex punctuation.definition.arguments.end.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex meta.function.section.section.latex support.function.section.latex punctuation.definition.function.latex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "section*", - "t": "text.tex.latex meta.function.section.section.latex support.function.section.latex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex.latex meta.function.section.section.latex punctuation.definition.arguments.begin.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Notes for My Paper", - "t": "text.tex.latex meta.function.section.section.latex entity.name.section.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.tex.latex meta.function.section.section.latex punctuation.definition.arguments.end.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "Don't forget to include examples of topicalization.", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "They look like this:", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "small", - "t": "text.tex.latex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "\\", - "t": "text.tex.latex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "enumsentence", - "t": "text.tex.latex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{Topicalization from sentential subject:", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\\\", - "t": "text.tex.latex keyword.control.newline.tex", - "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", - "hc_light": "keyword.control: #B5200D" - } - }, - { - "c": "\\", - "t": "text.tex.latex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "shortex", - "t": "text.tex.latex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{7}{a John", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "$", - "t": "text.tex.latex meta.math.block.tex support.class.math.block.tex punctuation.definition.string.begin.tex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "_", - "t": "text.tex.latex meta.math.block.tex support.class.math.block.tex punctuation.math.operator.tex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "i", - "t": "text.tex.latex meta.math.block.tex support.class.math.block.tex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "$", - "t": "text.tex.latex meta.math.block.tex support.class.math.block.tex punctuation.definition.string.end.tex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": " ", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "[", - "t": "text.tex.latex punctuation.definition.brackets.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "a & kltukl & ", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "[", - "t": "text.tex.latex punctuation.definition.brackets.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "el &", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " {", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "bf", - "t": "text.tex.latex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex.latex meta.space-after-command.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "l-}oltoir & er & ngii", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "$", - "t": "text.tex.latex meta.math.block.tex support.class.math.block.tex punctuation.definition.string.begin.tex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "_", - "t": "text.tex.latex meta.math.block.tex support.class.math.block.tex punctuation.math.operator.tex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "i", - "t": "text.tex.latex meta.math.block.tex support.class.math.block.tex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": "$", - "t": "text.tex.latex meta.math.block.tex support.class.math.block.tex punctuation.definition.string.end.tex", - "r": { - "dark_plus": "support.class: #4EC9B0", - "light_plus": "support.class: #267F99", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.class: #4EC9B0", - "hc_light": "support.class: #185E73" - } - }, - { - "c": " & a Mary", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "]]", - "t": "text.tex.latex punctuation.definition.brackets.tex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{ & {", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "bf", - "t": "text.tex.latex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex.latex meta.space-after-command.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "R-}clear & {", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "sc", - "t": "text.tex.latex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex.latex meta.space-after-command.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "comp} &", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": " {", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "bf", - "t": "text.tex.latex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex.latex meta.space-after-command.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "IR}.{", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex support.function.general.tex punctuation.definition.function.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "sc", - "t": "text.tex.latex support.function.general.tex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": " ", - "t": "text.tex.latex meta.space-after-command.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "3s}-love & P & him & }", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "{John, (it's) clear that Mary loves (him).}}", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "}", - "t": "text.tex.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "\\", - "t": "text.tex.latex meta.function.end-document.latex support.function.be.latex punctuation.definition.function.latex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "end", - "t": "text.tex.latex meta.function.end-document.latex support.function.be.latex", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC" - } - }, - { - "c": "{", - "t": "text.tex.latex meta.function.end-document.latex punctuation.definition.arguments.begin.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - }, - { - "c": "document", - "t": "text.tex.latex meta.function.end-document.latex variable.parameter.function.latex", - "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE", - "hc_light": "variable: #001080" - } - }, - { - "c": "}", - "t": "text.tex.latex meta.function.end-document.latex punctuation.definition.arguments.end.latex", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "hc_light": "default: #292929" - } - } -] \ No newline at end of file diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json index da90e7e380..d54d12ab52 100644 --- a/extensions/vscode-test-resolver/package.json +++ b/extensions/vscode-test-resolver/package.json @@ -6,7 +6,8 @@ "license": "MIT", "enableProposedApi": true, "enabledApiProposals": [ - "resolvers" + "resolvers", + "tunnels" ], "private": true, "engines": { @@ -65,6 +66,11 @@ "category": "Remote-TestResolver", "command": "vscode-testresolver.currentWindow" }, + { + "title": "Connect to TestResolver in Current Window with Managed Connection", + "category": "Remote-TestResolver", + "command": "vscode-testresolver.currentWindowManaged" + }, { "title": "Show TestResolver Log", "category": "Remote-TestResolver", @@ -89,6 +95,11 @@ "title": "Pause Connection (Test Reconnect)", "category": "Remote-TestResolver", "command": "vscode-testresolver.toggleConnectionPause" + }, + { + "title": "Slowdown Connection (Test Slow Down Indicator)", + "category": "Remote-TestResolver", + "command": "vscode-testresolver.toggleConnectionSlowdown" } ], "menus": { diff --git a/extensions/vscode-test-resolver/src/extension.ts b/extensions/vscode-test-resolver/src/extension.ts index fd6f483a48..7d2054b4b6 100644 --- a/extensions/vscode-test-resolver/src/extension.ts +++ b/extensions/vscode-test-resolver/src/extension.ts @@ -22,12 +22,65 @@ const enum CharCode { let outputChannel: vscode.OutputChannel; +const SLOWED_DOWN_CONNECTION_DELAY = 800; + export function activate(context: vscode.ExtensionContext) { let connectionPaused = false; const connectionPausedEvent = new vscode.EventEmitter(); - function doResolve(_authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise { + let connectionSlowedDown = false; + const connectionSlowedDownEvent = new vscode.EventEmitter(); + const slowedDownConnections = new Set(); + connectionSlowedDownEvent.event(slowed => { + if (!slowed) { + for (const cb of slowedDownConnections) { + cb(); + } + slowedDownConnections.clear(); + } + }); + + function getTunnelFeatures(): vscode.TunnelInformation['tunnelFeatures'] { + return { + elevation: true, + privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [ + { + id: 'public', + label: 'Public', + themeIcon: 'eye' + }, + { + id: 'other', + label: 'Other', + themeIcon: 'circuit-board' + }, + { + id: 'private', + label: 'Private', + themeIcon: 'eye-closed' + } + ] : [] + }; + } + + function maybeSlowdown(): Promise | void { + if (connectionSlowedDown) { + return new Promise(resolve => { + const handle = setTimeout(() => { + resolve(); + slowedDownConnections.delete(resolve); + }, SLOWED_DOWN_CONNECTION_DELAY); + + slowedDownConnections.add(() => { + resolve(); + clearTimeout(handle); + }); + }); + } + } + + function doResolve(authority: string, progress: vscode.Progress<{ message?: string; increment?: number }>): Promise { if (connectionPaused) { throw vscode.RemoteAuthorityResolverError.TemporarilyNotAvailable('Not available right now'); } @@ -150,7 +203,35 @@ export function activate(context: vscode.ExtensionContext) { } }); }); - return serverPromise.then(serverAddr => { + + return serverPromise.then((serverAddr): Promise => { + if (authority.includes('managed')) { + console.log('Connecting via a managed authority'); + return Promise.resolve(new vscode.ManagedResolvedAuthority(async () => { + const remoteSocket = net.createConnection({ port: serverAddr.port }); + const dataEmitter = new vscode.EventEmitter(); + const closeEmitter = new vscode.EventEmitter(); + const endEmitter = new vscode.EventEmitter(); + + await new Promise((res, rej) => { + remoteSocket.on('data', d => dataEmitter.fire(d)) + .on('error', err => { rej(); closeEmitter.fire(err); }) + .on('close', () => endEmitter.fire()) + .on('end', () => endEmitter.fire()) + .on('connect', res); + }); + + + return { + onDidReceiveMessage: dataEmitter.event, + onDidClose: closeEmitter.event, + onDidEnd: endEmitter.event, + send: d => remoteSocket.write(d), + end: () => remoteSocket.end(), + }; + }, connectionToken)); + } + return new Promise((res, _rej) => { const proxyServer = net.createServer(proxySocket => { outputChannel.appendLine(`Proxy connection accepted`); @@ -186,13 +267,15 @@ export function activate(context: vscode.ExtensionContext) { connectionPausedEvent.event(_ => handleConnectionPause()); handleConnectionPause(); - proxySocket.on('data', (data) => { + proxySocket.on('data', async (data) => { + await maybeSlowdown(); remoteReady = remoteSocket.write(data); if (!remoteReady) { proxySocket.pause(); } }); - remoteSocket.on('data', (data) => { + remoteSocket.on('data', async (data) => { + await maybeSlowdown(); localReady = proxySocket.write(data); if (!localReady) { remoteSocket.pause(); @@ -228,28 +311,7 @@ export function activate(context: vscode.ExtensionContext) { proxyServer.listen(0, '127.0.0.1', () => { const port = (proxyServer.address()).port; outputChannel.appendLine(`Going through proxy at port ${port}`); - const r: vscode.ResolverResult = new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken); - r.tunnelFeatures = { - elevation: true, - privacyOptions: vscode.workspace.getConfiguration('testresolver').get('supportPublicPorts') ? [ - { - id: 'public', - label: 'Public', - themeIcon: 'eye' - }, - { - id: 'other', - label: 'Other', - themeIcon: 'circuit-board' - }, - { - id: 'private', - label: 'Private', - themeIcon: 'eye-closed' - } - ] : [] - }; - res(r); + res(new vscode.ResolvedAuthority('127.0.0.1', port, connectionToken)); }); context.subscriptions.push({ dispose: () => { @@ -264,12 +326,16 @@ export function activate(context: vscode.ExtensionContext) { async getCanonicalURI(uri: vscode.Uri): Promise { return vscode.Uri.file(uri.path); }, - resolve(_authority: string): Thenable { + resolve(_authority: string): Thenable { return vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'Open TestResolver Remote ([details](command:vscode-testresolver.showLog))', cancellable: false - }, (progress) => doResolve(_authority, progress)); + }, async (progress) => { + const rr = await doResolve(_authority, progress); + rr.tunnelFeatures = getTunnelFeatures(); + return rr; + }); }, tunnelFactory, showCandidatePort @@ -282,6 +348,9 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindow', () => { return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+test', reuseWindow: true }); })); + context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.currentWindowManaged', () => { + return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+managed', reuseWindow: true }); + })); context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.newWindowWithError', () => { return vscode.commands.executeCommand('vscode.newWindow', { remoteAuthority: 'test+error' }); })); @@ -321,6 +390,22 @@ export function activate(context: vscode.ExtensionContext) { connectionPausedEvent.fire(connectionPaused); })); + const slowdownStatusBarEntry = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + slowdownStatusBarEntry.text = 'Remote connection slowed down. Click to undo'; + slowdownStatusBarEntry.command = 'vscode-testresolver.toggleConnectionSlowdown'; + slowdownStatusBarEntry.backgroundColor = new vscode.ThemeColor('statusBarItem.errorBackground'); + + context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.toggleConnectionSlowdown', () => { + if (!connectionSlowedDown) { + connectionSlowedDown = true; + slowdownStatusBarEntry.show(); + } else { + connectionSlowedDown = false; + slowdownStatusBarEntry.hide(); + } + connectionSlowedDownEvent.fire(connectionSlowedDown); + })); + context.subscriptions.push(vscode.commands.registerCommand('vscode-testresolver.openTunnel', async () => { const result = await vscode.window.showInputBox({ prompt: 'Enter the remote port for the tunnel', diff --git a/extensions/vscode-test-resolver/tsconfig.json b/extensions/vscode-test-resolver/tsconfig.json index f9a183ef9e..4a8025df9b 100644 --- a/extensions/vscode-test-resolver/tsconfig.json +++ b/extensions/vscode-test-resolver/tsconfig.json @@ -9,6 +9,7 @@ "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.tunnels.d.ts", "../../src/vscode-dts/vscode.proposed.resolvers.d.ts" ] } diff --git a/extensions/xml-language-features/.eslintrc.json b/extensions/xml-language-features/.eslintrc.json deleted file mode 100644 index ce28ab7a81..0000000000 --- a/extensions/xml-language-features/.eslintrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "rules": { - "@typescript-eslint/explicit-function-return-type": ["off"], - "@typescript-eslint/await-thenable": ["off"], - "@typescript-eslint/no-unsafe-assignment": "off" - } -} diff --git a/extensions/xml-language-features/.vscodeignore b/extensions/xml-language-features/.vscodeignore deleted file mode 100644 index 469e86abac..0000000000 --- a/extensions/xml-language-features/.vscodeignore +++ /dev/null @@ -1,11 +0,0 @@ -test/** -src/** -tsconfig.json -out/test/** -out/** -extension.webpack.config.js -cgmanifest.json -yarn.lock -preview-src/** -webpack.config.js -.vscode diff --git a/extensions/xml-language-features/package.json b/extensions/xml-language-features/package.json deleted file mode 100644 index 47da07861c..0000000000 --- a/extensions/xml-language-features/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "xml-language-features", - "displayName": "%displayName%", - "description": "%description%", - "version": "1.0.0", - "publisher": "Microsoft", - "engines": { - "vscode": "^1.20.0" - }, - "main": "./out/extension", - "categories": [ - "Programming Languages" - ], - "activationEvents": [ - "onLanguage:sql", - "onLanguage:xml" - ], - "scripts": { - "compile": "gulp compile-extension:markdown-language-features && npm run build-preview", - "watch": "npm run build-preview && gulp watch-extension:markdown-language-features", - "vscode:prepublish": "npm run build-ext && npm run build-preview", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", - "build-preview": "webpack --mode development" - }, - "dependencies": { - "tsxml": "^0.1.0" - } -} diff --git a/extensions/xml-language-features/package.nls.json b/extensions/xml-language-features/package.nls.json deleted file mode 100644 index 0021e0b113..0000000000 --- a/extensions/xml-language-features/package.nls.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "displayName": "XML Language Features", - "description": "Provides rich language support for XML." -} diff --git a/extensions/xml-language-features/src/extension.ts b/extensions/xml-language-features/src/extension.ts deleted file mode 100644 index 4e865f75ab..0000000000 --- a/extensions/xml-language-features/src/extension.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import * as xml from 'tsxml'; - -export function activate(context: vscode.ExtensionContext) { - context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider({ language: 'xml' }, { - provideDocumentFormattingEdits: document => format(document) - })); -} - -function format(document: vscode.TextDocument): vscode.ProviderResult { - const range = new vscode.Range(0, 0, document.lineCount, document.lineAt(document.lineCount - 1).range.end.character); - return xml.Compiler.formatXmlString(document.getText()).then(formatted => [new vscode.TextEdit(range, formatted)], () => [new vscode.TextEdit(range, document.getText())]); -} diff --git a/extensions/xml-language-features/tsconfig.json b/extensions/xml-language-features/tsconfig.json deleted file mode 100644 index ac03ca408b..0000000000 --- a/extensions/xml-language-features/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../tsconfig.base.json", - "compilerOptions": { - "outDir": "./out", - "experimentalDecorators": true, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "include": [ - "src/**/*", - "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts" - ] -} diff --git a/extensions/xml-language-features/yarn.lock b/extensions/xml-language-features/yarn.lock deleted file mode 100644 index cf7e370ca0..0000000000 --- a/extensions/xml-language-features/yarn.lock +++ /dev/null @@ -1,8 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -tsxml@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/tsxml/-/tsxml-0.1.0.tgz#d73f14a0d844af51edc0b98bdb52634a41b9b0d4" - integrity sha1-1z8UoNhEr1HtwLmL21JjSkG5sNQ= diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index cc2de87346..fbbabd8170 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -62,7 +62,8 @@ "[yaml]": { "editor.insertSpaces": true, "editor.tabSize": 2, - "editor.autoIndent": "advanced" + "editor.autoIndent": "advanced", + "diffEditor.ignoreTrimWhitespace": false }, "[dockercompose]": { "editor.insertSpaces": true, diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 7b4407130a..50070eb668 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,14 +2,133 @@ # yarn lockfile v1 -"@parcel/watcher@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.5.tgz#f913a54e1601b0aac972803829b0eece48de215b" - integrity sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw== +"@esbuild/android-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.14.tgz#4624cea3c8941c91f9e9c1228f550d23f1cef037" + integrity sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg== + +"@esbuild/android-arm@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.14.tgz#74fae60fcab34c3f0e15cb56473a6091ba2b53a6" + integrity sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g== + +"@esbuild/android-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.14.tgz#f002fbc08d5e939d8314bd23bcfb1e95d029491f" + integrity sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng== + +"@esbuild/darwin-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.14.tgz#b8dcd79a1dd19564950b4ca51d62999011e2e168" + integrity sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw== + +"@esbuild/darwin-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.14.tgz#4b49f195d9473625efc3c773fc757018f2c0d979" + integrity sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g== + +"@esbuild/freebsd-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.14.tgz#480923fd38f644c6342c55e916cc7c231a85eeb7" + integrity sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A== + +"@esbuild/freebsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.14.tgz#a6b6b01954ad8562461cb8a5e40e8a860af69cbe" + integrity sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw== + +"@esbuild/linux-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.14.tgz#1fe2f39f78183b59f75a4ad9c48d079916d92418" + integrity sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g== + +"@esbuild/linux-arm@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.14.tgz#18d594a49b64e4a3a05022c005cb384a58056a2a" + integrity sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg== + +"@esbuild/linux-ia32@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.14.tgz#f7f0182a9cfc0159e0922ed66c805c9c6ef1b654" + integrity sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ== + +"@esbuild/linux-loong64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.14.tgz#5f5305fdffe2d71dd9a97aa77d0c99c99409066f" + integrity sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ== + +"@esbuild/linux-mips64el@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.14.tgz#a602e85c51b2f71d2aedfe7f4143b2f92f97f3f5" + integrity sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg== + +"@esbuild/linux-ppc64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.14.tgz#32d918d782105cbd9345dbfba14ee018b9c7afdf" + integrity sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ== + +"@esbuild/linux-riscv64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.14.tgz#38612e7b6c037dff7022c33f49ca17f85c5dec58" + integrity sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw== + +"@esbuild/linux-s390x@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.14.tgz#4397dff354f899e72fd035d72af59a700c465ccb" + integrity sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww== + +"@esbuild/linux-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.14.tgz#6c5cb99891b6c3e0c08369da3ef465e8038ad9c2" + integrity sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw== + +"@esbuild/netbsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.14.tgz#5fa5255a64e9bf3947c1b3bef5e458b50b211994" + integrity sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ== + +"@esbuild/openbsd-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.14.tgz#74d14c79dcb6faf446878cc64284aa4e02f5ca6f" + integrity sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g== + +"@esbuild/sunos-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.14.tgz#5c7d1c7203781d86c2a9b2ff77bd2f8036d24cfa" + integrity sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA== + +"@esbuild/win32-arm64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.14.tgz#dc36ed84f1390e73b6019ccf0566c80045e5ca3d" + integrity sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ== + +"@esbuild/win32-ia32@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.14.tgz#0802a107afa9193c13e35de15a94fe347c588767" + integrity sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w== + +"@esbuild/win32-x64@0.17.14": + version "0.17.14" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.14.tgz#e81fb49de05fed91bf74251c9ca0343f4fc77d31" + integrity sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA== + +"@parcel/watcher@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.1.0.tgz#5f32969362db4893922c526a842d8af7a8538545" + integrity sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw== dependencies: + is-glob "^4.0.3" + micromatch "^4.0.5" node-addon-api "^3.2.1" node-gyp-build "^4.3.0" +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + coffeescript@1.12.7: version "1.12.7" resolved "https://registry.yarnpkg.com/coffeescript/-/coffeescript-1.12.7.tgz#e57ee4c4867cf7f606bfc4a0f2d550c0981ddd27" @@ -22,16 +141,71 @@ cson-parser@^4.0.9: dependencies: coffeescript "1.12.7" -esbuild@^0.11.12: - version "0.11.23" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.23.tgz#c42534f632e165120671d64db67883634333b4b8" - integrity sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q== +esbuild@0.17.14: + version "0.17.14" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.17.14.tgz#d61a22de751a3133f3c6c7f9c1c3e231e91a3245" + integrity sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw== + optionalDependencies: + "@esbuild/android-arm" "0.17.14" + "@esbuild/android-arm64" "0.17.14" + "@esbuild/android-x64" "0.17.14" + "@esbuild/darwin-arm64" "0.17.14" + "@esbuild/darwin-x64" "0.17.14" + "@esbuild/freebsd-arm64" "0.17.14" + "@esbuild/freebsd-x64" "0.17.14" + "@esbuild/linux-arm" "0.17.14" + "@esbuild/linux-arm64" "0.17.14" + "@esbuild/linux-ia32" "0.17.14" + "@esbuild/linux-loong64" "0.17.14" + "@esbuild/linux-mips64el" "0.17.14" + "@esbuild/linux-ppc64" "0.17.14" + "@esbuild/linux-riscv64" "0.17.14" + "@esbuild/linux-s390x" "0.17.14" + "@esbuild/linux-x64" "0.17.14" + "@esbuild/netbsd-x64" "0.17.14" + "@esbuild/openbsd-x64" "0.17.14" + "@esbuild/sunos-x64" "0.17.14" + "@esbuild/win32-arm64" "0.17.14" + "@esbuild/win32-ia32" "0.17.14" + "@esbuild/win32-x64" "0.17.14" fast-plist@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" integrity sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg= +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + node-addon-api@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" @@ -42,10 +216,22 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -typescript@^4.8.0-dev.20220614: - version "4.8.0-dev.20220714" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220714.tgz#9c1be002c44c3566e789aa404b2f94b0b96468cc" - integrity sha512-wKK9FMpdvwI68PZiQdNTNmX4rpVXJBDOG9aylV9O6nJUO5YX8yv3bQNcyLc5tbI7J3+u7AU48LVY9SF9lYwy5g== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +typescript@5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" + integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== vscode-grammar-updater@^1.1.0: version "1.1.0" diff --git a/extensions/javascript/yarn.lock b/i18n/ads-language-pack-de/yarn.lock similarity index 100% rename from extensions/javascript/yarn.lock rename to i18n/ads-language-pack-de/yarn.lock diff --git a/i18n/ads-language-pack-es/yarn.lock b/i18n/ads-language-pack-es/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-es/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/i18n/ads-language-pack-fr/yarn.lock b/i18n/ads-language-pack-fr/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-fr/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/i18n/ads-language-pack-it/yarn.lock b/i18n/ads-language-pack-it/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-it/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/i18n/ads-language-pack-ja/yarn.lock b/i18n/ads-language-pack-ja/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-ja/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/i18n/ads-language-pack-ko/yarn.lock b/i18n/ads-language-pack-ko/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-ko/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/i18n/ads-language-pack-pt-BR/yarn.lock b/i18n/ads-language-pack-pt-BR/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-pt-BR/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/i18n/ads-language-pack-ru/yarn.lock b/i18n/ads-language-pack-ru/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-ru/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/i18n/ads-language-pack-zh-hans/yarn.lock b/i18n/ads-language-pack-zh-hans/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-zh-hans/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/i18n/ads-language-pack-zh-hant/yarn.lock b/i18n/ads-language-pack-zh-hant/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/i18n/ads-language-pack-zh-hant/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/package.json b/package.json index aa4a3b24f0..f511fe2223 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azuredatastudio", "version": "1.45.0", - "distro": "90d90d3d534cc89532292bc23b38cdbc5c3f7a5c", + "distro": "a42cd60600a6cdad91afc25af929f5fe33c08e12", "author": { "name": "Microsoft Corporation" }, @@ -46,7 +46,8 @@ "web": "echo 'yarn web' is replaced by './scripts/code-server' or './scripts/code-web'", "compile-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-web", "watch-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-web", - "eslint": "node --max_old_space_size=4095 build/eslint", + "eslint": "node --max_old_space_size=8191 build/eslint", + "stylelint": "node build/stylelint", "playwright-install": "node build/azure-pipelines/common/installPlaywright.js", "compile-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-build", "compile-extensions-build": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile-extensions-build", @@ -54,11 +55,11 @@ "minify-vscode-reh": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh", "minify-vscode-reh-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web", "hygiene": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js hygiene", - "core-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js core-ci", + "core-ci": "node --max_old_space_size=8095 ./node_modules/gulp/bin/gulp.js core-ci", "extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci", - "sqllint": "node --max_old_space_size=4095 ./node_modules/eslint/bin/eslint.js --no-eslintrc -c .eslintrc.sql.ts.json --rulesdir ./build/lib/eslint --ext .ts ./src/sql", - "extensions-lint": "node --max_old_space_size=4095 ./node_modules/eslint/bin/eslint.js --rulesdir ./build/lib/eslint --ext .ts ./extensions", - "webview-generate-csp-hash": "npx github:apaatsio/csp-hash-from-html csp-hash ./src/vs/workbench/contrib/webview/browser/pre/index.html" + "sqllint": "node --max_old_space_size=4095 ./node_modules/eslint/bin/eslint.js --no-eslintrc -c .eslintrc.sql.ts.json --rulesdir ./.eslintplugin --ext .ts ./src/sql", + "extensions-lint": "node --max_old_space_size=4095 ./node_modules/eslint/bin/eslint.js --rulesdir ./.eslintplugin --ext .ts ./extensions", + "perf": "node scripts/code-perf.js" }, "dependencies": { "@angular/animations": "~4.1.3", @@ -71,10 +72,13 @@ "@angular/router": "~4.1.3", "@microsoft/1ds-core-js": "^3.2.2", "@microsoft/1ds-post-js": "^3.2.2", - "@parcel/watcher": "2.0.5", + "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/ripgrep": "^1.14.2", - "@vscode/sqlite3": "5.0.8", + "@vscode/policy-watcher": "^1.1.4", + "@vscode/proxy-agent": "^0.13.2", + "@vscode/ripgrep": "^1.15.3", + "@vscode/spdlog": "^0.13.10", + "@vscode/sqlite3": "5.1.4-vscode", "@vscode/sudo-prompt": "9.3.1", "@vscode/vscode-languagedetection": "1.0.21", "angular2-grid": "2.0.6", @@ -97,40 +101,40 @@ "moment": "^2.29.4", "native-is-elevated": "0.4.3", "native-keymap": "3.3.0", - "native-watchdog": "1.4.0", - "node-pty": "0.11.0-beta11", + "native-watchdog": "^1.4.1", + "node-pty": "0.11.0-beta32", "plotly.js-dist-min": "^1.53.0", "reflect-metadata": "^0.1.8", "rxjs": "5.4.0", "sanitize-html": "1.19.1", "semver-umd": "^5.5.7", "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.44", - "spdlog": "^0.13.0", - "tas-client-umd": "0.1.6", + "tas-client-umd": "0.1.8", "turndown": "^7.0.0", "turndown-plugin-gfm": "^1.0.2", "v8-inspect-profiler": "^0.1.0", - "vscode-oniguruma": "1.6.1", - "vscode-policy-watcher": "^1.1.1", - "vscode-proxy-agent": "^0.12.0", + "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.20", - "xterm-addon-search": "0.10.0-beta.3", - "xterm-addon-serialize": "0.8.0-beta.3", - "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.9", - "xterm-headless": "4.19.0-beta.25", + "vscode-textmate": "9.0.0", + "xterm": "5.2.0-beta.49", + "xterm-addon-canvas": "0.4.0-beta.14", + "xterm-addon-image": "0.4.0", + "xterm-addon-search": "0.12.0-beta.5", + "xterm-addon-serialize": "0.10.0-beta.2", + "xterm-addon-unicode11": "0.5.0", + "xterm-addon-webgl": "0.15.0-beta.15", + "xterm-headless": "5.2.0-beta.49", "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.11.4" }, "devDependencies": { "7zip": "0.0.6", - "@playwright/test": "1.26.0", + "@playwright/test": "1.32.3", "@types/applicationinsights": "0.20.0", + "@swc/cli": "0.1.57", + "@swc/core": "1.3.32", "@types/cookie": "^0.3.3", - "@types/copy-webpack-plugin": "^6.0.3", "@types/cssnano": "^4.0.0", "@types/debug": "4.1.5", "@types/graceful-fs": "4.1.2", @@ -148,41 +152,43 @@ "@types/sinon": "10.0.2", "@types/sinon-test": "^2.4.2", "@types/trusted-types": "^1.0.6", - "@types/vscode-notebook-renderer": "1.60.0", - "@types/webpack": "^4.41.25", - "@types/wicg-file-system-access": "^2020.9.5", + "@types/vscode-notebook-renderer": "^1.60.0", + "@types/webpack": "^5.28.1", + "@types/wicg-file-system-access": "^2020.9.6", "@types/windows-foreground-love": "^0.3.0", - "@types/windows-mutex": "^0.4.0", - "@types/windows-process-tree": "^0.2.0", "@types/winreg": "^1.2.30", "@types/yauzl": "^2.9.1", "@types/yazl": "^2.4.2", - "@typescript-eslint/eslint-plugin": "^5.10.0", - "@typescript-eslint/parser": "^5.10.0", - "@vscode/telemetry-extractor": "^1.9.6", - "@vscode/test-web": "^0.0.29", + "@typescript-eslint/eslint-plugin": "^5.57.0", + "@typescript-eslint/experimental-utils": "^5.57.0", + "@typescript-eslint/parser": "^5.57.0", + "@vscode/gulp-electron": "^1.34.0", + "@vscode/l10n-dev": "0.0.21", + "@vscode/telemetry-extractor": "^1.9.9", + "@vscode/test-web": "^0.0.41", + "@vscode/vscode-perf": "^0.0.14", "ansi-colors": "^3.2.3", "asar": "^3.0.3", "chromium-pickle-js": "^0.2.0", "concurrently": "^5.2.0", "cookie": "^0.4.0", - "copy-webpack-plugin": "^6.0.3", + "copy-webpack-plugin": "^11.0.0", "cson-parser": "^1.3.3", - "css-loader": "^3.6.0", + "css-loader": "^6.7.3", "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "19.1.8", - "eslint": "8.7.0", + "electron": "22.3.10", + "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^39.3.2", + "eslint-plugin-local": "^1.0.0", "event-stream": "3.3.4", "fancy-log": "^1.3.3", - "fast-plist": "0.1.2", - "file-loader": "^5.1.0", + "fast-plist": "0.1.3", + "file-loader": "^6.2.0", "glob": "^5.0.13", "gulp": "^4.0.0", - "gulp-atom-electron": "^1.33.0", "gulp-azure-storage": "^0.12.1", "gulp-bom": "^3.0.0", "gulp-buffer": "0.0.2", @@ -195,21 +201,19 @@ "gulp-json-editor": "^2.5.0", "gulp-plumber": "^1.2.0", "gulp-postcss": "^9.0.0", - "gulp-remote-retry-src": "^0.8.0", "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.4", "gulp-sourcemaps": "^3.0.0", "gulp-svgmin": "^4.1.0", "gulp-untar": "^0.0.7", - "gulp-vinyl-zip": "^2.1.2", "husky": "^0.13.1", "innosetup": "6.0.5", "is": "^3.1.0", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.2.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.1.5", "lazy.js": "^0.4.2", "merge-options": "^1.0.1", "mime": "^1.4.1", @@ -227,27 +231,25 @@ "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", - "request": "^2.85.0", "rimraf": "^2.2.8", - "sinon": "11.1.1", + "sinon": "^11.1.1", "sinon-test": "^3.1.3", "source-map": "0.6.1", "source-map-support": "^0.3.2", "style-loader": "^1.3.0", "temp-write": "^3.4.0", - "ts-loader": "^9.2.7", + "ts-loader": "^9.4.2", + "ts-node": "^10.9.1", "tsec": "0.1.4", "typemoq": "^0.3.2", - "typescript": "4.8.0-dev.20220719", + "typescript": "^5.2.0-dev.20230524", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", - "vinyl": "^2.0.0", - "vinyl-fs": "^3.0.0", "vscode-nls-dev": "^3.3.1", - "webpack": "^5.76.0", - "webpack-cli": "^4.7.2", - "webpack-stream": "^6.1.2", + "webpack": "^5.42.0", + "webpack-cli": "^5.0.1", + "webpack-stream": "^7.0.0", "xml2js": "^0.5.0", "yaserver": "^0.2.0" }, @@ -259,10 +261,10 @@ "url": "https://github.com/Microsoft/azuredatastudio/issues" }, "optionalDependencies": { - "@vscode/windows-registry": "1.0.6", - "windows-foreground-love": "0.4.0", - "windows-mutex": "0.4.1", - "windows-process-tree": "0.3.3" + "@vscode/windows-mutex": "0.4.2", + "@vscode/windows-process-tree": "0.4.2", + "@vscode/windows-registry": "1.0.10", + "windows-foreground-love": "0.5.0" }, "resolutions": { "elliptic": "^6.5.3", diff --git a/product.json b/product.json index 000070e697..0fd4ddc2b3 100644 --- a/product.json +++ b/product.json @@ -6,11 +6,13 @@ "win32MutexName": "azuredatastudio", "licenseName": "Microsoft EULA", "licenseUrl": "https://raw.githubusercontent.com/microsoft/azuredatastudio/main/LICENSE.txt", + "serverLicenseUrl": "https://raw.githubusercontent.com/microsoft/azuredatastudio/main/LICENSE.txt", "serverGreeting": [], "serverLicense": [], "serverLicensePrompt": "", - "serverApplicationName": "code-server-oss", - "serverDataFolderName": ".vscode-server-oss", + "serverApplicationName": "azuredatastudio-server-oss", + "serverDataFolderName": ".azuredatastudio-server-oss", + "tunnelApplicationName": "azuredatastudio-tunnel-oss", "win32DirName": "Azure Data Studio", "win32NameVersion": "Azure Data Studio", "win32RegValueName": "azuredatastudio", @@ -22,6 +24,8 @@ "win32arm64UserAppId": "{{9312E619-ABD1-458C-84FD-C5F6EA0839DA}", "win32AppUserModelId": "Microsoft.azuredatastudio", "win32ShellNameShort": "Azure Data Studio", + "win32TunnelServiceMutex": "azuredatastudio-oss-tunnelservice", + "win32TunnelMutex": "azuredatastudio-oss-tunnel", "darwinBundleIdentifier": "com.azuredatastudio.oss", "linuxIconName": "com.azuredatastudio.oss", "licenseFileName": "LICENSE.txt", @@ -31,7 +35,7 @@ "telemetryOptOutUrl": "https://github.com/Microsoft/azuredatastudio/wiki/How-to-Disable-Telemetry-Reporting", "urlProtocol": "azuredatastudio-oss", "enableTelemetry": false, - "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-webview.net/insider/69df0500a8963fc469161c038a14a39384d5a303/out/vs/workbench/contrib/webview/browser/pre/", + "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/3c8520fab514b9f56070214496b26ff68d1b1cb5/out/vs/workbench/contrib/webview/browser/pre/", "npsSurveyUrl": "https://aka.ms/azuredatastudio-nps", "aiConfig": { "ariaKey": "29a207bb14f84905966a8f22524cb730-25407f35-11b6-4d4e-8114-ab9e843cb52f-7380" @@ -43,7 +47,7 @@ "gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039", "releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578", "documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277", - "vscodeVersion": "1.70.0", + "vscodeVersion": "1.79.0", "commit": "9ca6200018fc206d67a47229f991901a8a453781", "date": "2017-12-15T12:00:00.000Z", "recommendedExtensions": [ diff --git a/remote/.yarnrc b/remote/.yarnrc index 3a3fbdc335..340ea50f6b 100644 --- a/remote/.yarnrc +++ b/remote/.yarnrc @@ -1,4 +1,4 @@ disturl "http://nodejs.org/dist" -target "16.14.2" +target "16.17.1" runtime "node" build_from_source "true" diff --git a/remote/package.json b/remote/package.json index 4d4b098327..61a5b52627 100755 --- a/remote/package.json +++ b/remote/package.json @@ -13,9 +13,11 @@ "@angular/router": "~4.1.3", "@microsoft/1ds-core-js": "^3.2.2", "@microsoft/1ds-post-js": "^3.2.2", - "@parcel/watcher": "2.0.5", + "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/ripgrep": "^1.14.2", + "@vscode/proxy-agent": "^0.13.2", + "@vscode/ripgrep": "^1.15.3", + "@vscode/spdlog": "^0.13.10", "@vscode/vscode-languagedetection": "1.0.21", "applicationinsights": "1.4.2", "angular2-grid": "2.0.6", @@ -35,34 +37,34 @@ "mark.js": "^8.11.1", "minimist": "^1.2.6", "moment": "^2.29.4", - "native-watchdog": "1.4.0", - "node-pty": "0.11.0-beta11", + "native-watchdog": "^1.4.1", + "node-pty": "0.11.0-beta32", "plotly.js-dist-min": "^1.53.0", "reflect-metadata": "^0.1.8", "rxjs": "5.4.0", "sanitize-html": "1.19.1", "semver-umd": "^5.5.7", - "spdlog": "^0.13.0", "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.44", "turndown": "^7.0.0", "turndown-plugin-gfm": "^1.0.2", - "tas-client-umd": "0.1.6", - "vscode-oniguruma": "1.6.1", - "vscode-proxy-agent": "^0.12.0", + "tas-client-umd": "0.1.8", + "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", - "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.20", - "xterm-addon-search": "0.10.0-beta.3", - "xterm-addon-serialize": "0.8.0-beta.3", - "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.9", - "xterm-headless": "4.19.0-beta.25", + "vscode-textmate": "9.0.0", + "xterm": "5.2.0-beta.49", + "xterm-addon-canvas": "0.4.0-beta.14", + "xterm-addon-image": "0.4.0", + "xterm-addon-search": "0.12.0-beta.5", + "xterm-addon-serialize": "0.10.0-beta.2", + "xterm-addon-unicode11": "0.5.0", + "xterm-addon-webgl": "0.15.0-beta.15", + "xterm-headless": "5.2.0-beta.49", "yauzl": "^2.9.2", "yazl": "^2.4.3", "zone.js": "^0.11.4" }, "optionalDependencies": { - "@vscode/windows-registry": "1.0.6", - "windows-process-tree": "0.3.3" + "@vscode/windows-process-tree": "0.4.2", + "@vscode/windows-registry": "1.0.10" } } diff --git a/remote/web/package.json b/remote/web/package.json index 6b30d09422..40724b29bb 100755 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -34,12 +34,14 @@ "slickgrid": "github:Microsoft/SlickGrid.ADS#2.3.44", "turndown": "^7.0.0", "turndown-plugin-gfm": "^1.0.2", - "tas-client-umd": "0.1.6", - "vscode-oniguruma": "1.6.1", - "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.20", - "xterm-addon-search": "0.10.0-beta.3", - "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.9" + "tas-client-umd": "0.1.8", + "vscode-oniguruma": "1.7.0", + "vscode-textmate": "9.0.0", + "xterm": "5.2.0-beta.49", + "xterm-addon-canvas": "0.4.0-beta.14", + "xterm-addon-image": "0.4.0", + "xterm-addon-search": "0.12.0-beta.5", + "xterm-addon-unicode11": "0.5.0", + "xterm-addon-webgl": "0.15.0-beta.15" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 0d255f4316..46d6101d9a 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -384,10 +384,10 @@ symbol-observable@^1.0.1: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== -tas-client-umd@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.6.tgz#a0cf70a68f50d406773457630666224f0eb545a6" - integrity sha512-eOz5IK4cuNmSZI9QlqlT0FdvgfnnHDB6rjqleFaYAbzYE4RdJzYNiM28zFIXgmOVEgESvfabMFxG8WX5M4z3HA== +tas-client-umd@0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.8.tgz#38bd32d49545417a0ea67fb618e646298e1b67cc" + integrity sha512-0jAAujLmjjGXf9PzrNpjOrr/6CTpSOp8jX80NOHK5nlOTWWpwaZ16EOyrPdHnm2bVfPHvT0/RAD0xyiQHGQvCQ== turndown-plugin-gfm@^1.0.2: version "1.0.2" @@ -406,37 +406,47 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -vscode-oniguruma@1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" - integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== +vscode-oniguruma@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== -vscode-textmate@7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79" - integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw== +vscode-textmate@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" + integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-search@0.10.0-beta.3: - version "0.10.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.3.tgz#5194434d86105637c71f6f20139a9d0b5c1a956a" - integrity sha512-UeGm/ymnp7HUYJJtsP0D+bljOWbdk3MctcLJ+0jv8AmU6YlAzJFtouvYSQrD5SAMyht5CRsvjzFgqic9X02JYg== +xterm-addon-canvas@0.4.0-beta.14: + version "0.4.0-beta.14" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.4.0-beta.14.tgz#ed8b5d2a839608ae5c9c73d1fa11f6ffb5f92be7" + integrity sha512-FBlHGuSjQMuujwOBhuoOO16iLHgeQ9ucOApsTS+Hf0ZpOnFVQX3JQDYAWlxRIpfDa/HiEGYPhAgFjqqFMtXL4Q== -xterm-addon-unicode11@0.4.0-beta.3: - version "0.4.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" - integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== +xterm-addon-image@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.4.0.tgz#36e98fa892db11755a5f6e9654f924e876e29bf8" + integrity sha512-3wumCJo4WTzxvecSMxJ7XtpVQeFe4gE2cdHCyUdo7zagVkS18YXJacGx6DjlAIccdJn6/LhGuD99xOSSvYx9Gw== -xterm-addon-webgl@0.13.0-beta.9: - version "0.13.0-beta.9" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.9.tgz#66a9ac142ae347d0548abbf4e66bb2f35f415adb" - integrity sha512-x1o1tpCqIsICvhcRsZs+BLcwUIdizYS2G4TIH0KBnUDiSN+oSqpVBQNG8qKg56xbK8WtpdbQ9dLB7JR2W5cX0g== +xterm-addon-search@0.12.0-beta.5: + version "0.12.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.12.0-beta.5.tgz#36bae02306a54837b86beebbf4ac1b9b19a0d567" + integrity sha512-ci3SMkjyGR+C9bRaeQerLY8WneJY6oeI/YLkM80ZgH4C3ViuD2foZav2AbcW5I7uwaHz3CFjlKAY6Nf00TRG8g== -xterm@4.20.0-beta.20: - version "4.20.0-beta.20" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.20.tgz#2979a31839f7b8ee3ffe4f063b40c02facdb0fed" - integrity sha512-ltDtTquH+33tXQPFSDqenbgz6LkvIob6l6Rac85L4aX5Ve7P3ubVLrq+lTFJGQn3iiwGqNmnE1t1EUuGhxsXcQ== +xterm-addon-unicode11@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" + integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== + +xterm-addon-webgl@0.15.0-beta.15: + version "0.15.0-beta.15" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.15.0-beta.15.tgz#bc9422425c27ebd86059f6b2465f01c5ec07071a" + integrity sha512-0zFn6Fsvo7jbSWO2TwNUHlAoceWWb2EYI/SgxN+H6+4kpUV1clP53pTQBdUpAtPEjJsVgxERqOPKBFu14TnR0w== + +xterm@5.2.0-beta.49: + version "5.2.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.49.tgz#0c9c176862019800172fc016d136300d1b6531ce" + integrity sha512-kfdiYljgAmjM9VpkNuuDcUJJHB62UiSTVZRYJbQdOU7jCi9mnUTPWov+5PnZIgONGts3t3Dv5kdbNF4UH53uTA== diff --git a/remote/yarn.lock b/remote/yarn.lock index 1261522276..917ac7ea38 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -83,11 +83,13 @@ resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.7.tgz#ede48dd3f85af14ee369c805e5ed5b84222b9fe2" integrity sha512-SK3D3aVt+5vOOccKPnGaJWB5gQ8FuKfjboUJHedMP7gu54HqSCXX5iFXhktGD8nfJb0Go30eDvs/UDoTnR2kOA== -"@parcel/watcher@2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.5.tgz#f913a54e1601b0aac972803829b0eece48de215b" - integrity sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw== +"@parcel/watcher@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.1.0.tgz#5f32969362db4893922c526a842d8af7a8538545" + integrity sha512-8s8yYjd19pDSsBpbkOHnT6Z2+UJSuLQx61pCFM0s5wSRvKCEMDjd/cHY3/GI1szHIWbpXpsJdg3V6ISGGx9xDw== dependencies: + is-glob "^4.0.3" + micromatch "^4.0.5" node-addon-api "^3.2.1" node-gyp-build "^4.3.0" @@ -106,23 +108,60 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/ripgrep@^1.14.2": - version "1.14.2" - resolved "https://registry.yarnpkg.com/@vscode/ripgrep/-/ripgrep-1.14.2.tgz#47c0eec2b64f53d8f7e1b5ffd22a62e229191c34" - integrity sha512-KDaehS8Jfdg1dqStaIPDKYh66jzKd5jy5aYEPzIv0JYFLADPsCSQPBUdsJVXnr0t72OlDcj96W05xt/rSnNFFQ== +"@vscode/proxy-agent@^0.13.2": + version "0.13.2" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.13.2.tgz#0d289826c07faecc4ca07de80a8e5a9459d06119" + integrity sha512-BSUd0NTj44WvG4O9A6N+4R1XhxtPqCYltWeHyNkquX9T//a1US+cd8fxzcZCPd3z7dygdYIPkZAKM+CrefWWOA== + dependencies: + "@tootallnate/once" "^1.1.2" + agent-base "^6.0.2" + debug "^4.3.1" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + socks-proxy-agent "^5.0.0" + optionalDependencies: + "@vscode/windows-ca-certs" "^0.3.1" + +"@vscode/ripgrep@^1.15.3": + version "1.15.3" + resolved "https://registry.yarnpkg.com/@vscode/ripgrep/-/ripgrep-1.15.3.tgz#bd53c555ed7f2f546edc46a47d72b1914a5ba23d" + integrity sha512-fCJP+4MRnhSTWw+GYAH93kSIomWYvdSe5206IqcHofBFcaFKR51XQNU0D5RB26Ps/5zRf5AQS26DIqqbMsB1Cw== dependencies: https-proxy-agent "^5.0.0" proxy-from-env "^1.1.0" +"@vscode/spdlog@^0.13.10": + version "0.13.10" + resolved "https://registry.yarnpkg.com/@vscode/spdlog/-/spdlog-0.13.10.tgz#5476853b968a1bcc389b92175d11e636464858e8" + integrity sha512-BHJN/r2XurLDR0doBhyQ5b+DUjFjqwnOcD4ZjW/7MkuShO+Wn5KVKOl6x/xQLCAlOlQqVVe42n2A7FwuIFpkWw== + dependencies: + bindings "^1.5.0" + mkdirp "^0.5.5" + nan "^2.17.0" + "@vscode/vscode-languagedetection@1.0.21": version "1.0.21" resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== -"@vscode/windows-registry@1.0.6": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.0.6.tgz#8b9fb9a55bf5a0be4ea11849c45ae94c6910e3e4" - integrity sha512-ZW5bz9F3Ta6zsikce2dchyruF3QsRyWYKOJ2dEicS+inReD/oE8Um+KsLVcvrjIb44aSYpsm64DIUmMl15ujtg== +"@vscode/windows-ca-certs@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@vscode/windows-ca-certs/-/windows-ca-certs-0.3.1.tgz#35c88b2d2a52f7759bfb6878906c3d40421ec6a3" + integrity sha512-1B6hZAsqg125wuMsXiKIFkBgKx/J7YR4RT/ccYGkWAToPU9MVa40PRe+evLFUmLPH6NmPohEPlCzZLbqgvHCcQ== + dependencies: + node-addon-api "^3.0.2" + +"@vscode/windows-process-tree@0.4.2": + version "0.4.2" + resolved "https://registry.yarnpkg.com/@vscode/windows-process-tree/-/windows-process-tree-0.4.2.tgz#54d010fdeb06dfe3a9c6d58fcb3ed9acfc962f33" + integrity sha512-b20865s1HG1VtGt887KrB1blwFS6p4L1Fl1o/WplO9j7sGBle8sLqkNnGXbCaRNgdIgfXtitmzG366FVynJZdQ== + dependencies: + nan "^2.17.0" + +"@vscode/windows-registry@1.0.10": + version "1.0.10" + resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.0.10.tgz#17e4e2f8fdd41990206d1bab2daf99c803206247" + integrity sha512-n2rLdTgv95fQUpDxZqgAURg9neQGymtOKkLW4eYP2SODmaxoL2WzgrxEz1kW0w5TI+J4tsPeuZylpRfrDJKQWw== agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -203,6 +242,13 @@ bl@^4.0.3: inherits "^2.0.4" readable-stream "^3.4.0" +braces@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -396,6 +442,13 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" @@ -484,6 +537,23 @@ ip@^2.0.0: resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + jquery@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.0.tgz#9980b97d9e4194611c36530e7dc46a58d7340fc9" @@ -539,6 +609,14 @@ mark.js@^8.11.1: resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5" integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ== +micromatch@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -571,7 +649,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -nan@^2.13.2, nan@^2.14.0, nan@^2.17.0: +nan@^2.17.0: version "2.17.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb" integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== @@ -581,10 +659,10 @@ napi-build-utils@^1.0.1: resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== -native-watchdog@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.4.0.tgz#547a1f9f88754c38089c622d405ed1e324c7a545" - integrity sha512-4FynAeGtTpoQ2+5AxVJXGEGsOzPsNYDh8Xmawjgs7YWJe+bbbgt7CYlA/Qx6X+kwtN5Ey1aNSm9MqZa0iNKkGw== +native-watchdog@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.4.2.tgz#cf9f913157ee992723aa372b6137293c663be9b7" + integrity sha512-iT3Uj6FFdrW5vHbQ/ybiznLus9oiUoMJ8A8nyugXv9rV3EBhIodmGs+mztrwQyyBc+PB5/CrskAH/WxaUVRRSQ== node-abi@^3.3.0: version "3.33.0" @@ -608,12 +686,12 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== -node-pty@0.11.0-beta11: - version "0.11.0-beta11" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.11.0-beta11.tgz#10843516868129c26a97253903c46fe0e4520eb0" - integrity sha512-Gw58duqHle4k/BunssCE1CUKKWipRQZTUFhaTegkKC19fw3IXsvillblLUuD2bQL42+3mQCAFSgTDo+OsJzYCQ== +node-pty@0.11.0-beta32: + version "0.11.0-beta32" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-0.11.0-beta32.tgz#49c0f174f600ac3f54a21df2a41b6f78256ff6ce" + integrity sha512-xtzB4/jYH64ksdVatYQnaU3TtCtSaDiiZPsZITmLHnywFSpI2bgfyj/bu6ofOXbe8PTtziL8bDn1U3xkRmx3mg== dependencies: - nan "^2.14.0" + nan "^2.17.0" number-is-nan@^1.0.0: version "1.0.1" @@ -632,6 +710,11 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + plotly.js-dist-min@^1.53.0: version "1.58.5" resolved "https://registry.yarnpkg.com/plotly.js-dist-min/-/plotly.js-dist-min-1.58.5.tgz#bd511199c876240fbc4b0d2e5810d247d0b33894" @@ -796,15 +879,6 @@ source-map@^0.6.1: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -spdlog@^0.13.0: - version "0.13.7" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.13.7.tgz#719a972be103e473770202e37dca1c9d80502180" - integrity sha512-DiWxvyHuDJKfNuanSnizY2pmqZGaSHej3xpOD4LQ+kkT3oLWpCXI6VRFDnziyXBQKCl8kmyaYnOu9QBhf1WEXQ== - dependencies: - bindings "^1.5.0" - mkdirp "^0.5.5" - nan "^2.17.0" - srcset@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef" @@ -816,7 +890,7 @@ srcset@^1.0.0: stack-chain@^1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" - integrity sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU= + integrity sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug== string_decoder@^1.1.1: version "1.3.0" @@ -863,10 +937,17 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" -tas-client-umd@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.6.tgz#a0cf70a68f50d406773457630666224f0eb545a6" - integrity sha512-eOz5IK4cuNmSZI9QlqlT0FdvgfnnHDB6rjqleFaYAbzYE4RdJzYNiM28zFIXgmOVEgESvfabMFxG8WX5M4z3HA== +tas-client-umd@0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/tas-client-umd/-/tas-client-umd-0.1.8.tgz#38bd32d49545417a0ea67fb618e646298e1b67cc" + integrity sha512-0jAAujLmjjGXf9PzrNpjOrr/6CTpSOp8jX80NOHK5nlOTWWpwaZ16EOyrPdHnm2bVfPHvT0/RAD0xyiQHGQvCQ== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" tslib@^2.3.0: version "2.5.0" @@ -886,9 +967,9 @@ turndown-plugin-gfm@^1.0.2: integrity sha512-vwz9tfvF7XN/jE0dGoBei3FXWuvll78ohzCZQuOb+ZjWrs3a0XhQVomJEb2Qh4VHTPNRO4GPZh0V7VRbiWwkRg== turndown@^7.0.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/turndown/-/turndown-7.1.1.tgz#96992f2d9b40a1a03d3ea61ad31b5a5c751ef77f" - integrity sha512-BEkXaWH7Wh7e9bd2QumhfAXk5g34+6QUmmWx+0q6ThaVOLuLUqsnkq35HQ5SBHSaxjSfSM7US5o4lhJNH7B9MA== + version "7.1.2" + resolved "https://registry.yarnpkg.com/turndown/-/turndown-7.1.2.tgz#7feb838c78f14241e79ed92a416e0d213e044a29" + integrity sha512-ntI9R7fcUKjqBP6QU8rBK2Ehyt8LAzt3UBT9JR9tgo6GtuKvyUzpayWmeMKJw1DPdXzktvtIT8m2mVXz+bL/Qg== dependencies: domino "^2.1.6" @@ -897,48 +978,20 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -vscode-oniguruma@1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.1.tgz#2bf4dfcfe3dd2e56eb549a3068c8ee39e6c30ce5" - integrity sha512-vc4WhSIaVpgJ0jJIejjYxPvURJavX6QG41vu0mGhqywMkQqulezEqEQ3cO3gc8GvcOpX6ycmKGqRoROEMBNXTQ== - -vscode-proxy-agent@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.12.0.tgz#0775f464b9519b0c903da4dcf50851e1453f4e48" - integrity sha512-jS7950hE9Kq6T18vYewVl0N9acEBD3d+scbPew2Nti7d61THBrhVF9FQjc8TLfrUZ//UzzOFO8why+F0kHDdNw== - dependencies: - "@tootallnate/once" "^1.1.2" - agent-base "^6.0.2" - debug "^4.3.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - socks-proxy-agent "^5.0.0" - optionalDependencies: - vscode-windows-ca-certs "^0.3.0" +vscode-oniguruma@1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz#439bfad8fe71abd7798338d1cd3dc53a8beea94b" + integrity sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA== 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-textmate@7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79" - integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw== - -vscode-windows-ca-certs@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/vscode-windows-ca-certs/-/vscode-windows-ca-certs-0.3.0.tgz#324e1f8ba842bbf048a39e7c0ee8fe655e9adfcc" - integrity sha512-CYrpCEKmAFQJoZNReOrelNL+VKyebOVRCqL9evrBlVcpWQDliliJgU5RggGS8FPGtQ3jAKLQt9frF0qlxYYPKA== - dependencies: - node-addon-api "^3.0.2" - -windows-process-tree@0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.3.3.tgz#7c178815f02bf4cfbcac1f93b2f3a3cc10bc9245" - integrity sha512-rkiAMP0AS27xikFyn7i4gPbOK16UdjY8X/C6eo37CnfNLqTvK2eEaT+Dh0e5xnvmlsi0lEKd60O+4ajzfDkq7A== - dependencies: - nan "^2.13.2" +vscode-textmate@9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-9.0.0.tgz#313c6c8792b0507aef35aeb81b6b370b37c44d6c" + integrity sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg== wrappy@1: version "1.0.2" @@ -950,35 +1003,45 @@ xtend@^4.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -xterm-addon-search@0.10.0-beta.3: - version "0.10.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.3.tgz#5194434d86105637c71f6f20139a9d0b5c1a956a" - integrity sha512-UeGm/ymnp7HUYJJtsP0D+bljOWbdk3MctcLJ+0jv8AmU6YlAzJFtouvYSQrD5SAMyht5CRsvjzFgqic9X02JYg== +xterm-addon-canvas@0.4.0-beta.14: + version "0.4.0-beta.14" + resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.4.0-beta.14.tgz#ed8b5d2a839608ae5c9c73d1fa11f6ffb5f92be7" + integrity sha512-FBlHGuSjQMuujwOBhuoOO16iLHgeQ9ucOApsTS+Hf0ZpOnFVQX3JQDYAWlxRIpfDa/HiEGYPhAgFjqqFMtXL4Q== -xterm-addon-serialize@0.8.0-beta.3: - version "0.8.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.3.tgz#47ade3fedacbb75bd26e63cfe0120586623e0e4f" - integrity sha512-gvfempZCYuAhLqN4O6fA2TuoavPjOxFKlh8hLcOzPackiLUhwKr1jQpDXcnq8VgqUiGgb+XNZpPEbI0Q7EhTgA== +xterm-addon-image@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/xterm-addon-image/-/xterm-addon-image-0.4.0.tgz#36e98fa892db11755a5f6e9654f924e876e29bf8" + integrity sha512-3wumCJo4WTzxvecSMxJ7XtpVQeFe4gE2cdHCyUdo7zagVkS18YXJacGx6DjlAIccdJn6/LhGuD99xOSSvYx9Gw== -xterm-addon-unicode11@0.4.0-beta.3: - version "0.4.0-beta.3" - resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" - integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== +xterm-addon-search@0.12.0-beta.5: + version "0.12.0-beta.5" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.12.0-beta.5.tgz#36bae02306a54837b86beebbf4ac1b9b19a0d567" + integrity sha512-ci3SMkjyGR+C9bRaeQerLY8WneJY6oeI/YLkM80ZgH4C3ViuD2foZav2AbcW5I7uwaHz3CFjlKAY6Nf00TRG8g== -xterm-addon-webgl@0.13.0-beta.9: - version "0.13.0-beta.9" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.9.tgz#66a9ac142ae347d0548abbf4e66bb2f35f415adb" - integrity sha512-x1o1tpCqIsICvhcRsZs+BLcwUIdizYS2G4TIH0KBnUDiSN+oSqpVBQNG8qKg56xbK8WtpdbQ9dLB7JR2W5cX0g== +xterm-addon-serialize@0.10.0-beta.2: + version "0.10.0-beta.2" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.10.0-beta.2.tgz#331a5f54ab1380e4d5fcde7f03ac28b138efa803" + integrity sha512-cCUoWGTFcBT4kDNhUP/Lohl5hJStEtxprJErYdhvT8lorPhA+zai6Sv0sT3A5Je5nmG2rSrS9hPZUj6kmPjGvw== -xterm-headless@4.19.0-beta.25: - version "4.19.0-beta.25" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.25.tgz#a0a1b59f386c44458f06b8ced64e3567371cc983" - integrity sha512-UswSgymk3g9i6XTpFAasnqqIvWhi+AEWT+iO3kkjII6ll+dYEQgeZAv92EnCmeRHp11u5TP+IBAo8jy+aTYbtA== +xterm-addon-unicode11@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.5.0.tgz#41c0d96acc1e3bb6c6596eee64e163b6bca74be7" + integrity sha512-Jm4/g4QiTxiKiTbYICQgC791ubhIZyoIwxAIgOW8z8HWFNY+lwk+dwaKEaEeGBfM48Vk8fklsUW9u/PlenYEBg== -xterm@4.20.0-beta.20: - version "4.20.0-beta.20" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.20.tgz#2979a31839f7b8ee3ffe4f063b40c02facdb0fed" - integrity sha512-ltDtTquH+33tXQPFSDqenbgz6LkvIob6l6Rac85L4aX5Ve7P3ubVLrq+lTFJGQn3iiwGqNmnE1t1EUuGhxsXcQ== +xterm-addon-webgl@0.15.0-beta.15: + version "0.15.0-beta.15" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.15.0-beta.15.tgz#bc9422425c27ebd86059f6b2465f01c5ec07071a" + integrity sha512-0zFn6Fsvo7jbSWO2TwNUHlAoceWWb2EYI/SgxN+H6+4kpUV1clP53pTQBdUpAtPEjJsVgxERqOPKBFu14TnR0w== + +xterm-headless@5.2.0-beta.49: + version "5.2.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.2.0-beta.49.tgz#ac6e833fa025441503db27980f40ab4ae549a3f4" + integrity sha512-jaNvl5f8Qx5xG1i79/7pzlAzUMC0Or8sPW7v/R7bw5MenMZPE5sE0pYNYW3xWB91voBPbGuXvrMdtraY8gL/Og== + +xterm@5.2.0-beta.49: + version "5.2.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.2.0-beta.49.tgz#0c9c176862019800172fc016d136300d1b6531ce" + integrity sha512-kfdiYljgAmjM9VpkNuuDcUJJHB62UiSTVZRYJbQdOU7jCi9mnUTPWov+5PnZIgONGts3t3Dv5kdbNF4UH53uTA== yallist@^4.0.0: version "4.0.0" diff --git a/resources/completions/bash/code b/resources/completions/bash/code index c9e4b16730..9f1b3d682d 100644 --- a/resources/completions/bash/code +++ b/resources/completions/bash/code @@ -31,7 +31,7 @@ _@@APPNAME@@() COMPREPLY=( $( compgen -W 'critical error warn info debug trace off' ) ) return ;; - --folder-uri|--disable-extension|--max-memory) + --folder-uri|--disable-extension) # argument required but no completions available return 0 ;; @@ -50,8 +50,7 @@ _@@APPNAME@@() --uninstall-extension --enable-proposed-api --verbose --log -s --status -p --performance --prof-startup --disable-extensions --disable-extension --inspect-extensions - --inspect-brk-extensions --disable-gpu - --max-memory=' -- "$cur") ) + --inspect-brk-extensions --disable-gpu' -- "$cur") ) [[ $COMPREPLY == *= ]] && compopt -o nospace return fi diff --git a/resources/completions/zsh/_code b/resources/completions/zsh/_code index 087ea61f56..97d163c13c 100644 --- a/resources/completions/zsh/_code +++ b/resources/completions/zsh/_code @@ -32,7 +32,6 @@ arguments=( '--inspect-extensions[allow debugging and profiling of extensions]' '--inspect-brk-extensions[allow debugging and profiling of extensions with the extension host being paused after start]' '--disable-gpu[disable GPU hardware acceleration]' - '--max-memory=[max memory size for a window (in Mbytes)]:size (Mbytes)' '*:file or directory:_files' ) diff --git a/resources/linux/rpm/code.spec.template b/resources/linux/rpm/code.spec.template index 00ddb6fdf0..06b8549256 100644 --- a/resources/linux/rpm/code.spec.template +++ b/resources/linux/rpm/code.spec.template @@ -11,7 +11,7 @@ Icon: @@NAME@@.xpm Requires: @@DEPENDENCIES@@ AutoReq: 0 -%global __provides_exclude_from ^%{_datadir}/@@NAME@@/.*\\.so.*$ +%global __provides_exclude_from ^%{_datadir}/%{name}/.*\\.so.*$ %description Visual Studio Code is a new choice of tool that combines the simplicity of a code editor with what developers need for the core edit-build-debug cycle. See https://code.visualstudio.com/docs/setup/linux for installation instructions and FAQ. @@ -21,25 +21,31 @@ Visual Studio Code is a new choice of tool that combines the simplicity of a cod %define _build_id_links none %install -mkdir -p %{buildroot}/usr/bin -mkdir -p %{buildroot}/usr/share/@@NAME@@ -mkdir -p %{buildroot}/usr/share/applications -mkdir -p %{buildroot}/usr/share/pixmaps -mkdir -p %{buildroot}/usr/share/bash-completion/completions -mkdir -p %{buildroot}/usr/share/zsh/site-functions -mkdir -p %{buildroot}/usr/share/mime/packages -cp -r usr/share/@@NAME@@/* %{buildroot}/usr/share/@@NAME@@ -cp -r usr/share/applications/@@NAME@@.desktop %{buildroot}/usr/share/applications -cp -r usr/share/applications/@@NAME@@-url-handler.desktop %{buildroot}/usr/share/applications -cp -r usr/share/mime/packages/@@NAME@@-workspace.xml %{buildroot}/usr/share/mime/packages/@@NAME@@-workspace.xml -cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}/usr/share/pixmaps -cp usr/share/bash-completion/completions/@@NAME@@ %{buildroot}/usr/share/bash-completion/completions/@@NAME@@ -cp usr/share/zsh/site-functions/_@@NAME@@ %{buildroot}/usr/share/zsh/site-functions/_@@NAME@@ -ln -s ../share/@@NAME@@/bin/@@NAME@@ %{buildroot}/usr/bin/@@NAME@@ +# Destination directories +mkdir -p %{buildroot}%{_bindir} +mkdir -p %{buildroot}%{_datadir}/%{name} +mkdir -p %{buildroot}%{_datadir}/applications +mkdir -p %{buildroot}%{_datadir}/appdata +mkdir -p %{buildroot}%{_datadir}/mime/packages +mkdir -p %{buildroot}%{_datadir}/pixmaps +mkdir -p %{buildroot}%{_datadir}/bash-completion/completions +mkdir -p %{buildroot}%{_datadir}/zsh/site-functions +# Application +cp -r usr/share/%{name}/* %{buildroot}%{_datadir}/%{name} +ln -s %{_datadir}/%{name}/bin/%{name} %{buildroot}%{_bindir}/%{name} +# Support files +cp -r usr/share/applications/%{name}.desktop %{buildroot}%{_datadir}/applications +cp -r usr/share/applications/%{name}-url-handler.desktop %{buildroot}%{_datadir}/applications +cp -r usr/share/appdata/%{name}.appdata.xml %{buildroot}%{_datadir}/appdata +cp -r usr/share/mime/packages/%{name}-workspace.xml %{buildroot}%{_datadir}/mime/packages/%{name}-workspace.xml +cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}%{_datadir}/pixmaps +# Shell completions +cp usr/share/bash-completion/completions/%{name} %{buildroot}%{_datadir}/bash-completion/completions/%{name} +cp usr/share/zsh/site-functions/_%{name} %{buildroot}%{_datadir}/zsh/site-functions/_%{name} %post # Remove the legacy bin command if this is the stable build -if [ "@@NAME@@" = "code" ]; then +if [ "%{name}" = "code" ]; then rm -f /usr/local/bin/code fi @@ -54,21 +60,22 @@ fi #fi # Update mimetype database to pickup workspace mimetype -update-mime-database /usr/share/mime &> /dev/null || : +update-mime-database %{_datadir}/mime &> /dev/null || : %postun # Update mimetype database for removed workspace mimetype -update-mime-database /usr/share/mime &> /dev/null || : +update-mime-database %{_datadir}/mime &> /dev/null || : %files %defattr(-,root,root) -%attr(4755, root, root) /usr/share/@@NAME@@/chrome-sandbox +%attr(4755, root, root) %{_datadir}/%{name}/chrome-sandbox -/usr/bin/@@NAME@@ -/usr/share/@@NAME@@/ -/usr/share/applications/@@NAME@@.desktop -/usr/share/applications/@@NAME@@-url-handler.desktop -/usr/share/mime/packages/@@NAME@@-workspace.xml -/usr/share/pixmaps/@@ICON@@.png -/usr/share/bash-completion/completions/@@NAME@@ -/usr/share/zsh/site-functions/_@@NAME@@ +%{_bindir}/%{name} +%{_datadir}/%{name}/ +%{_datadir}/applications/%{name}.desktop +%{_datadir}/applications/%{name}-url-handler.desktop +%{_datadir}/appdata/%{name}.appdata.xml +%{_datadir}/mime/packages/%{name}-workspace.xml +%{_datadir}/pixmaps/@@ICON@@.png +%{_datadir}/bash-completion/completions/%{name} +%{_datadir}/zsh/site-functions/_%{name} diff --git a/resources/linux/snap/electron-launch b/resources/linux/snap/electron-launch index 87011928b4..fd18e11e44 100644 --- a/resources/linux/snap/electron-launch +++ b/resources/linux/snap/electron-launch @@ -4,6 +4,64 @@ # We need to handle that case and reset $SNAP SNAP=$(echo "$SNAP" | sed -e "s|/var/lib/snapd||g") +# +# Exports are based on https://github.com/snapcore/snapcraft/blob/master/extensions/desktop/common/desktop-exports +# + +# ensure_dir_exists calls `mkdir -p` if the given path is not a directory. +# This speeds up execution time by avoiding unnecessary calls to mkdir. +# +# Usage: ensure_dir_exists []... +# +function ensure_dir_exists() { + [ -d "$1" ] || mkdir -p "$@" +} + +declare -A PIDS +function async_exec() { + "$@" & + PIDS[$!]=$* +} +function wait_for_async_execs() { + for pid in "${!PIDS[@]}" + do + wait "$pid" && continue || echo "ERROR: ${PIDS[$pid]} exited abnormally with status $?" + done +} + +function prepend_dir() { + local -n var="$1" + local dir="$2" + # We can't check if the dir exists when the dir contains variables + if [[ "$dir" == *"\$"* || -d "$dir" ]]; then + export "${!var}=${dir}${var:+:$var}" + fi +} + +function append_dir() { + local -n var="$1" + local dir="$2" + # We can't check if the dir exists when the dir contains variables + if [[ "$dir" == *"\$"* || -d "$dir" ]]; then + export "${!var}=${var:+$var:}${dir}" + fi +} + +# shellcheck source=/dev/null +source "$SNAP_USER_DATA/.last_revision" 2>/dev/null || true +if [ "$SNAP_DESKTOP_LAST_REVISION" = "$SNAP_VERSION" ]; then + needs_update=false +else + needs_update=true +fi + +# Set $REALHOME to the users real home directory +REALHOME=$(getent passwd $UID | cut -d ':' -f 6) + +# Set config folder to local path +ensure_dir_exists "$SNAP_USER_DATA/.config" +chmod 700 "$SNAP_USER_DATA/.config" + if [ "$SNAP_ARCH" == "amd64" ]; then ARCH="x86_64-linux-gnu" elif [ "$SNAP_ARCH" == "armhf" ]; then @@ -14,21 +72,171 @@ else ARCH="$SNAP_ARCH-linux-gnu" fi -GDK_CACHE_DIR="$SNAP_USER_COMMON/.cache" -if [[ -d "$SNAP_USER_DATA/.cache" && ! -e "$GDK_CACHE_DIR" ]]; then +export SNAP_LAUNCHER_ARCH_TRIPLET="$ARCH" + +function is_subpath() { + dir="$(realpath "$1")" + parent="$(realpath "$2")" + [ "${dir##"${parent}"/}" != "${dir}" ] && return 0 || return 1 +} + +function can_open_file() { + [ -f "$1" ] && [ -r "$1" ] +} + +# XDG Config +prepend_dir XDG_CONFIG_DIRS "$SNAP/etc/xdg" + +# Define snaps' own data dir +prepend_dir XDG_DATA_DIRS "$SNAP/usr/share" +prepend_dir XDG_DATA_DIRS "$SNAP/share" +prepend_dir XDG_DATA_DIRS "$SNAP/data-dir" +prepend_dir XDG_DATA_DIRS "$SNAP_USER_DATA" + +# Set XDG_DATA_HOME to local path +ensure_dir_exists "$SNAP_USER_DATA/.local/share" + +# Workaround for GLib < 2.53.2 not searching for schemas in $XDG_DATA_HOME: +# https://bugzilla.gnome.org/show_bug.cgi?id=741335 +prepend_dir XDG_DATA_DIRS "$SNAP_USER_DATA/.local/share" + +# Set cache folder to local path +if [[ -d "$SNAP_USER_DATA/.cache" && ! -e "$SNAP_USER_COMMON/.cache" ]]; then # the .cache directory used to be stored under $SNAP_USER_DATA, migrate it mv "$SNAP_USER_DATA/.cache" "$SNAP_USER_COMMON/" fi -[ ! -d "$GDK_CACHE_DIR" ] && mkdir -p "$GDK_CACHE_DIR" +ensure_dir_exists "$SNAP_USER_COMMON/.cache" -# Gdk-pixbuf loaders -export GDK_PIXBUF_MODULE_FILE="$GDK_CACHE_DIR/gdk-pixbuf-loaders.cache" -export GDK_PIXBUF_MODULEDIR="$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/2.10.0/loaders" -if [ -f "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" ]; then - "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" > "$GDK_PIXBUF_MODULE_FILE" +# Create $XDG_RUNTIME_DIR if not exists (to be removed when LP: #1656340 is fixed) +# shellcheck disable=SC2174 +ensure_dir_exists "$XDG_RUNTIME_DIR" -m 700 + +# Ensure the app finds locale definitions (requires locales-all to be installed) +append_dir LOCPATH "$SNAP/usr/lib/locale" + +# If detect wayland server socket, then set environment so applications prefer +# wayland, and setup compat symlink (until we use user mounts. Remember, +# XDG_RUNTIME_DIR is /run/user//snap.$SNAP so look in the parent directory +# for the socket. For details: +# https://forum.snapcraft.io/t/wayland-dconf-and-xdg-runtime-dir/186/10 +# Applications that don't support wayland natively may define DISABLE_WAYLAND +# (to any non-empty value) to skip that logic entirely. +wayland_available=false +if [[ -n "$XDG_RUNTIME_DIR" && -z "$DISABLE_WAYLAND" ]]; then + wdisplay="wayland-0" + if [ -n "$WAYLAND_DISPLAY" ]; then + wdisplay="$WAYLAND_DISPLAY" + fi + wayland_sockpath="$XDG_RUNTIME_DIR/../$wdisplay" + wayland_snappath="$XDG_RUNTIME_DIR/$wdisplay" + if [ -S "$wayland_sockpath" ]; then + # if running under wayland, use it + #export WAYLAND_DEBUG=1 + # shellcheck disable=SC2034 + wayland_available=true + # create the compat symlink for now + if [ ! -e "$wayland_snappath" ]; then + ln -s "$wayland_sockpath" "$wayland_snappath" + fi + fi fi -# Create $XDG_RUNTIME_DIR if not exists (to be removed when https://pad.lv/1656340 is fixed) -[ -n "$XDG_RUNTIME_DIR" ] && mkdir -p -m 700 "$XDG_RUNTIME_DIR" +# Keep an array of data dirs, for looping through them +IFS=':' read -r -a data_dirs_array <<< "$XDG_DATA_DIRS" + +# Build mime.cache +# needed for gtk and qt icon +if [ "$needs_update" = true ]; then + rm -rf "$SNAP_USER_DATA/.local/share/mime" + if [ ! -f "$SNAP/usr/share/mime/mime.cache" ]; then + if command -v update-mime-database >/dev/null; then + cp --preserve=timestamps -dR "$SNAP/usr/share/mime" "$SNAP_USER_DATA/.local/share" + async_exec update-mime-database "$SNAP_USER_DATA/.local/share/mime" + fi + fi +fi + +# Gio modules and cache (including gsettings module) +export GIO_MODULE_DIR="$SNAP_USER_COMMON/.cache/gio-modules" +function compile_giomodules { + if [ -f "$1/glib-2.0/gio-querymodules" ]; then + rm -rf "$GIO_MODULE_DIR" + ensure_dir_exists "$GIO_MODULE_DIR" + ln -s "$SNAP"/usr/lib/"$ARCH"/gio/modules/*.so "$GIO_MODULE_DIR" + "$1/glib-2.0/gio-querymodules" "$GIO_MODULE_DIR" + fi +} +if [ "$needs_update" = true ]; then + async_exec compile_giomodules "/snap/core20/current/usr/lib/$ARCH" +fi + +# Setup compiled gsettings schema +export GSETTINGS_SCHEMA_DIR="$SNAP_USER_DATA/.local/share/glib-2.0/schemas" +function compile_schemas { + if [ -f "$1" ]; then + rm -rf "$GSETTINGS_SCHEMA_DIR" + ensure_dir_exists "$GSETTINGS_SCHEMA_DIR" + for ((i = 0; i < ${#data_dirs_array[@]}; i++)); do + schema_dir="${data_dirs_array[$i]}/glib-2.0/schemas" + if [ -f "$schema_dir/gschemas.compiled" ]; then + # This directory already has compiled schemas + continue + fi + if [ -n "$(ls -A "$schema_dir"/*.xml 2>/dev/null)" ]; then + ln -s "$schema_dir"/*.xml "$GSETTINGS_SCHEMA_DIR" + fi + if [ -n "$(ls -A "$schema_dir"/*.override 2>/dev/null)" ]; then + ln -s "$schema_dir"/*.override "$GSETTINGS_SCHEMA_DIR" + fi + done + # Only compile schemas if we copied anything + if [ -n "$(ls -A "$GSETTINGS_SCHEMA_DIR"/*.xml "$GSETTINGS_SCHEMA_DIR"/*.override 2>/dev/null)" ]; then + "$1" "$GSETTINGS_SCHEMA_DIR" + fi + fi +} +if [ "$needs_update" = true ]; then + async_exec compile_schemas "/snap/core20/current/usr/lib/$ARCH/glib-2.0/glib-compile-schemas" +fi + +# Gdk-pixbuf loaders +export GDK_PIXBUF_MODULE_FILE="$SNAP_USER_COMMON/.cache/gdk-pixbuf-loaders.cache" +export GDK_PIXBUF_MODULEDIR="$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/2.10.0/loaders" +if [ "$needs_update" = true ] || [ ! -f "$GDK_PIXBUF_MODULE_FILE" ]; then + rm -f "$GDK_PIXBUF_MODULE_FILE" + if [ -f "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" ]; then + async_exec "$SNAP/usr/lib/$ARCH/gdk-pixbuf-2.0/gdk-pixbuf-query-loaders" > "$GDK_PIXBUF_MODULE_FILE" + fi +fi + +# shellcheck disable=SC2154 +if [ "$wayland_available" = true ]; then + export GDK_BACKEND="wayland" +fi + +append_dir GTK_PATH "$SNAP/usr/lib/$ARCH/gtk-3.0" +append_dir GTK_PATH "$SNAP/usr/lib/gtk-3.0" +# We don't have gtk libraries in this path but +# enforcing this environment variable will disallow +# gtk binaries like `gtk-query-immodules` to not search +# in system default library paths. +# Based on https://gitlab.gnome.org/GNOME/gtk/-/blob/main/gtk/gtkmodules.c#L104-136 +export GTK_EXE_PREFIX="$SNAP/usr" + +# ibus and fcitx integration +GTK_IM_MODULE_DIR="$SNAP_USER_COMMON/.cache/immodules" +export GTK_IM_MODULE_FILE="$GTK_IM_MODULE_DIR/immodules.cache" +# shellcheck disable=SC2154 +if [ "$needs_update" = true ]; then + rm -rf "$GTK_IM_MODULE_DIR" + ensure_dir_exists "$GTK_IM_MODULE_DIR" + ln -s "$SNAP"/usr/lib/"$ARCH"/gtk-3.0/3.0.0/immodules/*.so "$GTK_IM_MODULE_DIR" + async_exec "$SNAP/usr/lib/$ARCH/libgtk-3-0/gtk-query-immodules-3.0" > "$GTK_IM_MODULE_FILE" +fi + +# shellcheck disable=SC2154 +[ "$needs_update" = true ] && echo "SNAP_DESKTOP_LAST_REVISION=$SNAP_VERSION" > "$SNAP_USER_DATA/.last_revision" + +wait_for_async_execs exec "$@" diff --git a/resources/linux/snap/snapcraft.yaml b/resources/linux/snap/snapcraft.yaml index fc775b6554..0b1cfc7053 100644 --- a/resources/linux/snap/snapcraft.yaml +++ b/resources/linux/snap/snapcraft.yaml @@ -12,55 +12,70 @@ architectures: grade: stable confinement: classic +base: core20 +compression: lzo parts: - gnome: - plugin: nil - build-packages: - - software-properties-common - override-pull: | - add-apt-repository -y ppa:ubuntu-desktop/gnome-3-26 - apt -y update - code: - after: - - gnome plugin: dump source: . stage-packages: - - ibus-gtk3 - - fcitx-frontend-gtk3 - - gvfs-libs + - ca-certificates - libasound2 - - libgconf-2-4 - - libglib2.0-bin - - libgnome-keyring0 + - libatk-bridge2.0-0 + - libatk1.0-0 + - libatspi2.0-0 + - libcairo2 + - libcanberra-gtk3-module + - libcurl3-gnutls + - libcurl3-nss + - libcurl4 + - libdrm2 - libgbm1 + - libgl1 + - libglib2.0-0 - libgtk-3-0 - - libnotify4 - - libnspr4 + - libibus-1.0-5 - libnss3 - - libpcre3 - - libpulse0 + - libpango-1.0-0 - libsecret-1-0 + - libxcomposite1 + - libxdamage1 + - libxfixes3 + - libxkbcommon0 + - libxkbfile1 + - libxrandr2 - libxss1 - - libxtst6 - - zlib1g + - locales-all + - packagekit-gtk3-module + - xdg-utils prime: - -usr/share/doc - -usr/share/fonts - -usr/share/icons - -usr/share/lintian - -usr/share/man + override-build: | + snapcraftctl build + patchelf --force-rpath --set-rpath '$ORIGIN/../../lib/x86_64-linux-gnu:$ORIGIN:/snap/core20/current/lib/x86_64-linux-gnu' $SNAPCRAFT_PART_INSTALL/usr/share/@@NAME@@/chrome_crashpad_handler + cleanup: + after: + - code + plugin: nil + build-snaps: + - core20 + override-prime: | + set -eux + for snap in "core20"; do + cd "/snap/$snap/current" && find . -type f,l -exec rm -f "$SNAPCRAFT_PRIME/{}" \; + done + patchelf --print-rpath $SNAPCRAFT_PRIME/usr/share/@@NAME@@/chrome_crashpad_handler + apps: @@NAME@@: command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --no-sandbox common-id: @@NAME@@.desktop - environment: - GSETTINGS_SCHEMA_DIR: $SNAP/usr/share/glib-2.0/schemas url-handler: command: electron-launch $SNAP/usr/share/@@NAME@@/bin/@@NAME@@ --open-url --no-sandbox - environment: - GSETTINGS_SCHEMA_DIR: $SNAP/usr/share/glib-2.0/schemas diff --git a/resources/server/bin/server-old.cmd b/resources/server/bin/server-old.cmd deleted file mode 100644 index 3b459b30d3..0000000000 --- a/resources/server/bin/server-old.cmd +++ /dev/null @@ -1,24 +0,0 @@ -@echo off -setlocal - -set ROOT_DIR=%~dp0 - -set _FIRST_ARG=%1 -if "%_FIRST_ARG:~0,9%"=="--inspect" ( - set INSPECT=%1 - shift -) else ( - set INSPECT= -) - -:loop1 -if "%~1"=="" goto after_loop -set RESTVAR=%RESTVAR% %1 -shift -goto loop1 - -:after_loop - -"%ROOT_DIR%node.exe" %INSPECT% "%ROOT_DIR%out\server-main.js" --compatibility=1.63 %RESTVAR% - -endlocal diff --git a/resources/server/bin/server-old.sh b/resources/server/bin/server-old.sh deleted file mode 100644 index 7861d790be..0000000000 --- a/resources/server/bin/server-old.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env sh -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# - -case "$1" in - --inspect*) INSPECT="$1"; shift;; -esac - -ROOT="$(dirname "$0")" - -"$ROOT/node" ${INSPECT:-} "$ROOT/out/server-main.js" --compatibility=1.63 "$@" diff --git a/resources/server/manifest.json b/resources/server/manifest.json index 3b64fbb9ee..d92ca7ac01 100644 --- a/resources/server/manifest.json +++ b/resources/server/manifest.json @@ -3,7 +3,9 @@ "short_name": "Code- OSS", "start_url": "/", "lang": "en-US", - "display": "standalone", + "display_override": [ + "window-controls-overlay" + ], "icons": [ { "src": "code-192.png", diff --git a/scripts/code-perf.js b/scripts/code-perf.js new file mode 100644 index 0000000000..f6f6c19deb --- /dev/null +++ b/scripts/code-perf.js @@ -0,0 +1,91 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// @ts-check + +const path = require('path'); +const perf = require('@vscode/vscode-perf'); + +const VSCODE_FOLDER = path.join(__dirname, '..'); + +async function main() { + + const args = process.argv; + /** @type {string | undefined} */ + let build = undefined; + + if (args.indexOf('--help') === -1 && args.indexOf('-h') === -1) { + // get build arg from args + let buildArgIndex = args.indexOf('--build'); + buildArgIndex = buildArgIndex === -1 ? args.indexOf('-b') : buildArgIndex; + if (buildArgIndex === -1) { + let runtimeArgIndex = args.indexOf('--runtime'); + runtimeArgIndex = runtimeArgIndex === -1 ? args.indexOf('-r') : runtimeArgIndex; + if (runtimeArgIndex !== -1 && args[runtimeArgIndex + 1] !== 'desktop') { + console.error('Please provide the --build argument. It is an executable file for desktop or a URL for web'); + process.exit(1); + } + build = getLocalCLIPath(); + } else { + build = args[buildArgIndex + 1]; + if (build !== 'insider' && build !== 'stable' && build !== 'exploration') { + build = getExePath(args[buildArgIndex + 1]); + } + args.splice(buildArgIndex + 1, 1); + } + + args.push('--folder'); + args.push(VSCODE_FOLDER); + args.push('--file'); + args.push(path.join(VSCODE_FOLDER, 'package.json')); + } + + if (build) { + args.push('--build'); + args.push(build); + } + + await perf.run(); + process.exit(0); +} + +/** + * @param {string} buildPath + * @returns {string} + */ +function getExePath(buildPath) { + buildPath = path.normalize(path.resolve(buildPath)); + if (buildPath === path.normalize(getLocalCLIPath())) { + return buildPath; + } + let relativeExePath; + switch (process.platform) { + case 'darwin': + relativeExePath = path.join('Contents', 'MacOS', 'Electron'); + break; + case 'linux': { + const product = require(path.join(buildPath, 'resources', 'app', 'product.json')); + relativeExePath = product.applicationName; + break; + } + case 'win32': { + const product = require(path.join(buildPath, 'resources', 'app', 'product.json')); + relativeExePath = `${product.nameShort}.exe`; + break; + } + default: + throw new Error('Unsupported platform.'); + } + return buildPath.endsWith(relativeExePath) ? buildPath : path.join(buildPath, relativeExePath); +} + +/** + * @returns {string} + */ +function getLocalCLIPath() { + return process.platform === 'win32' ? path.join(VSCODE_FOLDER, 'scripts', 'code.bat') : path.join(VSCODE_FOLDER, 'scripts', 'code.sh'); +} + +main(); diff --git a/scripts/code-web.js b/scripts/code-web.js index 2057026d4a..42074ee539 100644 --- a/scripts/code-web.js +++ b/scripts/code-web.js @@ -14,9 +14,8 @@ const cp = require('child_process'); const minimist = require('minimist'); const fancyLog = require('fancy-log'); const ansiColors = require('ansi-colors'); -const remote = require('gulp-remote-retry-src'); -const vfs = require('vinyl-fs'); const opn = require('opn'); +const https = require('https'); const APP_ROOT = path.join(__dirname, '..'); const WEB_DEV_EXTENSIONS_ROOT = path.join(APP_ROOT, '.build', 'builtInWebDevExtensions'); @@ -41,8 +40,10 @@ async function main() { if (args.help) { console.log( - './scripts/code-web.sh|bat [options]\n' + - ' --playground Include the vscode-web-playground extension (added by default if no folderPath is provided)\n' + './scripts/code-web.sh|bat[, folderMountPath[, options]]\n' + + ' Start with an empty workspace and no folder opened in explorer\n' + + ' folderMountPath Open local folder (eg: use `.` to open current directory)\n' + + ' --playground Include the vscode-web-playground extension\n' ); startServer(['--help']); return; @@ -59,7 +60,9 @@ async function main() { if (args['port'] === undefined) { serverArgs.push('--port', PORT); } - if (args['playground'] === true || (args['_'].length === 0 && !args['--folder-uri'])) { + + // only use `./scripts/code-web.sh --playground` to add vscode-web-playground extension by default. + if (args['playground'] === true) { serverArgs.push('--extensionPath', WEB_DEV_EXTENSIONS_ROOT); serverArgs.push('--folder-uri', 'memfs:///sample-folder'); await ensureWebDevExtensions(args['verbose']); @@ -75,7 +78,6 @@ async function main() { serverArgs.push(...process.argv.slice(2).filter(v => !v.startsWith('--playground') && v !== '--no-playground')); - startServer(serverArgs); if (openSystemBrowser) { opn(`http://${HOST}:${PORT}/`); @@ -109,6 +111,23 @@ async function directoryExists(path) { } } +/** @return {Promise} */ +async function downloadPlaygroundFile(fileName, httpsLocation, destinationRoot) { + const destination = path.join(destinationRoot, fileName); + await fs.promises.mkdir(path.dirname(destination), { recursive: true }); + const fileStream = fs.createWriteStream(destination); + return (new Promise((resolve, reject) => { + const request = https.get(path.posix.join(httpsLocation, fileName), response => { + response.pipe(fileStream); + fileStream.on('finish', () => { + fileStream.close(); + resolve(); + }); + }); + request.on('error', reject); + })); +} + async function ensureWebDevExtensions(verbose) { // Playground (https://github.com/microsoft/vscode-web-playground) @@ -133,11 +152,11 @@ async function ensureWebDevExtensions(verbose) { if (verbose) { fancyLog(`${ansiColors.magenta('Web Development extensions')}: Downloading vscode-web-playground to ${webDevPlaygroundRoot}`); } - await new Promise((resolve, reject) => { - remote(['package.json', 'dist/extension.js', 'dist/extension.js.map'], { - base: 'https://raw.githubusercontent.com/microsoft/vscode-web-playground/main/' - }).pipe(vfs.dest(webDevPlaygroundRoot)).on('end', resolve).on('error', reject); - }); + const playgroundRepo = `https://raw.githubusercontent.com/microsoft/vscode-web-playground/main/`; + await Promise.all(['package.json', 'dist/extension.js', 'dist/extension.js.map'].map( + fileName => downloadPlaygroundFile(fileName, playgroundRepo, webDevPlaygroundRoot) + )); + } else { if (verbose) { fancyLog(`${ansiColors.magenta('Web Development extensions')}: Using existing vscode-web-playground in ${webDevPlaygroundRoot}`); @@ -145,5 +164,4 @@ async function ensureWebDevExtensions(verbose) { } } - main(); diff --git a/scripts/code.sh b/scripts/code.sh index 08fc867ca1..2b97aa42fa 100755 --- a/scripts/code.sh +++ b/scripts/code.sh @@ -75,6 +75,12 @@ if [ "$IN_WSL" == "true" ] && [ -z "$DISPLAY" ]; then code-wsl "$@" elif [ -f /mnt/wslg/versions.txt ]; then code --disable-gpu "$@" +elif [ -f /.dockerenv ]; then + # Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=1263267 + # Chromium does not release shared memory when streaming scripts + # which might exhaust the available resources in the container environment + # leading to failed script loading. + code --disable-dev-shm-usage "$@" else code "$@" fi diff --git a/scripts/playground-server.ts b/scripts/playground-server.ts new file mode 100644 index 0000000000..4a3c827ca6 --- /dev/null +++ b/scripts/playground-server.ts @@ -0,0 +1,710 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fsPromise from 'fs/promises'; +import path from 'path'; +import * as http from 'http'; +import * as parcelWatcher from '@parcel/watcher'; + +/** + * Launches the server for the monaco editor playground + */ +function main() { + const server = new HttpServer({ host: 'localhost', port: 5001, cors: true }); + server.use('/', redirectToMonacoEditorPlayground()); + + const rootDir = path.join(__dirname, '..'); + const fileServer = new FileServer(rootDir); + server.use(fileServer.handleRequest); + + const moduleIdMapper = new SimpleModuleIdPathMapper(path.join(rootDir, 'out')); + const editorMainBundle = new CachedBundle('vs/editor/editor.main', moduleIdMapper); + fileServer.overrideFileContent(editorMainBundle.entryModulePath, () => editorMainBundle.bundle()); + + const hotReloadJsCode = getHotReloadCode(new URL('/file-changes', server.url)); + const loaderPath = path.join(rootDir, 'out/vs/loader.js'); + fileServer.overrideFileContent(loaderPath, async () => + Buffer.from(new TextEncoder().encode(`${await fsPromise.readFile(loaderPath, 'utf8')}\n${hotReloadJsCode}`)) + ); + + const watcher = DirWatcher.watchRecursively(moduleIdMapper.rootDir); + watcher.onDidChange((path, newContent) => { + editorMainBundle.setModuleContent(path, newContent); + editorMainBundle.bundle(); + console.log(`${new Date().toLocaleTimeString()}, file change: ${path}`); + }); + server.use('/file-changes', handleGetFileChangesRequest(watcher, fileServer)); + + console.log(`Server listening on ${server.url}`); +} +setTimeout(main, 0); + +// #region Http/File Server + +type RequestHandler = (req: http.IncomingMessage, res: http.ServerResponse) => Promise; +type ChainableRequestHandler = (req: http.IncomingMessage, res: http.ServerResponse, next: RequestHandler) => Promise; + +class HttpServer { + private readonly server: http.Server; + public readonly url: URL; + + private handler: ChainableRequestHandler[] = []; + + constructor(options: { host: string; port: number; cors: boolean }) { + this.server = http.createServer(async (req, res) => { + if (options.cors) { + res.setHeader('Access-Control-Allow-Origin', '*'); + } + + let i = 0; + const next = async (req: http.IncomingMessage, res: http.ServerResponse) => { + if (i >= this.handler.length) { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('404 Not Found'); + return; + } + const handler = this.handler[i]; + i++; + await handler(req, res, next); + }; + await next(req, res); + }); + this.server.listen(options.port, options.host); + this.url = new URL(`http://${options.host}:${options.port}`); + } + + use(handler: ChainableRequestHandler); + use(path: string, handler: ChainableRequestHandler); + use(...args: [path: string, handler: ChainableRequestHandler] | [handler: ChainableRequestHandler]) { + const handler = args.length === 1 ? args[0] : (req, res, next) => { + const path = args[0]; + const requestedUrl = new URL(req.url, this.url); + if (requestedUrl.pathname === path) { + return args[1](req, res, next); + } else { + return next(req, res); + } + }; + + this.handler.push(handler); + } +} + +function redirectToMonacoEditorPlayground(): ChainableRequestHandler { + return async (req, res) => { + const url = new URL('https://microsoft.github.io/monaco-editor/playground.html'); + url.searchParams.append('source', `http://${req.headers.host}/out/vs`); + res.writeHead(302, { Location: url.toString() }); + res.end(); + }; +} + +class FileServer { + private readonly overrides = new Map Promise>(); + + constructor(public readonly publicDir: string) { } + + public readonly handleRequest: ChainableRequestHandler = async (req, res, next) => { + const requestedUrl = new URL(req.url!, `http://${req.headers.host}`); + + const pathName = requestedUrl.pathname; + + const filePath = path.join(this.publicDir, pathName); + if (!filePath.startsWith(this.publicDir)) { + res.writeHead(403, { 'Content-Type': 'text/plain' }); + res.end('403 Forbidden'); + return; + } + + try { + const override = this.overrides.get(filePath); + let content: Buffer; + if (override) { + content = await override(); + } else { + content = await fsPromise.readFile(filePath); + } + + const contentType = getContentType(filePath); + res.writeHead(200, { 'Content-Type': contentType }); + res.end(content); + } catch (err) { + if (err.code === 'ENOENT') { + next(req, res); + } else { + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end('500 Internal Server Error'); + } + } + }; + + public filePathToUrlPath(filePath: string): string | undefined { + const relative = path.relative(this.publicDir, filePath); + const isSubPath = !!relative && !relative.startsWith('..') && !path.isAbsolute(relative); + + if (!isSubPath) { + return undefined; + } + const relativePath = relative.replace(/\\/g, '/'); + return `/${relativePath}`; + } + + public overrideFileContent(filePath: string, content: () => Promise): void { + this.overrides.set(filePath, content); + } +} + +function getContentType(filePath: string): string { + const extname = path.extname(filePath); + switch (extname) { + case '.js': + return 'text/javascript'; + case '.css': + return 'text/css'; + case '.json': + return 'application/json'; + case '.png': + return 'image/png'; + case '.jpg': + return 'image/jpg'; + default: + return 'text/plain'; + } +} + +// #endregion + +// #region File Watching + +interface IDisposable { + dispose(): void; +} + +class DirWatcher { + public static watchRecursively(dir: string): DirWatcher { + const listeners: ((path: string, newContent: string) => void)[] = []; + const fileContents = new Map(); + const event = (handler: (path: string, newContent: string) => void) => { + listeners.push(handler); + return { + dispose: () => { + const idx = listeners.indexOf(handler); + if (idx >= 0) { + listeners.splice(idx, 1); + } + } + }; + }; + parcelWatcher.subscribe(dir, async (err, events) => { + for (const e of events) { + if (e.type === 'update') { + const newContent = await fsPromise.readFile(e.path, 'utf8'); + if (fileContents.get(e.path) !== newContent) { + fileContents.set(e.path, newContent); + listeners.forEach(l => l(e.path, newContent)); + } + } + } + }); + return new DirWatcher(event); + } + + constructor(public readonly onDidChange: (handler: (path: string, newContent: string) => void) => IDisposable) { + } +} + +function handleGetFileChangesRequest(watcher: DirWatcher, fileServer: FileServer): ChainableRequestHandler { + return async (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + const d = watcher.onDidChange(fsPath => { + const path = fileServer.filePathToUrlPath(fsPath); + if (path) { + res.write(JSON.stringify({ changedPath: path }) + '\n'); + } + }); + res.on('close', () => d.dispose()); + }; +} + +function getHotReloadCode(fileChangesUrl: URL): string { + const additionalJsCode = ` +function $watchChanges() { + console.log("Connecting to server to watch for changes..."); + fetch(${JSON.stringify(fileChangesUrl)}) + .then(async request => { + const reader = request.body.getReader(); + let buffer = ''; + while (true) { + const { done, value } = await reader.read(); + if (done) { break; } + buffer += new TextDecoder().decode(value); + const lines = buffer.split('\\n'); + buffer = lines.pop(); + for (const line of lines) { + const data = JSON.parse(line); + if (data.changedPath.endsWith('.css')) { + console.log('css changed', data.changedPath); + const styleSheet = [...document.querySelectorAll("link[rel='stylesheet']")].find(l => new URL(l.href, document.location.href).pathname.endsWith(data.changedPath)); + if (styleSheet) { + styleSheet.href = styleSheet.href.replace(/\\?.*/, '') + '?' + Date.now(); + } + } else { + $sendMessageToParent({ kind: "reload" }); + } + } + } + }) + .catch(err => { + console.error(err); + setTimeout($watchChanges, 1000); + }); + +} +$watchChanges(); +`; + return additionalJsCode; +} + +// #endregion + +// #region Bundling + +class CachedBundle { + public readonly entryModulePath = this.mapper.resolveRequestToPath(this.moduleId)!; + + constructor( + private readonly moduleId: string, + private readonly mapper: SimpleModuleIdPathMapper, + ) { + } + + private loader: ModuleLoader | undefined = undefined; + + private bundlePromise: Promise | undefined = undefined; + public async bundle(): Promise { + if (!this.bundlePromise) { + this.bundlePromise = (async () => { + if (!this.loader) { + this.loader = new ModuleLoader(this.mapper); + await this.loader.addModuleAndDependencies(this.entryModulePath); + } + const editorEntryPoint = await this.loader.getModule(this.entryModulePath); + const content = bundleWithDependencies(editorEntryPoint!); + return content; + })(); + } + return this.bundlePromise; + } + + public async setModuleContent(path: string, newContent: string): Promise { + if (!this.loader) { + return; + } + const module = await this.loader!.getModule(path); + if (module) { + if (!this.loader.updateContent(module, newContent)) { + this.loader = undefined; + } + } + this.bundlePromise = undefined; + } +} + +function bundleWithDependencies(module: IModule): Buffer { + const visited = new Set(); + const builder = new SourceMapBuilder(); + + function visit(module: IModule) { + if (visited.has(module)) { + return; + } + visited.add(module); + for (const dep of module.dependencies) { + visit(dep); + } + builder.addSource(module.source); + } + + visit(module); + + const sourceMap = builder.toSourceMap(); + sourceMap.sourceRoot = module.source.sourceMap.sourceRoot; + const sourceMapBase64Str = Buffer.from(JSON.stringify(sourceMap)).toString('base64'); + + builder.addLine(`//# sourceMappingURL=data:application/json;base64,${sourceMapBase64Str}`); + + return builder.toContent(); +} + +class ModuleLoader { + private readonly modules = new Map>(); + + constructor(private readonly mapper: SimpleModuleIdPathMapper) { } + + public getModule(path: string): Promise { + return Promise.resolve(this.modules.get(path)); + } + + public updateContent(module: IModule, newContent: string): boolean { + const parsedModule = parseModule(newContent, module.path, this.mapper); + if (!parsedModule) { + return false; + } + if (!arrayEquals(parsedModule.dependencyRequests, module.dependencyRequests)) { + return false; + } + + module.dependencyRequests = parsedModule.dependencyRequests; + module.source = parsedModule.source; + + return true; + } + + async addModuleAndDependencies(path: string): Promise { + if (this.modules.has(path)) { + return this.modules.get(path)!; + } + + const promise = (async () => { + const content = await fsPromise.readFile(path, { encoding: 'utf-8' }); + + const parsedModule = parseModule(content, path, this.mapper); + if (!parsedModule) { + return undefined; + } + + const dependencies = (await Promise.all(parsedModule.dependencyRequests.map(async r => { + if (r === 'require' || r === 'exports' || r === 'module') { + return null; + } + + const depPath = this.mapper.resolveRequestToPath(r, path); + if (!depPath) { + return null; + } + return await this.addModuleAndDependencies(depPath); + }))).filter((d): d is IModule => !!d); + + const module: IModule = { + id: this.mapper.getModuleId(path)!, + dependencyRequests: parsedModule.dependencyRequests, + dependencies, + path, + source: parsedModule.source, + }; + return module; + })(); + + this.modules.set(path, promise); + return promise; + } +} + +function arrayEquals(a: T[], b: T[]): boolean { + if (a.length !== b.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +const encoder = new TextEncoder(); + +function parseModule(content: string, path: string, mapper: SimpleModuleIdPathMapper): { source: Source; dependencyRequests: string[] } | undefined { + const m = content.match(/define\((\[.*?\])/); + if (!m) { + return undefined; + } + + const dependencyRequests = JSON.parse(m[1].replace(/'/g, '"')) as string[]; + + const sourceMapHeader = '//# sourceMappingURL=data:application/json;base64,'; + const idx = content.indexOf(sourceMapHeader); + + let sourceMap: any = null; + if (idx !== -1) { + const sourceMapJsonStr = Buffer.from(content.substring(idx + sourceMapHeader.length), 'base64').toString('utf-8'); + sourceMap = JSON.parse(sourceMapJsonStr); + content = content.substring(0, idx); + } + + content = content.replace('define([', `define("${mapper.getModuleId(path)}", [`); + + const contentBuffer = Buffer.from(encoder.encode(content)); + const source = new Source(contentBuffer, sourceMap); + + return { dependencyRequests, source }; +} + +class SimpleModuleIdPathMapper { + constructor(public readonly rootDir: string) { } + + public getModuleId(path: string): string | null { + if (!path.startsWith(this.rootDir) || !path.endsWith('.js')) { + return null; + } + const moduleId = path.substring(this.rootDir.length + 1); + + + return moduleId.replace(/\\/g, '/').substring(0, moduleId.length - 3); + } + + public resolveRequestToPath(request: string, requestingModulePath?: string): string | null { + if (request.indexOf('css!') !== -1) { + return null; + } + + if (request.startsWith('.')) { + return path.join(path.dirname(requestingModulePath!), request + '.js'); + } else { + return path.join(this.rootDir, request + '.js'); + } + } +} + +interface IModule { + id: string; + dependencyRequests: string[]; + dependencies: IModule[]; + path: string; + source: Source; +} + +// #endregion + +// #region SourceMapBuilder + +// From https://stackoverflow.com/questions/29905373/how-to-create-sourcemaps-for-concatenated-files with modifications + +class Source { + // Ends with \n + public readonly content: Buffer; + public readonly sourceMap: SourceMap; + public readonly sourceLines: number; + + public readonly sourceMapMappings: Buffer; + + + constructor(content: Buffer, sourceMap: SourceMap | undefined) { + if (!sourceMap) { + sourceMap = SourceMapBuilder.emptySourceMap; + } + + let sourceLines = countNL(content); + if (content.length > 0 && content[content.length - 1] !== 10) { + sourceLines++; + content = Buffer.concat([content, Buffer.from([10])]); + } + + this.content = content; + this.sourceMap = sourceMap; + this.sourceLines = sourceLines; + this.sourceMapMappings = typeof this.sourceMap.mappings === 'string' + ? Buffer.from(encoder.encode(sourceMap.mappings as string)) + : this.sourceMap.mappings; + } +} + +class SourceMapBuilder { + public static emptySourceMap: SourceMap = { version: 3, sources: [], mappings: Buffer.alloc(0) }; + + private readonly outputBuffer = new DynamicBuffer(); + private readonly sources: string[] = []; + private readonly mappings = new DynamicBuffer(); + private lastSourceIndex = 0; + private lastSourceLine = 0; + private lastSourceCol = 0; + + addLine(text: string) { + this.outputBuffer.addString(text); + this.outputBuffer.addByte(10); + this.mappings.addByte(59); // ; + } + + addSource(source: Source) { + const sourceMap = source.sourceMap; + this.outputBuffer.addBuffer(source.content); + + const sourceRemap: number[] = []; + for (const v of sourceMap.sources) { + let pos = this.sources.indexOf(v); + if (pos < 0) { + pos = this.sources.length; + this.sources.push(v); + } + sourceRemap.push(pos); + } + let lastOutputCol = 0; + + const inputMappings = source.sourceMapMappings; + let outputLine = 0; + let ip = 0; + let inOutputCol = 0; + let inSourceIndex = 0; + let inSourceLine = 0; + let inSourceCol = 0; + let shift = 0; + let value = 0; + let valpos = 0; + const commit = () => { + if (valpos === 0) { return; } + this.mappings.addVLQ(inOutputCol - lastOutputCol); + lastOutputCol = inOutputCol; + if (valpos === 1) { + valpos = 0; + return; + } + const outSourceIndex = sourceRemap[inSourceIndex]; + this.mappings.addVLQ(outSourceIndex - this.lastSourceIndex); + this.lastSourceIndex = outSourceIndex; + this.mappings.addVLQ(inSourceLine - this.lastSourceLine); + this.lastSourceLine = inSourceLine; + this.mappings.addVLQ(inSourceCol - this.lastSourceCol); + this.lastSourceCol = inSourceCol; + valpos = 0; + }; + while (ip < inputMappings.length) { + let b = inputMappings[ip++]; + if (b === 59) { // ; + commit(); + this.mappings.addByte(59); + inOutputCol = 0; + lastOutputCol = 0; + outputLine++; + } else if (b === 44) { // , + commit(); + this.mappings.addByte(44); + } else { + b = charToInteger[b]; + if (b === 255) { throw new Error('Invalid sourceMap'); } + value += (b & 31) << shift; + if (b & 32) { + shift += 5; + } else { + const shouldNegate = value & 1; + value >>= 1; + if (shouldNegate) { value = -value; } + switch (valpos) { + case 0: inOutputCol += value; break; + case 1: inSourceIndex += value; break; + case 2: inSourceLine += value; break; + case 3: inSourceCol += value; break; + } + valpos++; + value = shift = 0; + } + } + } + commit(); + while (outputLine < source.sourceLines) { + this.mappings.addByte(59); + outputLine++; + } + } + + toContent(): Buffer { + return this.outputBuffer.toBuffer(); + } + + toSourceMap(sourceRoot?: string): SourceMap { + return { version: 3, sourceRoot, sources: this.sources, mappings: this.mappings.toBuffer().toString() }; + } +} + +export interface SourceMap { + version: number; // always 3 + file?: string; + sourceRoot?: string; + sources: string[]; + sourcesContent?: string[]; + names?: string[]; + mappings: string | Buffer; +} + +const charToInteger = Buffer.alloc(256); +const integerToChar = Buffer.alloc(64); + +charToInteger.fill(255); + +'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.split('').forEach((char, i) => { + charToInteger[char.charCodeAt(0)] = i; + integerToChar[i] = char.charCodeAt(0); +}); + +class DynamicBuffer { + private buffer: Buffer; + private size: number; + + constructor() { + this.buffer = Buffer.alloc(512); + this.size = 0; + } + + ensureCapacity(capacity: number) { + if (this.buffer.length >= capacity) { + return; + } + const oldBuffer = this.buffer; + this.buffer = Buffer.alloc(Math.max(oldBuffer.length * 2, capacity)); + oldBuffer.copy(this.buffer); + } + + addByte(b: number) { + this.ensureCapacity(this.size + 1); + this.buffer[this.size++] = b; + } + + addVLQ(num: number) { + let clamped: number; + + if (num < 0) { + num = (-num << 1) | 1; + } else { + num <<= 1; + } + + do { + clamped = num & 31; + num >>= 5; + + if (num > 0) { + clamped |= 32; + } + + this.addByte(integerToChar[clamped]); + } while (num > 0); + } + + addString(s: string) { + const l = Buffer.byteLength(s); + this.ensureCapacity(this.size + l); + this.buffer.write(s, this.size); + this.size += l; + } + + addBuffer(b: Buffer) { + this.ensureCapacity(this.size + b.length); + b.copy(this.buffer, this.size); + this.size += b.length; + } + + toBuffer(): Buffer { + return this.buffer.slice(0, this.size); + } +} + +function countNL(b: Buffer): number { + let res = 0; + for (let i = 0; i < b.length; i++) { + if (b[i] === 10) { res++; } + } + return res; +} + +// #endregion diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 1a793d4a5b..cedd5bf64f 100755 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -9,13 +9,10 @@ set VSCODELOGSDIR=%~dp0\..\.build\logs\integration-tests :: Figure out which Electron to use for running tests if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( - :: Run out of sources: no need to compile as code.bat takes care of it chcp 65001 set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat set VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE=1 - echo Storing crash reports into '%VSCODECRASHDIR%'. - echo Storing log files into '%VSCODELOGSDIR%'. echo Running integration tests out of sources. ) else ( :: Run from a built: need to compile all test extensions @@ -27,7 +24,6 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( :: compile-extension:vscode-api-tests^ :: compile-extension:vscode-colorize-tests^ :: compile-extension:markdown-language-features^ - :: compile-extension:typescript-language-features^ :: compile-extension:vscode-custom-editor-tests^ :: compile-extension:emmet^ :: compile-extension:css-language-features-server^ @@ -40,8 +36,6 @@ if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( set VSCODE_CLI=1 set ELECTRON_ENABLE_LOGGING=1 - echo Storing crash reports into '%VSCODECRASHDIR%'. - echo Storing log files into '%VSCODELOGSDIR%'. echo Running integration tests with '%INTEGRATION_TEST_ELECTRON_PATH%' as build. ) @@ -67,9 +61,6 @@ set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip :: call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% :: if %errorlevel% neq 0 exit /b %errorlevel% -:: call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\typescript-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\typescript-language-features --extensionTestsPath=%~dp0\..\extensions\typescript-language-features\out\test\unit %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% -:: if %errorlevel% neq 0 exit /b %errorlevel% - :: call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\markdown-language-features\test-workspace --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% :: if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 4465737f64..96bda9c8b0 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -22,11 +22,8 @@ cd $ROOT # Figure out which Electron to use for running tests if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ] then - # Run out of sources: no need to compile as code.sh takes care of it INTEGRATION_TEST_ELECTRON_PATH="./scripts/code.sh" - echo "Storing crash reports into '$VSCODECRASHDIR'." - echo "Storing log files into '$VSCODELOGSDIR'." echo "Running integration tests out of sources." else # Run from a built: need to compile all test extensions @@ -39,7 +36,6 @@ else # compile-extension:vscode-colorize-tests \ # compile-extension:vscode-custom-editor-tests \ # compile-extension:markdown-language-features \ - # compile-extension:typescript-language-features \ # compile-extension:emmet \ # compile-extension:css-language-features-server \ # compile-extension:html-language-features-server \ @@ -53,11 +49,12 @@ else export ELECTRON_ENABLE_STACK_DUMPING=1 export ELECTRON_ENABLE_LOGGING=1 - echo "Storing crash reports into '$VSCODECRASHDIR'." - echo "Storing log files into '$VSCODELOGSDIR'." echo "Running integration tests with '$INTEGRATION_TEST_ELECTRON_PATH' as build." fi +echo "Storing crash reports into '$VSCODECRASHDIR'." +echo "Storing log files into '$VSCODELOGSDIR'." + if [ -z "$INTEGRATION_TEST_APP_NAME" ]; then after_suite() { true; } else diff --git a/scripts/test-remote-integration.bat b/scripts/test-remote-integration.bat index a7d0719205..e0fda5d1c4 100644 --- a/scripts/test-remote-integration.bat +++ b/scripts/test-remote-integration.bat @@ -19,7 +19,7 @@ IF "%VSCODEUSERDATADIR%" == "" ( set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5% ) -set REMOTE_VSCODE=%AUTHORITY%%EXT_PATH% +set REMOTE_EXT_PATH=%AUTHORITY%%EXT_PATH% set VSCODECRASHDIR=%~dp0\..\.build\crashes set VSCODELOGSDIR=%~dp0\..\.build\logs\integration-tests-remote set TESTRESOLVER_DATA_FOLDER=%TMP%\testresolverdatafolder-%RANDOM%-%TIME:~6,5% @@ -32,45 +32,80 @@ if "%VSCODE_REMOTE_SERVER_PATH%"=="" ( echo Using '%VSCODE_REMOTE_SERVER_PATH%' as server path ) -set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --disable-keytar --disable-inspect --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% - :: Figure out which Electron to use for running tests if "%INTEGRATION_TEST_ELECTRON_PATH%"=="" ( - echo Storing crash reports into '%VSCODECRASHDIR%' - echo Storing log files into '%VSCODELOGSDIR%' + chcp 65001 + set INTEGRATION_TEST_ELECTRON_PATH=.\scripts\code.bat + set API_TESTS_EXTRA_ARGS_BUILT= - :: Tests in the extension host running from sources - call .\scripts\code.bat --folder-uri=%REMOTE_VSCODE%/vscode-api-tests/testWorkspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/singlefolder-tests %API_TESTS_EXTRA_ARGS% - if %errorlevel% neq 0 exit /b %errorlevel% - - call .\scripts\code.bat --file-uri=%REMOTE_VSCODE%/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/workspace-tests %API_TESTS_EXTRA_ARGS% - if %errorlevel% neq 0 exit /b %errorlevel% + echo Running integration tests out of sources. ) else ( - echo Storing crash reports into '%VSCODECRASHDIR%' - echo Storing log files into '%VSCODELOGSDIR%' - echo Using %INTEGRATION_TEST_ELECTRON_PATH% as Electron path - - :: Run from a built: need to compile all test extensions - :: because we run extension tests from their source folders - :: and the build bundles extensions into .build webpacked - :: call yarn gulp compile-extension:vscode-api-tests^ - :: compile-extension:microsoft-authentication^ - :: compile-extension:github-authentication^ - :: compile-extension:vscode-test-resolver - - :: Configuration for more verbose output set VSCODE_CLI=1 set ELECTRON_ENABLE_LOGGING=1 - set ELECTRON_ENABLE_STACK_DUMPING=1 - :: Tests in the extension host running from built version (both client and server) - call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_VSCODE%/vscode-api-tests/testWorkspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/singlefolder-tests %API_TESTS_EXTRA_ARGS% --extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests - if %errorlevel% neq 0 exit /b %errorlevel% + :: Extra arguments only when running against a built version + set API_TESTS_EXTRA_ARGS_BUILT=--extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests - call "%INTEGRATION_TEST_ELECTRON_PATH%" --file-uri=%REMOTE_VSCODE%/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=%REMOTE_VSCODE%/vscode-api-tests --extensionTestsPath=%REMOTE_VSCODE%/vscode-api-tests/out/workspace-tests %API_TESTS_EXTRA_ARGS% --extensions-dir=%EXT_PATH% --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests - if %errorlevel% neq 0 exit /b %errorlevel% + echo Using %INTEGRATION_TEST_ELECTRON_PATH% as Electron path ) +echo Storing crash reports into '%VSCODECRASHDIR%' +echo Storing log files into '%VSCODELOGSDIR%' + + +:: Tests in the extension host + +set API_TESTS_EXTRA_ARGS=--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=%VSCODECRASHDIR% --logsPath=%VSCODELOGSDIR% --no-cached-data --disable-updates --disable-keytar --disable-inspect --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% + +echo. +echo ### API tests (folder) +call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_EXT_PATH%/vscode-api-tests/testWorkspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/vscode-api-tests --extensionTestsPath=%REMOTE_EXT_PATH%/vscode-api-tests/out/singlefolder-tests %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### API tests (workspace) +call "%INTEGRATION_TEST_ELECTRON_PATH%" --file-uri=%REMOTE_EXT_PATH%/vscode-api-tests/testworkspace.code-workspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/vscode-api-tests --extensionTestsPath=%REMOTE_EXT_PATH%/vscode-api-tests/out/workspace-tests %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### TypeScript tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_EXT_PATH%/typescript-language-features/test-workspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/typescript-language-features --extensionTestsPath=%REMOTE_EXT_PATH%/typescript-language-features\out\test\unit %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Markdown tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_EXT_PATH%/markdown-language-features/test-workspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/markdown-language-features --extensionTestsPath=%REMOTE_EXT_PATH%/markdown-language-features/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Emmet tests +call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%REMOTE_EXT_PATH%/emmet/test-workspace --extensionDevelopmentPath=%REMOTE_EXT_PATH%/emmet --extensionTestsPath=%REMOTE_EXT_PATH%/emmet/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Git tests +for /f "delims=" %%i in ('node -p "require('fs').realpathSync.native(require('os').tmpdir())"') do set TEMPDIR=%%i +set GITWORKSPACE=%TEMPDIR%\git-%RANDOM% +mkdir %GITWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%AUTHORITY%%GITWORKSPACE% --extensionDevelopmentPath=%REMOTE_EXT_PATH%/git --extensionTestsPath=%REMOTE_EXT_PATH%/git/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Ipynb tests +set IPYNBWORKSPACE=%TEMPDIR%\ipynb-%RANDOM% +mkdir %IPYNBWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%AUTHORITY%%IPYNBWORKSPACE% --extensionDevelopmentPath=%REMOTE_EXT_PATH%/ipynb --extensionTestsPath=%REMOTE_EXT_PATH%/ipynb/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT% +if %errorlevel% neq 0 exit /b %errorlevel% + +echo. +echo ### Configuration editing tests +set CFWORKSPACE=%TEMPDIR%\cf-%RANDOM% +mkdir %CFWORKSPACE% +call "%INTEGRATION_TEST_ELECTRON_PATH%" --folder-uri=%AUTHORITY%/%CFWORKSPACE% --extensionDevelopmentPath=%REMOTE_EXT_PATH%/configuration-editing --extensionTestsPath=%REMOTE_EXT_PATH%/configuration-editing/out/test %API_TESTS_EXTRA_ARGS% %API_TESTS_EXTRA_ARGS_BUILT% +if %errorlevel% neq 0 exit /b %errorlevel% + +:: Cleanup + IF "%3" == "" ( rmdir /s /q %VSCODEUSERDATADIR% ) diff --git a/scripts/test-remote-integration.sh b/scripts/test-remote-integration.sh index 6d883f5321..8163e79664 100755 --- a/scripts/test-remote-integration.sh +++ b/scripts/test-remote-integration.sh @@ -34,40 +34,19 @@ export REMOTE_VSCODE=$AUTHORITY$EXT_PATH # Figure out which Electron to use for running tests if [ -z "$INTEGRATION_TEST_ELECTRON_PATH" ] then - # Run out of sources: no need to compile as code.sh takes care of it INTEGRATION_TEST_ELECTRON_PATH="./scripts/code.sh" # No extra arguments when running out of sources EXTRA_INTEGRATION_TEST_ARGUMENTS="" - echo "Storing crash reports into '$VSCODECRASHDIR'." - echo "Storing log files into '$VSCODELOGSDIR'." echo "Running remote integration tests out of sources." else - # Run from a built: need to compile all test extensions - # because we run extension tests from their source folders - # and the build bundles extensions into .build webpacked - # yarn gulp compile-extension:vscode-api-tests \ - # compile-extension:vscode-test-resolver \ - # compile-extension:markdown-language-features \ - # compile-extension:typescript-language-features \ - # compile-extension:emmet \ - # compile-extension:git \ - # compile-extension:ipynb \ - # compile-extension:configuration-editing \ - # compile-extension:microsoft-authentication \ - # compile-extension:github-authentication \ - # compile-extension-media - - # Configuration for more verbose output export VSCODE_CLI=1 export ELECTRON_ENABLE_LOGGING=1 # Running from a build, we need to enable the vscode-test-resolver extension EXTRA_INTEGRATION_TEST_ARGUMENTS="--extensions-dir=$EXT_PATH --enable-proposed-api=vscode.vscode-test-resolver --enable-proposed-api=vscode.vscode-api-tests" - echo "Storing crash reports into '$VSCODECRASHDIR'." - echo "Storing log files into '$VSCODELOGSDIR'." echo "Running remote integration tests with $INTEGRATION_TEST_ELECTRON_PATH as build." fi @@ -91,6 +70,12 @@ fi API_TESTS_EXTRA_ARGS="--disable-telemetry --skip-welcome --skip-release-notes --crash-reporter-directory=$VSCODECRASHDIR --logsPath=$VSCODELOGSDIR --no-cached-data --disable-updates --disable-keytar --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" +echo "Storing crash reports into '$VSCODECRASHDIR'." +echo "Storing log files into '$VSCODELOGSDIR'." + + +# Tests in the extension host + echo echo "### API tests (folder)" echo diff --git a/scripts/test-web-integration.bat b/scripts/test-web-integration.bat index 7be27c00b3..700dd0a045 100644 --- a/scripts/test-web-integration.bat +++ b/scripts/test-web-integration.bat @@ -18,20 +18,11 @@ IF "%~1" == "" ( set REMOTE_VSCODE=%AUTHORITY%%EXT_PATH% if "%VSCODE_REMOTE_SERVER_PATH%"=="" ( + chcp 65001 + echo Using remote server out of sources for integration web tests ) else ( echo Using '%VSCODE_REMOTE_SERVER_PATH%' as server path for web integration tests - - :: Run from a built: need to compile all test extensions - :: because we run extension tests from their source folders - :: and the build bundles extensions into .build webpacked - :: call yarn gulp compile-extension:vscode-api-tests^ - :: compile-extension:markdown-language-features^ - :: compile-extension:typescript-language-features^ - :: compile-extension:emmet^ - :: compile-extension:configuration-editing^ - :: compile-extension:git^ - :: compile-extension-media ) if not exist ".\test\integration\browser\out\index.js" ( @@ -39,6 +30,9 @@ if not exist ".\test\integration\browser\out\index.js" ( call yarn playwright-install ) + +:: Tests in the extension host + echo. echo ### API tests (folder) call node .\test\integration\browser\out\index.js --workspacePath=.\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=.\extensions\vscode-api-tests --extensionTestsPath=.\extensions\vscode-api-tests\out\singlefolder-tests %* @@ -72,9 +66,20 @@ mkdir %GITWORKSPACE% call node .\test\integration\browser\out\index.js --workspacePath=%GITWORKSPACE% --extensionDevelopmentPath=.\extensions\git --extensionTestsPath=.\extensions\git\out\test %* if %errorlevel% neq 0 exit /b %errorlevel% +echo. +echo ### Ipynb tests +set IPYNBWORKSPACE=%TEMPDIR%\ipynb-%RANDOM% +mkdir %IPYNBWORKSPACE% +call node .\test\integration\browser\out\index.js --workspacePath=%IPYNBWORKSPACE% --extensionDevelopmentPath=.\extensions\ipynb --extensionTestsPath=.\extensions\ipynb\out\test %* +if %errorlevel% neq 0 exit /b %errorlevel% + echo. echo ### Configuration editing tests set CFWORKSPACE=%TEMPDIR%\git-%RANDOM% mkdir %CFWORKSPACE% call node .\test\integration\browser\out\index.js --workspacePath=%CFWORKSPACE% --extensionDevelopmentPath=.\extensions\configuration-editing --extensionTestsPath=.\extensions\configuration-editing\out\test %* if %errorlevel% neq 0 exit /b %errorlevel% + +popd + +endlocal diff --git a/scripts/test-web-integration.sh b/scripts/test-web-integration.sh index 95278eec0e..0648a7f8cd 100755 --- a/scripts/test-web-integration.sh +++ b/scripts/test-web-integration.sh @@ -15,18 +15,6 @@ then echo "Using remote server out of sources for integration web tests" else echo "Using $VSCODE_REMOTE_SERVER_PATH as server path for web integration tests" - - # Run from a built: need to compile all test extensions - # because we run extension tests from their source folders - # and the build bundles extensions into .build webpacked - # yarn gulp compile-extension:vscode-api-tests \ - # compile-extension:markdown-language-features \ - # compile-extension:typescript-language-features \ - # compile-extension:emmet \ - # compile-extension:git \ - # compile-extension:ipynb \ - # compile-extension:configuration-editing \ - # compile-extension-media fi if [ ! -e 'test/integration/browser/out/index.js' ];then @@ -34,6 +22,7 @@ if [ ! -e 'test/integration/browser/out/index.js' ];then yarn playwright-install fi + # Tests in the extension host echo diff --git a/scripts/update-xterm.js b/scripts/update-xterm.js index 3fb6080745..39702d8f66 100644 --- a/scripts/update-xterm.js +++ b/scripts/update-xterm.js @@ -8,6 +8,8 @@ const path = require('path'); const moduleNames = [ 'xterm', + 'xterm-addon-canvas', + 'xterm-addon-image', 'xterm-addon-search', 'xterm-addon-unicode11', 'xterm-addon-webgl' @@ -30,7 +32,17 @@ function getLatestModuleVersion(moduleName) { if (err) { reject(err); } - const versions = JSON.parse(stdout); + let versions = JSON.parse(stdout); + // HACK: Some bad versions were published as v5 which cannot be unpublished, ignore these + if (moduleName === 'xterm-addon-canvas') { + versions = versions.filter(e => ![ + '0.12.0', + '5.0.0-beta.1', + '5.0.0-beta.2', + '5.0.0-beta.3', + '5.0.0-beta.4', + ].includes(e)); + } resolve(versions[versions.length - 1]); }); }); diff --git a/src/bootstrap-amd.js b/src/bootstrap-amd.js index 0be935a026..0c1b51e53d 100644 --- a/src/bootstrap-amd.js +++ b/src/bootstrap-amd.js @@ -6,6 +6,28 @@ //@ts-check 'use strict'; +// Store the node.js require function in a variable +// before loading our AMD loader to avoid issues +// when this file is bundled with other files. +const nodeRequire = require; + +// VSCODE_GLOBALS: node_modules +globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => nodeRequire(String(mod)) }); + +// VSCODE_GLOBALS: package/product.json +/** @type Record */ +globalThis._VSCODE_PRODUCT_JSON = require('../product.json'); +if (process.env['VSCODE_DEV']) { + // Patch product overrides when running out of sources + try { + // @ts-ignore + const overrides = require('../product.overrides.json'); + globalThis._VSCODE_PRODUCT_JSON = Object.assign(globalThis._VSCODE_PRODUCT_JSON, overrides); + } catch (error) { /* ignore */ } +} +globalThis._VSCODE_PACKAGE_JSON = require('../package.json'); + +// @ts-ignore const loader = require('./vs/loader'); const bootstrap = require('./bootstrap'); const performance = require('./vs/base/common/performance'); @@ -17,8 +39,7 @@ const nlsConfig = bootstrap.setupNLS(); loader.config({ baseUrl: bootstrap.fileUriFromPath(__dirname, { isWindows: process.platform === 'win32' }), catchError: true, - nodeRequire: require, - nodeMain: __filename, + nodeRequire, 'vs/nls': nlsConfig, amdModulesPattern: /^(vs|sql)\//, recordStats: true @@ -26,18 +47,23 @@ loader.config({ // Running in Electron if (process.env['ELECTRON_RUN_AS_NODE'] || process.versions['electron']) { - loader.define('fs', ['original-fs'], function (originalFS) { + loader.define('fs', ['original-fs'], function (/** @type {import('fs')} */originalFS) { return originalFS; // replace the patched electron fs with the original node fs for all AMD code }); } // Pseudo NLS support if (nlsConfig && nlsConfig.pseudo) { - loader(['vs/nls'], function (nlsPlugin) { - nlsPlugin.setPseudoTranslation(nlsConfig.pseudo); + loader(['vs/nls'], function (/** @type {import('vs/nls')} */nlsPlugin) { + nlsPlugin.setPseudoTranslation(!!nlsConfig.pseudo); }); } +/** + * @param {string=} entrypoint + * @param {(value: any) => void=} onLoad + * @param {(err: Error) => void=} onError + */ exports.load = function (entrypoint, onLoad, onError) { if (!entrypoint) { return; @@ -56,6 +82,6 @@ exports.load = function (entrypoint, onLoad, onError) { onLoad = onLoad || function () { }; onError = onError || function (err) { console.error(err); }; - performance.mark(`code/fork/willLoadCode`); + performance.mark('code/fork/willLoadCode'); loader([entrypoint], onLoad, onError); }; diff --git a/src/bootstrap-fork.js b/src/bootstrap-fork.js index 2844cde913..eab8ed5607 100644 --- a/src/bootstrap-fork.js +++ b/src/bootstrap-fork.js @@ -12,6 +12,9 @@ performance.mark('code/fork/start'); const bootstrap = require('./bootstrap'); const bootstrapNode = require('./bootstrap-node'); +// Crash reporter +configureCrashReporter(); + // Remove global paths from the node module lookup bootstrapNode.removeGlobalNodeModuleLookupPaths(); @@ -37,11 +40,6 @@ if (process.env['VSCODE_PARENT_PID']) { terminateWhenParentTerminates(); } -// Listen for message ports -if (process.env['VSCODE_WILL_SEND_MESSAGE_PORT']) { - listenForMessagePort(); -} - // Load AMD entry point require('./bootstrap-amd').load(process.env['VSCODE_AMD_ENTRYPOINT']); @@ -237,17 +235,17 @@ function terminateWhenParentTerminates() { } } -function listenForMessagePort() { - // We need to listen for the 'port' event as soon as possible, - // otherwise we might miss the event. But we should also be - // prepared in case the event arrives late. - process.on('port', (e) => { - if (global.vscodePortsCallback) { - global.vscodePortsCallback(e.ports); - } else { - global.vscodePorts = e.ports; +function configureCrashReporter() { + const crashReporterProcessType = process.env['VSCODE_CRASH_REPORTER_PROCESS_TYPE']; + if (crashReporterProcessType) { + try { + if (process['crashReporter'] && typeof process['crashReporter'].addExtraParameter === 'function' /* Electron only */) { + process['crashReporter'].addExtraParameter('processType', crashReporterProcessType); + } + } catch (error) { + console.error(error); } - }); + } } //#endregion diff --git a/src/bootstrap-node.js b/src/bootstrap-node.js index 7658d95f0a..1a5071dcaa 100644 --- a/src/bootstrap-node.js +++ b/src/bootstrap-node.js @@ -78,11 +78,14 @@ exports.removeGlobalNodeModuleLookupPaths = function () { // @ts-ignore Module._resolveLookupPaths = function (moduleName, parent) { const paths = originalResolveLookupPaths(moduleName, parent); - let commonSuffixLength = 0; - while (commonSuffixLength < paths.length && paths[paths.length - 1 - commonSuffixLength] === globalPaths[globalPaths.length - 1 - commonSuffixLength]) { - commonSuffixLength++; + if (Array.isArray(paths)) { + let commonSuffixLength = 0; + while (commonSuffixLength < paths.length && paths[paths.length - 1 - commonSuffixLength] === globalPaths[globalPaths.length - 1 - commonSuffixLength]) { + commonSuffixLength++; + } + return paths.slice(0, paths.length - commonSuffixLength); } - return paths.slice(0, paths.length - commonSuffixLength); + return paths; }; }; diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 812110d05b..90a0974c9a 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -43,15 +43,6 @@ * }} [options] */ async function load(modulePaths, resultCallback, options) { - const isDev = !!safeProcess.env['VSCODE_DEV']; - - // Error handler (node.js enabled renderers only) - let showDevtoolsOnError = isDev; - if (!safeProcess.sandboxed) { - safeProcess.on('uncaughtException', function (/** @type {string | Error} */ error) { - onUnexpectedError(error, showDevtoolsOnError); - }); - } // Await window configuration from preload const timeout = setTimeout(() => { console.error(`[resolve window config] Could not resolve window configuration within 10 seconds, but will continue to wait...`); }, 10000); @@ -68,17 +59,15 @@ // Developer settings const { - forceDisableShowDevtoolsOnError, forceEnableDeveloperKeybindings, disallowReloadKeybinding, removeDeveloperKeybindingsAfterLoad } = typeof options?.configureDeveloperSettings === 'function' ? options.configureDeveloperSettings(configuration) : { - forceDisableShowDevtoolsOnError: false, forceEnableDeveloperKeybindings: false, disallowReloadKeybinding: false, removeDeveloperKeybindingsAfterLoad: false }; - showDevtoolsOnError = isDev && !forceDisableShowDevtoolsOnError; + const isDev = !!safeProcess.env['VSCODE_DEV']; const enableDeveloperKeybindings = isDev || forceEnableDeveloperKeybindings; let developerDeveloperKeybindingsDisposable; if (enableDeveloperKeybindings) { @@ -102,16 +91,11 @@ window.document.documentElement.setAttribute('lang', locale); - // Define `fs` as `original-fs` to disable ASAR support - // in fs-operations (node.js enabled renderers only) - if (!safeProcess.sandboxed) { - require.define('fs', [], function () { - return require.__$__nodeRequire('original-fs'); - }); - } - window['MonacoEnvironment'] = {}; + // VSCODE_GLOBALS: node_modules + globalThis._VSCODE_NODE_MODULES = new Proxy(Object.create(null), { get: (_target, mod) => (require.__$__nodeRequire ?? require)(String(mod)) }); + const loaderConfig = { baseUrl: `${bootstrapLib.fileUriFromPath(configuration.appRoot, { isWindows: safeProcess.platform === 'win32', scheme: 'vscode-file', fallbackAuthority: 'vscode-app' })}/out`, 'vs/nls': nlsConfig, @@ -135,8 +119,12 @@ loaderConfig.paths = { 'vscode-textmate': `${baseNodeModulesPath}/vscode-textmate/release/main.js`, 'vscode-oniguruma': `${baseNodeModulesPath}/vscode-oniguruma/release/main.js`, + 'vsda': `${baseNodeModulesPath}/vsda/index.js`, 'xterm': `${baseNodeModulesPath}/xterm/lib/xterm.js`, + 'xterm-addon-canvas': `${baseNodeModulesPath}/xterm-addon-canvas/lib/xterm-addon-canvas.js`, + 'xterm-addon-image': `${baseNodeModulesPath}/xterm-addon-image/lib/xterm-addon-image.js`, 'xterm-addon-search': `${baseNodeModulesPath}/xterm-addon-search/lib/xterm-addon-search.js`, + 'xterm-addon-serialize': `${baseNodeModulesPath}/xterm-addon-serialize/lib/xterm-addon-serialize.js`, 'xterm-addon-unicode11': `${baseNodeModulesPath}/xterm-addon-unicode11/lib/xterm-addon-unicode11.js`, 'xterm-addon-webgl': `${baseNodeModulesPath}/xterm-addon-webgl/lib/xterm-addon-webgl.js`, 'iconv-lite-umd': `${baseNodeModulesPath}/iconv-lite-umd/lib/iconv-lite-umd.js`, @@ -182,11 +170,11 @@ } // Actually require the main module as specified - require(modulePaths, async result => { + require(modulePaths, async firstModule => { try { // Callback only after process environment is resolved - const callbackResult = resultCallback(result, configuration); + const callbackResult = resultCallback(firstModule, configuration); if (callbackResult instanceof Promise) { await callbackResult; diff --git a/src/bootstrap.js b/src/bootstrap.js index 31ff7e5b8d..e8f38108c6 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -16,6 +16,7 @@ // Browser else { + // @ts-ignore globalThis.MonacoBootstrap = factory(); } }(this, function () { @@ -31,8 +32,16 @@ if (typeof process !== 'undefined' && !process.env['VSCODE_HANDLES_SIGPIPE']) { // Workaround for Electron not installing a handler to ignore SIGPIPE // (https://github.com/electron/electron/issues/13254) + let didLogAboutSIGPIPE = false; process.on('SIGPIPE', () => { - console.error(new Error('Unexpected SIGPIPE')); + // See https://github.com/microsoft/vscode-remote-release/issues/6543 + // We would normally install a SIGPIPE listener in bootstrap.js + // But in certain situations, the console itself can be in a broken pipe state + // so logging SIGPIPE to the console will cause an infinite async loop + if (!didLogAboutSIGPIPE) { + didLogAboutSIGPIPE = true; + console.error(new Error(`Unexpected SIGPIPE`)); + } }); } @@ -51,7 +60,6 @@ } const NODE_MODULES_PATH = appRoot ? path.join(appRoot, 'node_modules') : path.join(__dirname, '../node_modules'); - // Windows only: // use both lowercase and uppercase drive letter // as a way to ensure we do the right check on @@ -155,6 +163,7 @@ // Get the nls configuration as early as possible. const process = safeProcess(); + /** @type {{ availableLanguages: {}; loadBundle?: (bundle: string, language: string, cb: (err: Error | undefined, result: string | undefined) => void) => void; _resolvedLanguagePackCoreLocation?: string; _corruptedFile?: string }} */ let nlsConfig = { availableLanguages: {} }; if (process && process.env['VSCODE_NLS_CONFIG']) { try { @@ -167,6 +176,11 @@ if (nlsConfig._resolvedLanguagePackCoreLocation) { const bundles = Object.create(null); + /** + * @param {string} bundle + * @param {string} language + * @param {(err: Error | undefined, result: string | undefined) => void} cb + */ nlsConfig.loadBundle = function (bundle, language, cb) { const result = bundles[bundle]; if (result) { @@ -175,6 +189,7 @@ return; } + // @ts-ignore safeReadNlsFile(nlsConfig._resolvedLanguagePackCoreLocation, `${bundle.replace(/\//g, '!')}.nls.json`).then(function (content) { const json = JSON.parse(content); bundles[bundle] = json; @@ -201,6 +216,7 @@ function safeSandboxGlobals() { const globals = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); + // @ts-ignore return globals.vscode; } @@ -269,28 +285,8 @@ //#endregion - - //#region ApplicationInsights - - // Prevents appinsights from monkey patching modules. - // This should be called before importing the applicationinsights module - function avoidMonkeyPatchFromAppInsights() { - if (typeof process === 'undefined') { - console.warn('avoidMonkeyPatchFromAppInsights() is only available in node.js environments'); - return; - } - - // @ts-ignore - process.env['APPLICATION_INSIGHTS_NO_DIAGNOSTIC_CHANNEL'] = true; // Skip monkey patching of 3rd party modules by appinsights - global['diagnosticsSource'] = {}; // Prevents diagnostic channel (which patches "require") from initializing entirely - } - - //#endregion - - return { enableASARSupport, - avoidMonkeyPatchFromAppInsights, setupNLS, fileUriFromPath }; diff --git a/src/buildfile.js b/src/buildfile.js index e444470d78..91d8a49af5 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -35,7 +35,6 @@ exports.base = [ exclude: ['vs/nls'], prepend: [ { path: 'vs/loader.js' }, - { path: 'vs/nls.js', amdModuleId: 'vs/nls' }, { path: 'vs/base/worker/workerMain.js' } ], dest: 'vs/base/worker/workerMain.js' @@ -48,12 +47,13 @@ exports.base = [ exports.workerExtensionHost = [createEditorWorkerModuleDescription('vs/workbench/api/worker/extensionHostWorker')]; exports.workerNotebook = [createEditorWorkerModuleDescription('vs/workbench/contrib/notebook/common/services/notebookSimpleWorker')]; -exports.workerSharedProcess = [createEditorWorkerModuleDescription('vs/platform/sharedProcess/electron-browser/sharedProcessWorkerMain')]; exports.workerLanguageDetection = [createEditorWorkerModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker')]; exports.workerLocalFileSearch = [createEditorWorkerModuleDescription('vs/workbench/services/search/worker/localFileSearch')]; +exports.workerProfileAnalysis = [createEditorWorkerModuleDescription('vs/platform/profiling/electron-sandbox/profileAnalysisWorker')]; exports.workbenchDesktop = [ createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), + createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/worker/textMate.worker'), createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), createModuleDescription('vs/platform/files/node/watcher/watcherMain'), createModuleDescription('vs/platform/terminal/node/ptyHostMain'), @@ -62,6 +62,7 @@ exports.workbenchDesktop = [ exports.workbenchWeb = [ createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), + createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/worker/textMate.worker'), createModuleDescription('vs/code/browser/workbench/workbench', ['vs/workbench/workbench.web.main']) ]; @@ -76,7 +77,7 @@ exports.code = [ createModuleDescription('vs/code/node/cli'), createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), createModuleDescription('vs/code/electron-sandbox/issue/issueReporterMain'), - createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain'), + createModuleDescription('vs/code/node/sharedProcess/sharedProcessMain'), createModuleDescription('vs/code/electron-sandbox/processExplorer/processExplorerMain') ]; diff --git a/src/cli.js b/src/cli.js index b6bcd96a15..fda62590d1 100644 --- a/src/cli.js +++ b/src/cli.js @@ -18,9 +18,6 @@ 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 bootstrapNode.configurePortable(product); diff --git a/src/main.js b/src/main.js index b5647f8ad4..3e2630abe5 100644 --- a/src/main.js +++ b/src/main.js @@ -22,9 +22,10 @@ const bootstrap = require('./bootstrap'); const bootstrapNode = require('./bootstrap-node'); const { getUserDataPath } = require('./vs/platform/environment/node/userDataPath'); const { stripComments } = require('./vs/base/common/stripComments'); +const { getUNCHost, addUNCHostToAllowlist } = require('./vs/base/node/unc'); /** @type {Partial} */ const product = require('../product.json'); -const { app, protocol, crashReporter } = require('electron'); +const { app, protocol, crashReporter, Menu } = require('electron'); // Enable portable support const portable = bootstrapNode.configurePortable(product); @@ -32,8 +33,11 @@ const portable = bootstrapNode.configurePortable(product); // Enable ASAR support bootstrap.enableASARSupport(); -// Set userData path before app 'ready' event +// Enable sandbox globally unless disabled via `--no-sandbox` argument const args = parseCLIArgs(); +// if (args['sandbox']) { // {{SQL CARBON EDIT}} - disable sandbox +// app.enableSandbox(); +// } if (args['nogpu']) { // {{SQL CARBON EDIT}} app.disableHardwareAcceleration(); // {{SQL CARBON EDIT}} @@ -41,7 +45,15 @@ if (args['nogpu']) { // {{SQL CARBON EDIT}} app.commandLine.appendSwitch('disable-gpu'); // {{SQL CARBON EDIT}} } // {{SQL CARBON EDIT}} -const userDataPath = getUserDataPath(args); + +// Set userData path before app 'ready' event +const userDataPath = getUserDataPath(args, product.nameShort ?? 'azuredatastudio-oss-dev'); // {{SQL CARBON EDIT}} - change app name +if (process.platform === 'win32') { + const userDataUNCHost = getUNCHost(userDataPath); + if (userDataUNCHost) { + addUNCHostToAllowlist(userDataUNCHost); // enables to use UNC paths in userDataPath + } +} app.setPath('userData', userDataPath); // Resolve code cache path @@ -50,6 +62,9 @@ const codeCachePath = getCodeCachePath(); // Configure static command line arguments const argvConfig = configureCommandlineSwitchesSync(args); +// Disable default menu (https://github.com/electron/electron/issues/35512) +Menu.setApplicationMenu(null); + // Configure crash reporter perf.mark('code/willStartCrashReporter'); // If a crash-reporter-directory is specified we store the crash reports @@ -60,8 +75,7 @@ perf.mark('code/willStartCrashReporter'); // * --disable-crash-reporter command line parameter is not set // // Disable crash reporting in all other cases. -if (args['crash-reporter-directory'] || - (argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) { +if (args['crash-reporter-directory'] || (argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) { configureCrashReporter(); } perf.mark('code/didStartCrashReporter'); @@ -97,11 +111,33 @@ registerListeners(); */ let nlsConfigurationPromise = undefined; +/** + * @type {String} + **/ +// Use the most preferred OS language for language recommendation. +// The API might return an empty array on Linux, such as when +// the 'C' locale is the user's only configured locale. +// No matter the OS, if the array is empty, default back to 'en'. +const resolved = app.getPreferredSystemLanguages()?.[0] ?? 'en'; +const osLocale = processZhLocale(resolved.toLowerCase()); const metaDataFile = path.join(__dirname, 'nls.metadata.json'); const locale = getUserDefinedLocale(argvConfig); if (locale) { const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); - nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); + nlsConfigurationPromise = getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale, osLocale); +} + +// Pass in the locale to Electron so that the +// Windows Control Overlay is rendered correctly on Windows. +// For now, don't pass in the locale on macOS due to +// https://github.com/microsoft/vscode/issues/167543. +// If the locale is `qps-ploc`, the Microsoft +// Pseudo Language Language Pack is being used. +// In that case, use `en` as the Electron locale. + +if (process.platform === 'win32' || process.platform === 'linux') { + const electronLocale = (!locale || locale === 'qps-ploc') ? 'en' : locale; + app.commandLine.appendSwitch('lang', electronLocale); } // Load our code once ready @@ -175,7 +211,7 @@ function configureCommandlineSwitchesSync(cliArgs) { // Persistently enable proposed api via argv.json: https://github.com/microsoft/vscode/issues/99775 'enable-proposed-api', - // Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'. + // Log level to use. Default is 'info'. Allowed values are 'error', 'warn', 'info', 'debug', 'trace', 'off'. 'log-level' ]; @@ -219,12 +255,20 @@ function configureCommandlineSwitchesSync(cliArgs) { case 'log-level': if (typeof argvValue === 'string') { process.argv.push('--log', argvValue); + } else if (Array.isArray(argvValue)) { + for (const value of argvValue) { + process.argv.push('--log', value); + } } break; } } }); + // Following features are disabled from the runtime: + // `CalculateNativeWinOcclusion` - Disable native window occlusion tracker (https://groups.google.com/a/chromium.org/g/embedder-dev/c/ZF3uHHyWLKw/m/VDN2hDXMAAAJ) + app.commandLine.appendSwitch('disable-features', 'CalculateNativeWinOcclusion'); + // Support JS Flags const jsFlags = getJSFlags(cliArgs); if (jsFlags) { @@ -303,6 +347,7 @@ function getArgvConfigPath() { dataFolderName = `${dataFolderName}-dev`; } + // @ts-ignore return path.join(os.homedir(), dataFolderName, 'argv.json'); } @@ -392,23 +437,14 @@ function configureCrashReporter() { /* {{SQL CARBON EDIT}} Disable crash reporting until we're actually set up to use it const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort; const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft'; - if (process.env['VSCODE_DEV']) { - crashReporter.start({ - companyName: companyName, - productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, - submitURL, - uploadToServer: false, - compress: true - }); - } else { - crashReporter.start({ - companyName: companyName, - productName: productName, - submitURL, - uploadToServer: !crashReporterDirectory, - compress: true - }); - } + const uploadToServer = Boolean(!process.env['VSCODE_DEV'] && submitURL && !crashReporterDirectory); + crashReporter.start({ + companyName, + productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, + submitURL, + uploadToServer, + compress: true + }); */ } @@ -424,11 +460,6 @@ function getJSFlags(cliArgs) { jsFlags.push(cliArgs['js-flags']); } - // Support max-memory flag - if (cliArgs['max-memory'] && !/max_old_space_size=(\d+)/g.exec(cliArgs['js-flags'])) { - jsFlags.push(`--max_old_space_size=${cliArgs['max-memory']}`); - } - return jsFlags.length > 0 ? jsFlags.join(' ') : null; } @@ -443,9 +474,14 @@ function parseCLIArgs() { 'user-data-dir', 'locale', 'js-flags', - 'max-memory', 'crash-reporter-directory' - ] + ], + default: { + 'sandbox': false // {{SQL CARBON EDIT} - set sandbox to false + }, + alias: { + 'no-sandbox': 'sandbox' + } }); } @@ -546,6 +582,30 @@ async function mkdirpIgnoreError(dir) { //#region NLS Support +/** + * @param {string} appLocale + * @returns string + */ +function processZhLocale(appLocale) { + if (appLocale.startsWith('zh')) { + const region = appLocale.split('-')[1]; + // On Windows and macOS, Chinese languages returned by + // app.getPreferredSystemLanguages() start with zh-hans + // for Simplified Chinese or zh-hant for Traditional Chinese, + // so we can easily determine whether to use Simplified or Traditional. + // However, on Linux, Chinese languages returned by that same API + // are of the form zh-XY, where XY is a country code. + // For China (CN), Singapore (SG), and Malaysia (MY) + // country codes, assume they use Simplified Chinese. + // For other cases, assume they use Traditional. + if (['hans', 'cn', 'sg', 'my'].includes(region)) { + return 'zh-cn'; + } + return 'zh-tw'; + } + return appLocale; +} + /** * Resolve the NLS configuration * @@ -556,30 +616,28 @@ async function resolveNlsConfiguration() { // First, we need to test a user defined locale. If it fails we try the app locale. // If that fails we fall back to English. let nlsConfiguration = nlsConfigurationPromise ? await nlsConfigurationPromise : undefined; - if (!nlsConfiguration) { - - // Try to use the app locale. Please note that the app locale is only - // valid after we have received the app ready event. This is why the - // code is here. - let appLocale = app.getLocale(); - if (!appLocale) { - nlsConfiguration = { locale: 'en', availableLanguages: {} }; - } else { - - // See above the comment about the loader and case sensitiveness - appLocale = appLocale.toLowerCase(); - - const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); - nlsConfiguration = await getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale); - if (!nlsConfiguration) { - nlsConfiguration = { locale: appLocale, availableLanguages: {} }; - } - } - } else { - // We received a valid nlsConfig from a user defined locale + if (nlsConfiguration) { + return nlsConfiguration; } - return nlsConfiguration; + // Try to use the app locale. Please note that the app locale is only + // valid after we have received the app ready event. This is why the + // code is here. + + /** + * @type string + */ + let appLocale = app.getLocale(); + if (!appLocale) { + return { locale: 'en', osLocale, availableLanguages: {} }; + } + + // See above the comment about the loader and case sensitiveness + appLocale = processZhLocale(appLocale.toLowerCase()); + + const { getNLSConfiguration } = require('./vs/base/node/languagePacks'); + nlsConfiguration = await getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale, osLocale); + return nlsConfiguration ?? { locale: 'en', osLocale, availableLanguages: {} }; } /** diff --git a/src/server-main.js b/src/server-main.js index 91abf7114c..bf80fea8de 100644 --- a/src/server-main.js +++ b/src/server-main.js @@ -21,7 +21,7 @@ async function start() { // Do a quick parse to determine if a server or the cli needs to be started const parsedArgs = minimist(process.argv.slice(2), { boolean: ['start-server', 'list-extensions', 'print-ip-address', 'help', 'version', 'accept-server-license-terms'], - string: ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'locate-extension', 'socket-path', 'host', 'port', 'pick-port', 'compatibility'], + string: ['install-extension', 'install-builtin-extension', 'uninstall-extension', 'locate-extension', 'socket-path', 'host', 'port', 'compatibility'], alias: { help: 'h', version: 'v' } }); ['host', 'port', 'accept-server-license-terms'].forEach(e => { @@ -45,23 +45,21 @@ async function start() { return; } - if (parsedArgs['compatibility'] === '1.63') { - console.warn(`server.sh is being replaced by 'bin/${product.serverApplicationName}'. Please migrate to the new command and adopt the following new default behaviors:`); - console.warn('* connection token is mandatory unless --without-connection-token is used'); - console.warn('* host defaults to `localhost`'); - } - /** * @typedef { import('./vs/server/node/remoteExtensionHostAgentServer').IServerAPI } IServerAPI */ /** @type {IServerAPI | null} */ - const _remoteExtensionHostAgentServer = null; + let _remoteExtensionHostAgentServer = null; /** @type {Promise | null} */ let _remoteExtensionHostAgentServerPromise = null; /** @returns {Promise} */ const getRemoteExtensionHostAgentServer = () => { if (!_remoteExtensionHostAgentServerPromise) { - _remoteExtensionHostAgentServerPromise = loadCode().then((mod) => mod.createServer(address)); + _remoteExtensionHostAgentServerPromise = loadCode().then(async (mod) => { + const server = await mod.createServer(address); + _remoteExtensionHostAgentServer = server; + return server; + }); } return _remoteExtensionHostAgentServerPromise; }; @@ -119,7 +117,7 @@ async function start() { const nodeListenOptions = ( parsedArgs['socket-path'] ? { path: sanitizeStringArg(parsedArgs['socket-path']) } - : { host, port: await parsePort(host, sanitizeStringArg(parsedArgs['port']), sanitizeStringArg(parsedArgs['pick-port'])) } + : { host, port: await parsePort(host, sanitizeStringArg(parsedArgs['port'])) } ); server.listen(nodeListenOptions, async () => { let output = Array.isArray(product.serverGreeting) && product.serverGreeting.length ? `\n\n${product.serverGreeting.join('\n')}\n\n` : ``; @@ -127,7 +125,7 @@ async function start() { if (typeof nodeListenOptions.port === 'number' && parsedArgs['print-ip-address']) { const ifaces = os.networkInterfaces(); Object.keys(ifaces).forEach(function (ifname) { - ifaces[ifname].forEach(function (iface) { + ifaces[ifname]?.forEach(function (iface) { if (!iface.internal && iface.family === 'IPv4') { output += `IP Address: ${iface.address}\n`; } @@ -171,35 +169,29 @@ function sanitizeStringArg(val) { } /** - * If `--pick-port` and `--port` is specified, connect to that port. + * If `--port` is specified and describes a single port, connect to that port. * - * If not and a port range is specified through `--pick-port` + * If `--port`describes a port range * then find a free port in that range. Throw error if no * free port available in range. * - * If only `--port` is provided then connect to that port. - * * In absence of specified ports, connect to port 8000. * @param {string | undefined} host * @param {string | undefined} strPort - * @param {string | undefined} strPickPort * @returns {Promise} * @throws */ -async function parsePort(host, strPort, strPickPort) { - let specificPort; +async function parsePort(host, strPort) { if (strPort) { let range; if (strPort.match(/^\d+$/)) { - specificPort = parseInt(strPort, 10); - if (specificPort === 0 || !strPickPort) { - return specificPort; - } + return parseInt(strPort, 10); } else if (range = parseRange(strPort)) { const port = await findFreePort(host, range.start, range.end); if (port !== undefined) { return port; } + // Remote-SSH extension relies on this exact port error message, treat as an API console.warn(`--port: Could not find free port in range: ${range.start} - ${range.end} (inclusive).`); process.exit(1); @@ -208,25 +200,6 @@ async function parsePort(host, strPort, strPickPort) { process.exit(1); } } - // pick-port is deprecated and will be removed soon - if (strPickPort) { - const range = parseRange(strPickPort); - if (range) { - if (range.start <= specificPort && specificPort <= range.end) { - return specificPort; - } else { - const port = await findFreePort(host, range.start, range.end); - if (port !== undefined) { - return port; - } - console.log(`--pick-port: Could not find free port in range: ${range.start} - ${range.end}.`); - process.exit(1); - } - } else { - console.log(`--pick-port "${strPickPort}" is not a valid range. Ranges must be in the form 'from-to' with 'from' an integer larger than 0 and not larger than 'end'.`); - process.exit(1); - } - } return 8000; } @@ -256,7 +229,7 @@ function parseRange(strRange) { * @throws */ async function findFreePort(host, start, end) { - const testPort = (port) => { + const testPort = (/** @type {number} */ port) => { return new Promise((resolve) => { const server = http.createServer(); server.listen(port, host, () => { diff --git a/src/sql/azdata.d.ts b/src/sql/azdata.d.ts index c52ab1168e..cf9426ed7d 100644 --- a/src/sql/azdata.d.ts +++ b/src/sql/azdata.d.ts @@ -1580,7 +1580,7 @@ declare module 'azdata' { } export enum FrequencyTypes { - Unknown, + Unknown = 0, OneTime = 1 << 1, Daily = 1 << 2, Weekly = 1 << 3, diff --git a/src/sql/base/browser/ui/button/button.ts b/src/sql/base/browser/ui/button/button.ts index 71522202dd..e933eb84c1 100644 --- a/src/sql/base/browser/ui/button/button.ts +++ b/src/sql/base/browser/ui/button/button.ts @@ -3,17 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Button as vsButton, IButtonOptions, IButtonStyles as vsIButtonStyles } from 'vs/base/browser/ui/button/button'; +import { Button as vsButton, IButtonOptions } from 'vs/base/browser/ui/button/button'; import { Color } from 'vs/base/common/color'; -export interface IButtonStyles extends vsIButtonStyles { -} - export class Button extends vsButton { protected buttonFocusOutline?: Color; constructor(container: HTMLElement, options?: IButtonOptions) { - super(container, options); + super(container, options ?? {}); } public set title(value: string) { diff --git a/src/sql/base/browser/ui/checkbox/checkbox.ts b/src/sql/base/browser/ui/checkbox/checkbox.ts index af177bd9fc..90d7bfd70f 100644 --- a/src/sql/base/browser/ui/checkbox/checkbox.ts +++ b/src/sql/base/browser/ui/checkbox/checkbox.ts @@ -4,13 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/checkbox'; - -import { Color } from 'vs/base/common/color'; import { Event, Emitter } from 'vs/base/common/event'; import { Widget } from 'vs/base/browser/ui/widget'; import { generateUuid } from 'vs/base/common/uuid'; -export interface ICheckboxOptions { +export interface ICheckboxOptions extends Partial { label: string; enabled?: boolean; checked?: boolean; @@ -20,13 +18,12 @@ export interface ICheckboxOptions { } export interface ICheckboxStyles { - disabledCheckboxForeground?: Color; + disabledCheckboxForeground?: string; } export class Checkbox extends Widget { private _el: HTMLInputElement; private _label: HTMLSpanElement; - private disabledCheckboxForeground?: Color; private _onChange = new Emitter(); public readonly onChange: Event = this._onChange.event; @@ -34,7 +31,7 @@ export class Checkbox extends Widget { private _onFocus = new Emitter(); public readonly onFocus: Event = this._onFocus.event; - constructor(container: HTMLElement, opts: ICheckboxOptions) { + constructor(container: HTMLElement, private readonly _options: ICheckboxOptions) { super(); const id = generateUuid(); this._el = document.createElement('input'); @@ -42,12 +39,12 @@ export class Checkbox extends Widget { this._el.style.verticalAlign = 'middle'; this._el.id = id; - if (opts.ariaLabel) { - this.ariaLabel = opts.ariaLabel; + if (_options.ariaLabel) { + this.ariaLabel = _options.ariaLabel; } - if (opts.ariaDescription) { - this._el.setAttribute('aria-description', opts.ariaDescription); + if (_options.ariaDescription) { + this._el.setAttribute('aria-description', _options.ariaDescription); } this.onchange(this._el, e => { @@ -62,12 +59,12 @@ export class Checkbox extends Widget { this._label.style.verticalAlign = 'middle'; this._label.setAttribute('for', id); - this.label = opts.label; - this.enabled = opts.enabled ?? true; - this.checked = opts.checked ?? false; + this.label = _options.label; + this.enabled = _options.enabled ?? true; + this.checked = _options.checked ?? false; - if (opts.onChange) { - this.onChange(opts.onChange); + if (_options.onChange) { + this.onChange(_options.onChange); } container.appendChild(this._el); @@ -84,7 +81,7 @@ export class Checkbox extends Widget { public set enabled(val: boolean) { this._el.disabled = !val; - this.updateStyle(); + this._label.style.color = !this.enabled && this._options.disabledCheckboxForeground ? this._options.disabledCheckboxForeground : 'inherit'; } public get enabled(): boolean { @@ -134,13 +131,4 @@ export class Checkbox extends Widget { public setWidth(value: string) { this._el.style.width = value; } - - public style(styles: ICheckboxStyles): void { - this.disabledCheckboxForeground = styles.disabledCheckboxForeground; - this.updateStyle(); - } - - private updateStyle(): void { - this._label.style.color = !this.enabled && this.disabledCheckboxForeground ? this.disabledCheckboxForeground.toString() : 'inherit'; - } } diff --git a/src/sql/base/browser/ui/colorbox/colorbox.ts b/src/sql/base/browser/ui/colorbox/colorbox.ts index d7db2bccf3..f5a6169abb 100644 --- a/src/sql/base/browser/ui/colorbox/colorbox.ts +++ b/src/sql/base/browser/ui/colorbox/colorbox.ts @@ -3,8 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/colorbox'; - -import { Color } from 'vs/base/common/color'; import { Event, Emitter } from 'vs/base/common/event'; import { Widget } from 'vs/base/browser/ui/widget'; import * as DOM from 'vs/base/browser/dom'; @@ -13,18 +11,13 @@ import { generateUuid } from 'vs/base/common/uuid'; export interface ColorboxOptions { name: string; class?: string[]; - label: string; -} - -export interface ColorboxStyle { - backgroundColor?: Color; + color: string; } export class Colorbox extends Widget { readonly radioButton: HTMLInputElement; readonly colorElement: HTMLDivElement; private labelNode: HTMLLabelElement; - private backgroundColor?: Color; private _onSelect = new Emitter(); public readonly onSelect: Event = this._onSelect.event; @@ -34,6 +27,7 @@ export class Colorbox extends Widget { const colorboxContainer = DOM.$('.colorbox-container'); this.colorElement = DOM.$('.color-element'); const radiobuttonContainer = DOM.$('.color-selector-container'); + this.colorElement.style.background = opts.color; this.radioButton = DOM.$('input'); this.radioButton.type = 'radio'; this.radioButton.name = opts.name; @@ -43,10 +37,10 @@ export class Colorbox extends Widget { if (opts.class) { this.radioButton.classList.add(...opts.class); } - this.radioButton.setAttribute('aria-label', opts.label); + this.radioButton.setAttribute('aria-label', opts.color); this.labelNode = DOM.$('label.colorbox-label'); this.labelNode.setAttribute('for', this.radioButton.id); - this.labelNode.innerText = opts.label; + this.labelNode.innerText = opts.color; radiobuttonContainer.appendChild(this.radioButton); radiobuttonContainer.appendChild(this.labelNode); @@ -60,17 +54,6 @@ export class Colorbox extends Widget { } - public style(styles: ColorboxStyle): void { - if (styles.backgroundColor) { - this.backgroundColor = styles.backgroundColor; - } - this.updateStyle(); - } - - private updateStyle(): void { - this.colorElement.style.background = this.backgroundColor ? this.backgroundColor.toString() : this.radioButton.style.background; - } - public get checked(): boolean { return this.radioButton.checked; } diff --git a/src/sql/base/browser/ui/dropdownList/dropdownList.ts b/src/sql/base/browser/ui/dropdownList/dropdownList.ts index c78c1881c2..d14b4d18d3 100644 --- a/src/sql/base/browser/ui/dropdownList/dropdownList.ts +++ b/src/sql/base/browser/ui/dropdownList/dropdownList.ts @@ -5,28 +5,81 @@ import 'vs/css!./media/dropdownList'; import * as DOM from 'vs/base/browser/dom'; -import { Dropdown, IDropdownOptions } from 'vs/base/browser/ui/dropdown/dropdown'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { Color } from 'vs/base/common/color'; import { IAction } from 'vs/base/common/actions'; import { EventType as GestureEventType } from 'vs/base/browser/touch'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { Button, IButtonStyles } from 'sql/base/browser/ui/button/button'; +import { Button } from 'sql/base/browser/ui/button/button'; +import { BaseDropdown, IBaseDropdownOptions } from 'vs/base/browser/ui/dropdown/dropdown'; +import { IAnchor, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { IButtonStyles } from 'vs/base/browser/ui/button/button'; export interface IDropdownStyles { - backgroundColor?: Color; - foregroundColor?: Color; - borderColor?: Color; + backgroundColor?: string; + foregroundColor?: string; + borderColor?: string; } -export class DropdownList extends Dropdown { +export interface IDropdownOptions extends IBaseDropdownOptions { + contextViewProvider: IContextViewProvider; + buttonStyles: IButtonStyles; + dropdownStyles: IDropdownStyles; +} - protected backgroundColor?: Color; - protected foregroundColor?: Color; - protected borderColor?: Color; +export class Dropdown extends BaseDropdown { + private contextViewProvider: IContextViewProvider; + + constructor(container: HTMLElement, options: IDropdownOptions) { + super(container, options); + + this.contextViewProvider = options.contextViewProvider; + } + + override show(): void { + super.show(); + + this.element.classList.add('active'); + + this.contextViewProvider.showContextView({ + getAnchor: () => this.getAnchor(), + + render: (container) => { + return this.renderContents(container); + }, + + onDOMEvent: (e, activeElement) => { + this.onEvent(e, activeElement); + }, + + onHide: () => this.onHide() + }); + } + + protected getAnchor(): HTMLElement | IAnchor { + return this.element; + } + + protected onHide(): void { + this.element.classList.remove('active'); + } + + override hide(): void { + super.hide(); + + if (this.contextViewProvider) { + this.contextViewProvider.hideContextView(); + } + } + + protected renderContents(container: HTMLElement): IDisposable | null { + return null; + } +} + +export class DropdownList extends BaseDropdown { protected borderWidth = 1; private button?: Button; @@ -40,7 +93,7 @@ export class DropdownList extends Dropdown { ) { super(container, _options); if (action) { - this.button = new Button(_contentContainer); + this.button = new Button(_contentContainer, this._options.buttonStyles); this.button.label = action.label; this._register(DOM.addDisposableListener(this.button.element, DOM.EventType.CLICK, () => { action.run(); @@ -83,12 +136,16 @@ export class DropdownList extends Dropdown { })); this.element.setAttribute('tabindex', '0'); + this.applyStylesOnElement(this._contentContainer, _options.dropdownStyles.backgroundColor, _options.dropdownStyles.foregroundColor, _options.dropdownStyles.borderColor); + if (this.label) { + this.applyStylesOnElement(this.element, _options.dropdownStyles.backgroundColor, _options.dropdownStyles.foregroundColor, _options.dropdownStyles.borderColor); + } } /** * Render the dropdown contents */ - protected override renderContents(container: HTMLElement): IDisposable { + protected renderContents(container: HTMLElement): IDisposable | null { let div = DOM.append(container, this._contentContainer); div.style.width = (DOM.getTotalWidth(this.element) - this.borderWidth * 2) + 'px'; // Subtract border width return { dispose: () => { } }; @@ -121,26 +178,6 @@ export class DropdownList extends Dropdown { } } - public style(styles: IDropdownStyles & IButtonStyles): void { - this.backgroundColor = styles.backgroundColor; - this.foregroundColor = styles.foregroundColor; - this.borderColor = styles.borderColor; - this.applyStyles(); - if (this.button) { - this.button.style(styles); - } - } - - protected applyStyles(): void { - const background = this.backgroundColor ? this.backgroundColor.toString() : ''; - const foreground = this.foregroundColor ? this.foregroundColor.toString() : ''; - const border = this.borderColor ? this.borderColor.toString() : ''; - this.applyStylesOnElement(this._contentContainer, background, foreground, border); - if (this.label) { - this.applyStylesOnElement(this.element, background, foreground, border); - } - } - private applyStylesOnElement(element: HTMLElement, background: string, foreground: string, border: string): void { if (element) { element.style.backgroundColor = background; diff --git a/src/sql/base/browser/ui/editableDropdown/browser/dropdown.ts b/src/sql/base/browser/ui/editableDropdown/browser/dropdown.ts index 23701e19d3..2b18a6a2fa 100644 --- a/src/sql/base/browser/ui/editableDropdown/browser/dropdown.ts +++ b/src/sql/base/browser/ui/editableDropdown/browser/dropdown.ts @@ -20,6 +20,7 @@ import { clamp } from 'vs/base/common/numbers'; import { mixin } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import * as nls from 'vs/nls'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface IDropdownOptions extends IDropdownStyles { @@ -124,7 +125,8 @@ export class Dropdown extends Disposable implements IListVirtualDelegate }, placeholder: this._options.placeholder, ariaLabel: this._options.ariaLabel, - ariaDescription: this._options.ariaDescription + ariaDescription: this._options.ariaDescription, + inputBoxStyles: defaultInputBoxStyles }); // Clear title from input box element (defaults to placeholder value) since we don't want a tooltip for the selected value diff --git a/src/sql/base/browser/ui/infoButton/infoButton.ts b/src/sql/base/browser/ui/infoButton/infoButton.ts index 681a97743c..e175b55d9f 100644 --- a/src/sql/base/browser/ui/infoButton/infoButton.ts +++ b/src/sql/base/browser/ui/infoButton/infoButton.ts @@ -5,16 +5,16 @@ import 'vs/css!./infoButton'; import { Button as sqlButton } from 'sql/base/browser/ui/button/button'; -import { IButtonOptions, IButtonStyles } from 'vs/base/browser/ui/button/button'; +import { IButtonOptions } from 'vs/base/browser/ui/button/button'; export interface IInfoButtonOptions extends IButtonOptions { - buttonMaxHeight: number, - buttonMaxWidth: number, - description: string, - iconClass: string, - iconHeight: number, - iconWidth: number, - title: string, + buttonMaxHeight?: number, + buttonMaxWidth?: number, + description?: string, + iconClass?: string, + iconHeight?: number, + iconWidth?: number, + title?: string, } export class InfoButton extends sqlButton { @@ -34,8 +34,6 @@ export class InfoButton extends sqlButton { private _iconWidth?: number; private _title?: string; - private _styles: IButtonStyles; - constructor(container: HTMLElement, options?: IInfoButtonOptions) { super(container, options); this._container = container; @@ -143,27 +141,37 @@ export class InfoButton extends sqlButton { if (!options) { return; } - this.buttonMaxHeight = options.buttonMaxHeight; - this.buttonMaxWidth = options.buttonMaxWidth; - this.description = options.description; - this.iconHeight = options.iconHeight; - this.iconWidth = options.iconWidth; - this.iconClass = options.iconClass; - this.title = options.title; + if (options.buttonMaxHeight !== undefined) { + this.buttonMaxHeight = options.buttonMaxHeight; + } + if (options.buttonMaxWidth !== undefined) { + this.buttonMaxWidth = options.buttonMaxWidth; + } + if (options.description) { + this.description = options.description; + } + if (options.iconHeight !== undefined) { + this.iconHeight = options.iconHeight; + } + if (options.iconWidth !== undefined) { + this.iconWidth = options.iconWidth; + } + if (options.iconClass) { + this.iconClass = options.iconClass; + } + if (options.title) { + this.title = options.title; + } } - override style(styles: IButtonStyles): void { - this._styles = styles; - this.applyStyles(); + override updateStyles() { + this.element.style.backgroundColor = this.options.buttonBackground ?? ''; + this.element.style.color = this.options.buttonForeground ?? ''; + this.element.style.borderColor = this.options.buttonBorder ?? ''; } - override applyStyles(): void { - this.element.style.backgroundColor = this._styles?.buttonBackground?.toString(); - this.element.style.color = this._styles?.buttonForeground?.toString(); - this.element.style.borderColor = this._styles?.buttonBorder?.toString(); + override updateBackground(hover: boolean): void { + this.element.style.backgroundColor = (hover ? this.options.buttonHoverBackground : this.options.buttonBackground) ?? ''; } - override setHoverBackground(): void { - this.element.style.backgroundColor = this._styles?.buttonHoverBackground?.toString(); - } } diff --git a/src/sql/base/browser/ui/inputBox/inputBox.ts b/src/sql/base/browser/ui/inputBox/inputBox.ts index c5adb53e9f..48b7699663 100644 --- a/src/sql/base/browser/ui/inputBox/inputBox.ts +++ b/src/sql/base/browser/ui/inputBox/inputBox.ts @@ -31,12 +31,12 @@ export interface IInputOptions extends vsIInputBoxOptions { } export class InputBox extends vsInputBox implements AdsWidget { - private enabledInputBackground?: Color; - private enabledInputForeground?: Color; - private enabledInputBorder?: Color; - private disabledInputBackground?: Color; - private disabledInputForeground?: Color; - private disabledInputBorder?: Color; + // private enabledInputBackground?: Color; + // private enabledInputForeground?: Color; + // private enabledInputBorder?: Color; + // private disabledInputBackground?: Color; + // private disabledInputForeground?: Color; + // private disabledInputBorder?: Color; private _lastLoseFocusValue: string; @@ -51,10 +51,11 @@ export class InputBox extends vsInputBox implements AdsWidget { constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, private _sqlOptions?: IInputOptions, id?: string) { super(container, contextViewProvider, _sqlOptions); - this.enabledInputBackground = this.inputBackground; - this.enabledInputForeground = this.inputForeground; - this.enabledInputBorder = this.inputBorder; - this.disabledInputBackground = Color.transparent; + // {{SQL CARBON TODO}} - fix styles + // this.enabledInputBackground = this.inputBackground; + // this.enabledInputForeground = this.inputForeground; + // this.enabledInputBorder = this.inputBorder; + //this.disabledInputBackground = Color.transparent; this._lastLoseFocusValue = this.value; let self = this; @@ -82,14 +83,14 @@ export class InputBox extends vsInputBox implements AdsWidget { } public override style(styles: IInputBoxStyles): void { - super.style(styles); - this.enabledInputBackground = this.inputBackground; - this.enabledInputForeground = this.inputForeground; - this.enabledInputBorder = this.inputBorder; - this.disabledInputBackground = styles.disabledInputBackground; - this.disabledInputForeground = styles.disabledInputForeground; - this.updateInputEnabledDisabledColors(); - this.applyStyles(); + // super.style(styles); + // this.enabledInputBackground = this.inputBackground; + // this.enabledInputForeground = this.inputForeground; + // this.enabledInputBorder = this.inputBorder; + // this.disabledInputBackground = styles.disabledInputBackground; + // this.disabledInputForeground = styles.disabledInputForeground; + // this.updateInputEnabledDisabledColors(); + // this.applyStyles(); } public override enable(): void { @@ -168,10 +169,10 @@ export class InputBox extends vsInputBox implements AdsWidget { } private updateInputEnabledDisabledColors(): void { - let enabled = this.isEnabled(); - this.inputBackground = enabled ? this.enabledInputBackground : this.disabledInputBackground; - this.inputForeground = enabled ? this.enabledInputForeground : this.disabledInputForeground; - this.inputBorder = enabled ? this.enabledInputBorder : this.disabledInputBorder; + // let enabled = this.isEnabled(); + // this.inputBackground = enabled ? this.enabledInputBackground : this.disabledInputBackground; + // this.inputForeground = enabled ? this.enabledInputForeground : this.disabledInputForeground; + // this.inputBorder = enabled ? this.enabledInputBorder : this.disabledInputBorder; } public override validate(force?: boolean): MessageType | undefined { diff --git a/src/sql/base/browser/ui/listBox/listBox.ts b/src/sql/base/browser/ui/listBox/listBox.ts index c9120d1567..ac3f730cfd 100644 --- a/src/sql/base/browser/ui/listBox/listBox.ts +++ b/src/sql/base/browser/ui/listBox/listBox.ts @@ -3,15 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { SelectBox, ISelectBoxStyles, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import { Color } from 'vs/base/common/color'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { IMessage, MessageType, defaultOpts } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IContextViewProvider, AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { Emitter } from 'vs/base/common/event'; import { renderFormattedText, renderText, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer'; +import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; const $ = dom.$; @@ -31,12 +32,12 @@ export interface IListBoxStyles { * Extends SelectBox to allow multiple selection and adding/remove items dynamically */ export class ListBox extends SelectBox { - private enabledSelectBackground?: Color; - private enabledSelectForeground?: Color; - private enabledSelectBorder?: Color; - private disabledSelectBackground?: Color; - private disabledSelectForeground?: Color; - private disabledSelectBorder?: Color; + // private enabledSelectBackground?: Color; + // private enabledSelectForeground?: Color; + // private enabledSelectBorder?: Color; + // private disabledSelectBackground?: Color; + // private disabledSelectForeground?: Color; + // private disabledSelectBorder?: Color; private inputValidationInfoBorder?: Color; private inputValidationInfoBackground?: Color; @@ -56,7 +57,7 @@ export class ListBox extends SelectBox { private options: ISelectOptionItem[], contextViewProvider: IContextViewProvider) { - super(options, 0, contextViewProvider); + super(options, 0, contextViewProvider, defaultSelectBoxStyles); this.contextViewProvider = contextViewProvider; this.isValid = true; this.selectElement.multiple = true; @@ -77,51 +78,53 @@ export class ListBox extends SelectBox { this.selectElement.focus(); })); - this.enabledSelectBackground = this.selectBackground; - this.enabledSelectForeground = this.selectForeground; - this.enabledSelectBorder = this.selectBorder; - this.disabledSelectBackground = Color.transparent; + // this.enabledSelectBackground = this.selectBackground; + // this.enabledSelectForeground = this.selectForeground; + // this.enabledSelectBorder = this.selectBorder; + //this.disabledSelectBackground = Color.transparent; - this.inputValidationInfoBorder = defaultOpts.inputValidationInfoBorder; - this.inputValidationInfoBackground = defaultOpts.inputValidationInfoBackground; - this.inputValidationWarningBorder = defaultOpts.inputValidationWarningBorder; - this.inputValidationWarningBackground = defaultOpts.inputValidationWarningBackground; - this.inputValidationErrorBorder = defaultOpts.inputValidationErrorBorder; - this.inputValidationErrorBackground = defaultOpts.inputValidationErrorBackground; + // this.inputValidationInfoBorder = defaultOpts.inputValidationInfoBorder; + // this.inputValidationInfoBackground = defaultOpts.inputValidationInfoBackground; + // this.inputValidationWarningBorder = defaultOpts.inputValidationWarningBorder; + // this.inputValidationWarningBackground = defaultOpts.inputValidationWarningBackground; + // this.inputValidationErrorBorder = defaultOpts.inputValidationErrorBorder; + // this.inputValidationErrorBackground = defaultOpts.inputValidationErrorBackground; this.onblur(this.selectElement, () => this.onBlur()); this.onfocus(this.selectElement, () => this.onFocus()); } - public override style(styles: IListBoxStyles): void { - let superStyle: ISelectBoxStyles = { - selectBackground: styles.selectBackground, - selectForeground: styles.selectForeground, - selectBorder: styles.selectBorder - }; - super.style(superStyle); - this.enabledSelectBackground = this.selectBackground; - this.enabledSelectForeground = this.selectForeground; - this.enabledSelectBorder = this.selectBorder; + // {{SQL CARBON TODO}} - apply styles + public style(styles: IListBoxStyles): void { + // let superStyle: ISelectBoxStyles = { + // selectBackground: styles.selectBackground, + // selectForeground: styles.selectForeground, + // selectBorder: styles.selectBorder + // }; + // super.style(superStyle); + // this.enabledSelectBackground = this.selectBackground; + // this.enabledSelectForeground = this.selectForeground; + // this.enabledSelectBorder = this.selectBorder; - this.inputValidationInfoBackground = styles.inputValidationInfoBackground; - this.inputValidationInfoBorder = styles.inputValidationInfoBorder; - this.inputValidationWarningBackground = styles.inputValidationWarningBackground; - this.inputValidationWarningBorder = styles.inputValidationWarningBorder; - this.inputValidationErrorBackground = styles.inputValidationErrorBackground; - this.inputValidationErrorBorder = styles.inputValidationErrorBorder; + // this.inputValidationInfoBackground = styles.inputValidationInfoBackground; + // this.inputValidationInfoBorder = styles.inputValidationInfoBorder; + // this.inputValidationWarningBackground = styles.inputValidationWarningBackground; + // this.inputValidationWarningBorder = styles.inputValidationWarningBorder; + // this.inputValidationErrorBackground = styles.inputValidationErrorBackground; + // this.inputValidationErrorBorder = styles.inputValidationErrorBorder; } public setValidation(isValid: boolean, message?: IMessage): void { this.isValid = isValid; this.message = message; - if (this.isValid) { - this.selectElement.style.border = `1px solid ${this.selectBorder}`; - } else if (this.message) { - const styles = this.stylesForType(this.message.type); - this.selectElement.style.border = styles.border ? `1px solid ${styles.border}` : ''; - } + // {{SQL CARBON TODO}} - apply styles + // if (this.isValid) { + // this.selectElement.style.border = `1px solid ${this.selectBorder}`; + // } else if (this.message) { + // const styles = this.stylesForType(this.message.type); + // this.selectElement.style.border = styles.border ? `1px solid ${styles.border}` : ''; + // } } public get isContentValid(): boolean { @@ -171,18 +174,18 @@ export class ListBox extends SelectBox { public enable(): void { this.selectElement.disabled = false; - this.selectBackground = this.enabledSelectBackground; - this.selectForeground = this.enabledSelectForeground; - this.selectBorder = this.enabledSelectBorder; - this.applyStyles(); + // this.selectBackground = this.enabledSelectBackground; + // this.selectForeground = this.enabledSelectForeground; + // this.selectBorder = this.enabledSelectBorder; + // this.applyStyles(); } public disable(): void { this.selectElement.disabled = true; - this.selectBackground = this.disabledSelectBackground; - this.selectForeground = this.disabledSelectForeground; - this.selectBorder = this.disabledSelectBorder; - this.applyStyles(); + // this.selectBackground = this.disabledSelectBackground; + // this.selectForeground = this.disabledSelectForeground; + // this.selectBorder = this.disabledSelectBorder; + // this.applyStyles(); } public onBlur(): void { diff --git a/src/sql/base/browser/ui/panel/panel.component.ts b/src/sql/base/browser/ui/panel/panel.component.ts index 5ab57b2d32..713aa3cc41 100644 --- a/src/sql/base/browser/ui/panel/panel.component.ts +++ b/src/sql/base/browser/ui/panel/panel.component.ts @@ -22,7 +22,6 @@ import * as nls from 'vs/nls'; import { TabHeaderComponent } from 'sql/base/browser/ui/panel/tabHeader.component'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { IThemable } from 'vs/base/common/styler'; import { ITabbedPanelStyles } from 'sql/base/browser/ui/panel/panel'; import { createStyleSheet } from 'vs/base/browser/dom'; @@ -83,7 +82,7 @@ let idPool = 0;
` }) -export class PanelComponent extends Disposable implements IThemable { +export class PanelComponent extends Disposable { private _options: IPanelOptions = defaultOptions; @Input() public set options(newOptions: IPanelOptions) { diff --git a/src/sql/base/browser/ui/selectBox/selectBox.ts b/src/sql/base/browser/ui/selectBox/selectBox.ts index 01f2fb7b0c..bb34cdd9d9 100644 --- a/src/sql/base/browser/ui/selectBox/selectBox.ts +++ b/src/sql/base/browser/ui/selectBox/selectBox.ts @@ -18,6 +18,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom'; import { Event, Emitter } from 'vs/base/common/event'; import { AdsWidget } from 'sql/base/browser/ui/adsWidget'; +import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; const $ = dom.$; @@ -45,12 +46,12 @@ export class SelectBox extends vsSelectBox implements AdsWidget { private _dialogOptions: SelectOptionItemSQL[]; private _selectedOption: string; private _selectBoxOptions?: ISelectBoxOptions; - private enabledSelectBackground?: Color; - private enabledSelectForeground?: Color; - private enabledSelectBorder?: Color; - private disabledSelectBackground?: Color; - private disabledSelectForeground?: Color; - private disabledSelectBorder?: Color; + // private enabledSelectBackground?: Color; + // private enabledSelectForeground?: Color; + // private enabledSelectBorder?: Color; + // private disabledSelectBackground?: Color; + // private disabledSelectForeground?: Color; + // private disabledSelectBorder?: Color; private contextViewProvider: IContextViewProvider; private message?: IMessage; private _onDidSelect: Emitter; @@ -70,7 +71,7 @@ export class SelectBox extends vsSelectBox implements AdsWidget { constructor(options: SelectOptionItemSQL[] | string[], selectedOption: string, contextViewProvider: IContextViewProvider, container?: HTMLElement, selectBoxOptions?: ISelectBoxOptions, id?: string) { let optionItems: SelectOptionItemSQL[] = SelectBox.createOptions(options); - super(optionItems, 0, contextViewProvider, undefined, selectBoxOptions); + super(optionItems, 0, contextViewProvider, defaultSelectBoxStyles, selectBoxOptions); this._onDidSelect = new Emitter(); this._onDidFocus = new Emitter(); @@ -88,12 +89,13 @@ export class SelectBox extends vsSelectBox implements AdsWidget { this._onDidSelect.fire(newSelect); })); - this.enabledSelectBackground = this.selectBackground; - this.enabledSelectForeground = this.selectForeground; - this.enabledSelectBorder = this.selectBorder; - this.disabledSelectBackground = Color.transparent; - this.disabledSelectForeground = undefined; - this.disabledSelectBorder = undefined; + // {{SQL CARBON TODO}} - fix styles + // this.enabledSelectBackground = this.selectBackground; + // this.enabledSelectForeground = this.selectForeground; + // this.enabledSelectBorder = this.selectBorder; + // this.disabledSelectBackground = Color.transparent; + // this.disabledSelectForeground = undefined; + // this.disabledSelectBorder = undefined; this.contextViewProvider = contextViewProvider; if (container) { this.element = dom.append(container, $('.monaco-selectbox.idle')); @@ -178,13 +180,12 @@ export class SelectBox extends vsSelectBox implements AdsWidget { this._dialogOptions = options; } - public override style(styles: ISelectBoxStyles): void { - super.style(styles); - this.enabledSelectBackground = this.selectBackground; - this.enabledSelectForeground = this.selectForeground; - this.enabledSelectBorder = this.selectBorder; - this.disabledSelectBackground = styles.disabledSelectBackground; - this.disabledSelectForeground = styles.disabledSelectForeground; + public style(styles: ISelectBoxStyles): void { + // this.enabledSelectBackground = this.selectBackground; + // this.enabledSelectForeground = this.selectForeground; + // this.enabledSelectBorder = this.selectBorder; + // this.disabledSelectBackground = styles.disabledSelectBackground; + // this.disabledSelectForeground = styles.disabledSelectForeground; this.inputValidationInfoBorder = styles.inputValidationInfoBorder; this.inputValidationInfoBackground = styles.inputValidationInfoBackground; this.inputValidationInfoForeground = styles.inputinputValidationInfoForeground; @@ -194,7 +195,7 @@ export class SelectBox extends vsSelectBox implements AdsWidget { this.inputValidationErrorBorder = styles.inputValidationErrorBorder; this.inputValidationErrorBackground = styles.inputValidationErrorBackground; this.inputValidationErrorForeground = styles.inputValidationErrorForeground; - this.applyStyles(); + //this.applyStyles(); } public selectWithOptionName(optionName?: string, selectFirstByDefault: boolean = true, forceSelectionEvent: boolean = false): void { @@ -244,18 +245,18 @@ export class SelectBox extends vsSelectBox implements AdsWidget { public enable(): void { this.selectElement.disabled = false; - this.selectBackground = this.enabledSelectBackground; - this.selectForeground = this.enabledSelectForeground; - this.selectBorder = this.enabledSelectBorder; - this.applyStyles(); + // this.selectBackground = this.enabledSelectBackground; + // this.selectForeground = this.enabledSelectForeground; + // this.selectBorder = this.enabledSelectBorder; + //this.applyStyles(); } public disable(): void { this.selectElement.disabled = true; - this.selectBackground = this.disabledSelectBackground; - this.selectForeground = this.disabledSelectForeground; - this.selectBorder = this.disabledSelectBorder; - this.applyStyles(); + // this.selectBackground = this.disabledSelectBackground; + // this.selectForeground = this.disabledSelectForeground; + // this.selectBorder = this.disabledSelectBorder; + //this.applyStyles(); } public getAriaLabel(): string { @@ -343,7 +344,7 @@ export class SelectBox extends vsSelectBox implements AdsWidget { } this._hideMessage(); - this.applyStyles(); + //this.applyStyles(); this.message = undefined; } diff --git a/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts b/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts index e84d7461cf..cb032ab516 100644 --- a/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts +++ b/src/sql/base/browser/ui/table/plugins/headerFilter.plugin.ts @@ -19,7 +19,8 @@ import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Emitter } from 'vs/base/common/event'; -import { CountBadge, ICountBadgetyles } from 'vs/base/browser/ui/countBadge/countBadge'; +import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; +import { defaultCountBadgeStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export type HeaderFilterCommands = 'sort-asc' | 'sort-desc'; @@ -35,17 +36,17 @@ export interface ITableFilterOptions { */ disabledFilterMessage?: string; /** - * The columns are refreshed by default to add the filter menu button to the headers. + * The columns are refreshed by default to add the filter menu button to the headers. The default value is true. * Set to false to prevent the grid from being re-drawn multiple times by different plugins. */ refreshColumns?: boolean; + /** + * The button styles. + */ + buttonStyles: IButtonStyles; } -const DefaultTableFilterOptions: ITableFilterOptions = { - refreshColumns: true -}; - -export interface ITableFilterStyles extends IButtonStyles, IInputBoxStyles, IListStyles, ICountBadgetyles { +export interface ITableFilterStyles extends IInputBoxStyles, IListStyles { } interface NotificationProvider { @@ -69,8 +70,9 @@ export class HeaderFilter { private okButton?: Button; private clearButton?: Button; private cancelButton?: Button; - private sortAscButton?: Button; - private sortDescButton?: Button; + // {{SQL CARBON TODO}} - disable + // private sortAscButton?: Button; + // private sortDescButton?: Button; private selectAllCheckBox?: Checkbox; private searchInputBox?: InputBox; private countBadge?: CountBadge; @@ -86,7 +88,7 @@ export class HeaderFilter { private previouslyFocusedElement: HTMLElement; private listContainer?: HTMLElement; - constructor(private readonly contextViewProvider: IContextViewProvider, private readonly notificationProvider?: NotificationProvider, private readonly options: ITableFilterOptions = DefaultTableFilterOptions) { + constructor(private readonly options: ITableFilterOptions, private readonly contextViewProvider: IContextViewProvider, private readonly notificationProvider?: NotificationProvider) { } public init(grid: Slick.Grid): void { @@ -182,8 +184,8 @@ export class HeaderFilter { private createButtonMenuItem(title: string, command: HeaderFilterCommands, iconClass: string): Button { const buttonContainer = append(this.menu, $('.slick-header-menu-image-button-container')); - const button = new Button(buttonContainer); - button.icon = { id: `slick-header-menuicon ${iconClass}` }; + const button = new Button(buttonContainer, this.options.buttonStyles); + button.icon = `slick-header-menuicon ${iconClass}`; button.label = title; button.onDidClick(async () => { await this.handleMenuItemClick(command, this.columnDef); @@ -205,20 +207,21 @@ export class HeaderFilter { this.searchInputBox = new InputBox(append(searchRow, $('.search-input')), this.contextViewProvider, { - placeholder: localize('table.searchPlaceHolder', "Search") + placeholder: localize('table.searchPlaceHolder', "Search"), + inputBoxStyles: defaultInputBoxStyles }); const visibleCountContainer = append(searchRow, $('.visible-count')); visibleCountContainer.setAttribute('aria-live', 'polite'); visibleCountContainer.setAttribute('aria-atomic', 'true'); this.visibleCountBadge = new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'tableFilter.visibleCount', comment: ['This tells the user how many items are shown in the list. Currently not visible, but read by screen readers.'] }, "{0} Results") - }); + }, defaultCountBadgeStyles); const selectedCountBadgeContainer = append(searchRow, $('.selected-count')); selectedCountBadgeContainer.setAttribute('aria-live', 'polite'); this.countBadge = new CountBadge(selectedCountBadgeContainer, { countFormat: localize({ key: 'tableFilter.selectedCount', comment: ['This tells the user how many items are selected in the list'] }, "{0} Selected") - }); + }, defaultCountBadgeStyles); this.searchInputBox.onDidChange(async (newString) => { this.filteredListData = this.listData.filter(element => element.value?.toUpperCase().indexOf(newString.toUpperCase()) !== -1); @@ -405,27 +408,31 @@ export class HeaderFilter { // Make sure the menu can fit in the screen. this.menu.style.height = `${Math.min(DefaultMenuHeight, window.innerHeight - MenuBarHeight) - MenuVerticalPadding}px`; - this.sortAscButton = this.createButtonMenuItem(localize('table.sortAscending', "Sort Ascending"), 'sort-asc', 'ascending'); - this.sortDescButton = this.createButtonMenuItem(localize('table.sortDescending', "Sort Descending"), 'sort-desc', 'descending'); + // {{SQL CARBON TODO}} - style buttons + // this.sortAscButton = this.createButtonMenuItem(localize('table.sortAscending', "Sort Ascending"), 'sort-asc', 'ascending'); + // this.sortDescButton = this.createButtonMenuItem(localize('table.sortDescending', "Sort Descending"), 'sort-desc', 'descending'); + this.createButtonMenuItem(localize('table.sortAscending', "Sort Ascending"), 'sort-asc', 'ascending'); + this.createButtonMenuItem(localize('table.sortDescending', "Sort Descending"), 'sort-desc', 'descending'); + this.createSearchInputRow(); await this.createFilterList(); const buttonGroupContainer = append(this.menu, $('.filter-menu-button-container')); - this.okButton = this.createButton(buttonGroupContainer, 'filter-ok-button', localize('headerFilter.ok', "OK")); + this.okButton = this.createButton(buttonGroupContainer, 'filter-ok-button', localize('headerFilter.ok', "OK"), this.options.buttonStyles); this.okButton.onDidClick(async () => { this.columnDef.filterValues = this.listData.filter(element => element.checked).map(element => element.value); this.setButtonImage($menuButton, this.columnDef.filterValues.length > 0); await this.handleApply(this.columnDef); }); - this.clearButton = this.createButton(buttonGroupContainer, 'filter-clear-button', localize('headerFilter.clear', "Clear"), { secondary: true }); + this.clearButton = this.createButton(buttonGroupContainer, 'filter-clear-button', localize('headerFilter.clear', "Clear"), { secondary: true, ...this.options.buttonStyles }); this.clearButton.onDidClick(async () => { this.columnDef.filterValues!.length = 0; this.setButtonImage($menuButton, false); await this.handleApply(this.columnDef); }); - this.cancelButton = this.createButton(buttonGroupContainer, 'filter-cancel-button', localize('headerFilter.cancel', "Cancel"), { secondary: true }); + this.cancelButton = this.createButton(buttonGroupContainer, 'filter-cancel-button', localize('headerFilter.cancel', "Cancel"), { secondary: true, ...this.options.buttonStyles }); this.cancelButton.onDidClick(() => { this.hideMenu(); }); @@ -441,15 +448,16 @@ export class HeaderFilter { private applyStyles() { if (this.filterStyles) { - this.okButton?.style(this.filterStyles); - this.cancelButton?.style(this.filterStyles); - this.clearButton?.style(this.filterStyles); - this.sortAscButton?.style(this.filterStyles); - this.sortDescButton?.style(this.filterStyles); - this.searchInputBox?.style(this.filterStyles); - this.countBadge?.style(this.filterStyles); - this.visibleCountBadge?.style(this.filterStyles); - this.list?.style(this.filterStyles); + // {{SQL CARBON TODO}} - apply styles + // this.okButton?.style(this.filterStyles); + // this.cancelButton?.style(this.filterStyles); + // this.clearButton?.style(this.filterStyles); + // this.sortAscButton?.style(this.filterStyles); + // this.sortDescButton?.style(this.filterStyles); + // this.searchInputBox?.style(this.filterStyles); + // this.countBadge?.style(this.filterStyles); + // this.visibleCountBadge?.style(this.filterStyles); + // this.list?.style(this.filterStyles); } } diff --git a/src/sql/base/browser/ui/table/table.ts b/src/sql/base/browser/ui/table/table.ts index 75ec8a8a6a..f7b20426d8 100644 --- a/src/sql/base/browser/ui/table/table.ts +++ b/src/sql/base/browser/ui/table/table.ts @@ -15,7 +15,7 @@ import { mixin } from 'vs/base/common/objects'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { Widget } from 'vs/base/browser/ui/widget'; -import { isArray, isBoolean } from 'vs/base/common/types'; +import { isBoolean } from 'vs/base/common/types'; import { Event, Emitter } from 'vs/base/common/event'; import { range } from 'vs/base/common/arrays'; import { AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView'; @@ -23,6 +23,7 @@ import { IDisposableDataProvider } from 'sql/base/common/dataProvider'; import { IAccessibilityProvider } from 'sql/base/browser/ui/accessibility/accessibilityProvider'; import { IQuickInputProvider } from 'sql/base/browser/ui/quickInput/quickInputProvider'; import { localize } from 'vs/nls'; +import { IThemable } from 'sql/platform/theme/common/vsstyler'; function getDefaultOptions(): Slick.GridOptions { return >{ @@ -32,7 +33,7 @@ function getDefaultOptions(): Slick.GridOptions { }; } -export class Table extends Widget implements IDisposable { +export class Table extends Widget implements IDisposable, IThemable { protected styleElement: HTMLStyleElement; protected idPrefix: string; @@ -75,10 +76,10 @@ export class Table extends Widget implements IDisposa configuration?: ITableConfiguration, options?: Slick.GridOptions) { super(); - if (!configuration || !configuration.dataProvider || isArray(configuration.dataProvider)) { + if (!configuration || !configuration.dataProvider || Array.isArray(configuration.dataProvider)) { this._data = new TableDataView(configuration && configuration.dataProvider as Array); } else { - this._data = configuration.dataProvider; + this._data = configuration.dataProvider; } this._register(this._data); diff --git a/src/sql/base/browser/ui/table/tableCellEditorFactory.ts b/src/sql/base/browser/ui/table/tableCellEditorFactory.ts index 0b0e7d9219..a6dc097d13 100644 --- a/src/sql/base/browser/ui/table/tableCellEditorFactory.ts +++ b/src/sql/base/browser/ui/table/tableCellEditorFactory.ts @@ -11,6 +11,7 @@ import * as DOM from 'vs/base/browser/dom'; import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown'; import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; const InverseKeyCodeMap: { [k: string]: number } = Object.fromEntries(Object.entries(EVENT_KEY_CODE_MAP).map(([key, value]) => [value, Number(key)])); @@ -64,7 +65,8 @@ export class TableCellEditorFactory { public init(): void { this._input = new InputBox(this._args.container, self._contextViewProvider, { - type: inputType + type: inputType, + inputBoxStyles: defaultInputBoxStyles }); self._options.editorStyler(this._input); this._input.element.style.height = '100%'; diff --git a/src/sql/base/browser/ui/taskbar/actionbar.ts b/src/sql/base/browser/ui/taskbar/actionbar.ts index f132c5b7cf..83cc4c4617 100644 --- a/src/sql/base/browser/ui/taskbar/actionbar.ts +++ b/src/sql/base/browser/ui/taskbar/actionbar.ts @@ -227,7 +227,7 @@ export class ActionBar extends ActionRunner implements IActionRunner { let item: IActionViewItem | undefined = undefined; if (this._options.actionViewItemProvider) { - item = this._options.actionViewItemProvider(action); + item = this._options.actionViewItemProvider(action, {}); } if (!item) { diff --git a/src/sql/base/browser/ui/taskbar/taskbar.ts b/src/sql/base/browser/ui/taskbar/taskbar.ts index d37645a275..206f76cc30 100644 --- a/src/sql/base/browser/ui/taskbar/taskbar.ts +++ b/src/sql/base/browser/ui/taskbar/taskbar.ts @@ -52,7 +52,7 @@ export class Taskbar { orientation: options.orientation, ariaLabel: options.ariaLabel, actionViewItemProvider: (action: IAction): IActionViewItem | undefined => { - return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; + return options.actionViewItemProvider ? options.actionViewItemProvider(action, {}) : undefined; } } ); @@ -63,7 +63,7 @@ export class Taskbar { orientation: options.orientation, ariaLabel: options.ariaLabel, actionViewItemProvider: (action: IAction): IActionViewItem | undefined => { - return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; + return options.actionViewItemProvider ? options.actionViewItemProvider(action, {}) : undefined; } } ); diff --git a/src/sql/base/parts/tree/browser/treeDefaults.ts b/src/sql/base/parts/tree/browser/treeDefaults.ts index 08607cfdf8..c3e326c61c 100644 --- a/src/sql/base/parts/tree/browser/treeDefaults.ts +++ b/src/sql/base/parts/tree/browser/treeDefaults.ts @@ -14,7 +14,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as _ from 'sql/base/parts/tree/browser/tree'; import { IDragAndDropData } from 'vs/base/browser/dnd'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { createKeybinding, Keybinding, SimpleKeybinding } from 'vs/base/common/keybindings'; +import { Keybinding, decodeKeybinding } from 'vs/base/common/keybindings'; export interface IKeyBindingCallback { (tree: _.ITree, event: IKeyboardEvent): void; @@ -63,7 +63,7 @@ export class KeybindingDispatcher { } public has(keybinding: KeyCode): boolean { - let target = createKeybinding(keybinding, platform.OS); + let target = decodeKeybinding(keybinding, platform.OS); if (target !== null) { for (const a of this._arr) { if (target.equals(a.keybinding)) { @@ -76,16 +76,16 @@ export class KeybindingDispatcher { public set(keybinding: number, callback: IKeyBindingCallback) { this._arr.push({ - keybinding: createKeybinding(keybinding, platform.OS), + keybinding: decodeKeybinding(keybinding, platform.OS), callback: callback }); } - public dispatch(keybinding: SimpleKeybinding): IKeyBindingCallback | null { + public dispatch(keybinding: Keybinding): IKeyBindingCallback | null { // Loop from the last to the first to handle overwrites for (let i = this._arr.length - 1; i >= 0; i--) { let item = this._arr[i]; - if (keybinding.toChord().equals(item.keybinding)) { + if (keybinding.equals(item.keybinding)) { return item.callback; } } @@ -266,7 +266,7 @@ export class DefaultController implements _.IController { } private onKey(bindings: KeybindingDispatcher, tree: _.ITree, event: IKeyboardEvent): boolean { - const handler: any = bindings.dispatch(event.toKeybinding()); + const handler: any = bindings.dispatch(event.toKeyCodeChord().toKeybinding()); if (handler) { // TODO: TS 3.1 upgrade. Why are we checking against void? if (handler(tree, event)) { diff --git a/src/sql/base/parts/tree/browser/treeView.ts b/src/sql/base/parts/tree/browser/treeView.ts index ba7fbf5007..1e93247f59 100644 --- a/src/sql/base/parts/tree/browser/treeView.ts +++ b/src/sql/base/parts/tree/browser/treeView.ts @@ -20,11 +20,12 @@ import { HeightMap, IViewItem } from 'sql/base/parts/tree/browser/treeViewModel' import * as _ from 'sql/base/parts/tree/browser/tree'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Event, Emitter } from 'vs/base/common/event'; -import { DataTransfers, StaticDND, IDragAndDropData } from 'vs/base/browser/dnd'; +import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { DefaultTreestyler } from './treeDefaults'; import { Delayer, timeout } from 'vs/base/common/async'; import { MappedNavigator } from 'sql/base/common/navigator'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { StaticDND } from 'vs/base/browser/ui/list/listView'; export interface IRow { element: HTMLElement | null; diff --git a/src/sql/base/test/browser/ui/taskbar/overflowActionbar.test.ts b/src/sql/base/test/browser/ui/taskbar/overflowActionbar.test.ts index c92b3d2221..f0231cde3f 100644 --- a/src/sql/base/test/browser/ui/taskbar/overflowActionbar.test.ts +++ b/src/sql/base/test/browser/ui/taskbar/overflowActionbar.test.ts @@ -96,7 +96,7 @@ suite('Overflow Actionbar tests', () => { assert(overflowActionbar.actionsList.children.length === 4); assert(overflowActionbar.items.length === 4); assert.strictEqual(getMoreItemPlaceholderIndex(overflowActionbar.items), 2); - assert(overflowActionbar.overflow.childElementCount === 1); + assert(overflowActionbar.overflow.childElementCount === 1); verifyOverflowFocusedIndex(overflowActionbar, 3); // move separator to overflow diff --git a/src/sql/platform/accounts/common/accountActions.ts b/src/sql/platform/accounts/common/accountActions.ts index ce6523fec1..1be58d5735 100644 --- a/src/sql/platform/accounts/common/accountActions.ts +++ b/src/sql/platform/accounts/common/accountActions.ts @@ -81,7 +81,7 @@ export class RemoveAccountAction extends Action { const confirm: IConfirmation = { message: localize('confirmRemoveUserAccountMessage', "Are you sure you want to remove '{0}'?", this._account.displayInfo.displayName), primaryButton: localize('accountActions.yes', "Yes"), - secondaryButton: localize('accountActions.no', "No"), + cancelButton: localize('accountActions.no', "No"), type: 'question' }; diff --git a/src/sql/platform/accounts/test/common/accountStore.test.ts b/src/sql/platform/accounts/test/common/accountStore.test.ts index d2b7289eee..fb1b970bbc 100644 --- a/src/sql/platform/accounts/test/common/accountStore.test.ts +++ b/src/sql/platform/accounts/test/common/accountStore.test.ts @@ -7,7 +7,8 @@ import * as assert from 'assert'; import * as azdata from 'azdata'; import AccountStore from 'sql/platform/accounts/common/accountStore'; import { EventVerifierSingle } from 'sql/base/test/common/event'; -import { ConsoleLogger, LogService } from 'vs/platform/log/common/log'; +import { ConsoleLogger } from 'vs/platform/log/common/log'; +import { LogService } from 'vs/platform/log/common/logService'; const consoleLogService = new LogService(new ConsoleLogger()); suite('Account Store Tests', () => { diff --git a/src/sql/platform/actions/browser/menuEntryActionViewItem.ts b/src/sql/platform/actions/browser/menuEntryActionViewItem.ts index 2b6bfc71db..e3de5314e9 100644 --- a/src/sql/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/sql/platform/actions/browser/menuEntryActionViewItem.ts @@ -10,10 +10,11 @@ import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { ICommandAction } from 'vs/platform/action/common/action'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ThemeIcon } from 'vs/base/common/themables'; const ids = new IdGenerator('menu-item-action-item-icon-'); @@ -39,7 +40,7 @@ export class LabeledMenuItemActionItem extends MenuEntryActionViewItem { super(_action, undefined, labeledkeybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuService); } - override updateLabel(): void { + protected override updateLabel(): void { if (this.label) { this.label.innerText = this._commandAction.label; } @@ -114,7 +115,7 @@ export class MaskedLabeledMenuItemActionItem extends MenuEntryActionViewItem { super(action, undefined, keybindingService, notificationService, contextKeyService, _themeService, _contextMenuService); } - override updateLabel(): void { + protected override updateLabel(): void { if (this.label) { this.label.innerText = this._commandAction.label; } diff --git a/src/sql/platform/browser/inputbox/inputBox.component.ts b/src/sql/platform/browser/inputbox/inputBox.component.ts index 228e8bbbc0..5047e341d3 100644 --- a/src/sql/platform/browser/inputbox/inputBox.component.ts +++ b/src/sql/platform/browser/inputbox/inputBox.component.ts @@ -11,9 +11,10 @@ import { import { InputBox as vsInputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { AngularDisposable } from 'sql/base/browser/lifecycle'; -import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachInputBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; @Component({ selector: 'input-box', @@ -45,7 +46,8 @@ export class InputBox extends AngularDisposable implements OnInit, OnChanges { max: this.max, type: this.type, placeholder: this.placeholder, - ariaLabel: this.ariaLabel + ariaLabel: this.ariaLabel, + inputBoxStyles: defaultInputBoxStyles }); if (this.value) { this._inputbox.value = this.value; diff --git a/src/sql/platform/browser/selectBox/selectBox.component.ts b/src/sql/platform/browser/selectBox/selectBox.component.ts index d8a1139607..4984bb422f 100644 --- a/src/sql/platform/browser/selectBox/selectBox.component.ts +++ b/src/sql/platform/browser/selectBox/selectBox.component.ts @@ -13,7 +13,7 @@ import { AngularDisposable } from 'sql/base/browser/lifecycle'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; -import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachSelectBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @Component({ diff --git a/src/sql/platform/theme/browser/defaultStyles.ts b/src/sql/platform/theme/browser/defaultStyles.ts new file mode 100644 index 0000000000..3caaa3eef1 --- /dev/null +++ b/src/sql/platform/theme/browser/defaultStyles.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { ICheckboxStyles } from 'sql/base/browser/ui/checkbox/checkbox'; +import { IDropdownStyles } from 'sql/base/browser/ui/dropdownList/dropdownList'; +import * as sqlcr from 'sql/platform/theme/common/colorRegistry'; +import { disabledCheckboxForeground } from 'sql/platform/theme/common/colors'; +import { IButtonStyles } from 'vs/base/browser/ui/button/button'; +import { IStyleOverride, overrideStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { asCssVariable, editorBackground, inputBorder, inputForeground } from 'vs/platform/theme/common/colorRegistry'; + + +export const defaultCheckboxStyles: ICheckboxStyles = { + disabledCheckboxForeground: asCssVariable(disabledCheckboxForeground) +}; + +export function getCheckboxStyles(override: IStyleOverride): ICheckboxStyles { + return overrideStyles(override, defaultCheckboxStyles); +} + +export const defaultInfoButtonStyles: IButtonStyles = { + buttonBackground: asCssVariable(sqlcr.infoButtonBackground), + buttonForeground: asCssVariable(sqlcr.infoButtonForeground), + buttonBorder: asCssVariable(sqlcr.infoButtonBorder), + buttonHoverBackground: asCssVariable(sqlcr.infoButtonHoverBackground), + buttonSeparator: undefined, + buttonSecondaryBackground: undefined, + buttonSecondaryForeground: undefined, + buttonSecondaryHoverBackground: undefined, + buttonSecondaryBorder: undefined, + buttonDisabledBackground: undefined, + buttonDisabledForeground: undefined, + buttonDisabledBorder: undefined +} + +export const defaultDropdownStyles: IDropdownStyles = { + foregroundColor: asCssVariable(inputForeground), + borderColor: asCssVariable(inputBorder), + backgroundColor: asCssVariable(editorBackground) +} diff --git a/src/sql/platform/theme/common/styler.ts b/src/sql/platform/theme/common/styler.ts index a3fe262b21..141df18e3d 100644 --- a/src/sql/platform/theme/common/styler.ts +++ b/src/sql/platform/theme/common/styler.ts @@ -8,35 +8,8 @@ import * as colors from './colors'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import * as cr from 'vs/platform/theme/common/colorRegistry'; import * as sqlcr from 'sql/platform/theme/common/colorRegistry'; -import { attachStyler, computeStyles, defaultButtonStyles, defaultListStyles, IColorMapping, IStyleOverrides } from 'vs/platform/theme/common/styler'; +import { IThemable, attachStyler, computeStyles, defaultListStyles, IColorMapping, IStyleOverrides } from 'sql/platform/theme/common/vsstyler'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IThemable } from 'vs/base/common/styler'; - -export interface IDropdownStyleOverrides extends IStyleOverrides { - foregroundColor?: cr.ColorIdentifier; - borderColor?: cr.ColorIdentifier; - backgroundColor?: cr.ColorIdentifier; - buttonForeground?: cr.ColorIdentifier; - buttonBackground?: cr.ColorIdentifier; - buttonHoverBackground?: cr.ColorIdentifier; - buttonBorder?: cr.ColorIdentifier; - buttonFocusOutline?: cr.ColorIdentifier; -} - -export const defaultDropdownStyle: IDropdownStyleOverrides = { - foregroundColor: cr.inputForeground, - borderColor: cr.inputBorder, - backgroundColor: cr.editorBackground, - buttonForeground: cr.buttonForeground, - buttonBackground: cr.buttonBackground, - buttonHoverBackground: cr.buttonHoverBackground, - buttonBorder: cr.contrastBorder, - buttonFocusOutline: colors.buttonFocusOutline -}; - -export function attachDropdownStyler(widget: IThemable, themeService: IThemeService, style?: IDropdownStyleOverrides): IDisposable { - return attachStyler(themeService, { ...defaultDropdownStyle, ...(style || {}) }, widget); -} export interface IInputBoxStyleOverrides extends IStyleOverrides { inputBackground?: cr.ColorIdentifier, @@ -302,18 +275,6 @@ export function attachEditableDropdownStyler(widget: IThemable, themeService: IT return attachStyler(themeService, { ...defaultEditableDropdownStyle, ...(style || {}) }, widget); } -export interface ICheckboxStyleOverrides extends IStyleOverrides { - disabledCheckboxForeground?: cr.ColorIdentifier -} - -export const defaultCheckboxStyles: ICheckboxStyleOverrides = { - disabledCheckboxForeground: colors.disabledCheckboxForeground -}; - -export function attachCheckboxStyler(widget: IThemable, themeService: IThemeService, style?: ICheckboxStyleOverrides): IDisposable { - return attachStyler(themeService, {}, widget); -} - export interface IInfoBoxStyleOverrides { informationBackground: cr.ColorIdentifier, warningBackground: cr.ColorIdentifier, @@ -339,17 +300,6 @@ export interface IInfoButtonStyleOverrides { buttonHoverBackground: cr.ColorIdentifier } -export const defaultInfoButtonStyles: IInfoButtonStyleOverrides = { - buttonBackground: sqlcr.infoButtonBackground, - buttonForeground: sqlcr.infoButtonForeground, - buttonBorder: sqlcr.infoButtonBorder, - buttonHoverBackground: sqlcr.infoButtonHoverBackground -}; - -export function attachInfoButtonStyler(widget: IThemable, themeService: IThemeService, style?: IInfoButtonStyleOverrides): IDisposable { - return attachStyler(themeService, { ...defaultInfoButtonStyles, ...style }, widget); -} - export function attachTableFilterStyler(widget: IThemable, themeService: IThemeService): IDisposable { return attachStyler(themeService, { ...defaultInputBoxStyles, @@ -377,15 +327,11 @@ export function attachDesignerStyler(widget: any, themeService: IThemeService): const inputStyles = computeStyles(colorTheme, defaultInputBoxStyles); const selectBoxStyles = computeStyles(colorTheme, defaultSelectBoxStyles); const tableStyles = computeStyles(colorTheme, defaultTableStyles); - const checkboxStyles = computeStyles(colorTheme, defaultCheckboxStyles); - const buttonStyles = computeStyles(colorTheme, defaultButtonStyles); const editableDropdownStyles = computeStyles(colorTheme, defaultEditableDropdownStyle); widget.style({ inputBoxStyles: inputStyles, selectBoxStyles: selectBoxStyles, tableStyles: tableStyles, - checkboxStyles: checkboxStyles, - buttonStyles: buttonStyles, dropdownStyles: editableDropdownStyles, paneSeparator: cr.resolveColorValue(sqlcr.DesignerPaneSeparator, colorTheme), groupHeaderBackground: cr.resolveColorValue(sqlcr.GroupHeaderBackground, colorTheme) diff --git a/src/vs/platform/theme/common/styler.ts b/src/sql/platform/theme/common/vsstyler.ts similarity index 83% rename from src/vs/platform/theme/common/styler.ts rename to src/sql/platform/theme/common/vsstyler.ts index cd49d26b13..9f6faad6a4 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/sql/platform/theme/common/vsstyler.ts @@ -5,11 +5,17 @@ import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IThemable, styleFn } from 'vs/base/common/styler'; -import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, progressBarBackground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, selectBackground, selectBorder, selectForeground, selectListBackground, checkboxBackground, checkboxBorder, checkboxForeground, tableColumnsBorder, tableOddRowsBackgroundColor, textLinkForeground, treeIndentGuidesStroke, widgetShadow, listFocusAndSelectionOutline, listFilterWidgetShadow, buttonSeparator, buttonSecondaryBorder, buttonDisabledBorder, buttonDisabledBackground, buttonDisabledForeground } from 'vs/platform/theme/common/colorRegistry'; // {{SQL CARBON EDIT}} Added buttonSecondaryBorder, buttonDisabledBorder, buttonDisabledBackground, buttonDisabledForeground to import list +import { activeContrastBorder, badgeBackground, badgeForeground, breadcrumbsActiveSelectionForeground, breadcrumbsBackground, breadcrumbsFocusForeground, breadcrumbsForeground, buttonBackground, buttonBorder, buttonForeground, buttonHoverBackground, buttonSecondaryBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground, ColorIdentifier, ColorTransform, ColorValue, contrastBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, focusBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, keybindingLabelBackground, keybindingLabelBorder, keybindingLabelBottomBorder, keybindingLabelForeground, listActiveSelectionBackground, listActiveSelectionForeground, listActiveSelectionIconForeground, listDropBackground, listFilterWidgetBackground, listFilterWidgetNoMatchesOutline, listFilterWidgetOutline, listFocusBackground, listFocusForeground, listFocusOutline, listHoverBackground, listHoverForeground, listInactiveFocusBackground, listInactiveFocusOutline, listInactiveSelectionBackground, listInactiveSelectionForeground, listInactiveSelectionIconForeground, menuBackground, menuBorder, menuForeground, menuSelectionBackground, menuSelectionBorder, menuSelectionForeground, menuSeparatorBackground, pickerGroupForeground, problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground, progressBarBackground, quickInputListFocusBackground, quickInputListFocusForeground, quickInputListFocusIconForeground, resolveColorValue, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, selectBackground, selectBorder, selectForeground, selectListBackground, checkboxBackground, checkboxBorder, checkboxForeground, tableColumnsBorder, tableOddRowsBackgroundColor, textLinkForeground, treeIndentGuidesStroke, widgetShadow, listFocusAndSelectionOutline, listFilterWidgetShadow, buttonSeparator } from 'vs/platform/theme/common/colorRegistry'; // {{SQL CARBON EDIT}} Added buttonSecondaryBorder, buttonDisabledBorder, buttonDisabledBackground, buttonDisabledForeground to import list import { isHighContrast } from 'vs/platform/theme/common/theme'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; +//export type styleFn = (colors: { [name: string]: Color | undefined }) => void; +export type styleFn = (colors: any) => void; + +export interface IThemable { + style: styleFn; +} + export interface IStyleOverrides { [color: string]: ColorIdentifier | undefined; } @@ -19,7 +25,7 @@ export interface IColorMapping { } export interface IComputedStyles { - [color: string]: Color | undefined; + [color: string]: string | Color | undefined; } export function computeStyles(theme: IColorTheme, styleMap: IColorMapping): IComputedStyles { @@ -27,7 +33,11 @@ export function computeStyles(theme: IColorTheme, styleMap: IColorMapping): ICom for (const key in styleMap) { const value = styleMap[key]; if (value) { - styles[key] = resolveColorValue(value, theme); + let color: any = resolveColorValue(value, theme); + if (color) { + color = color.toString(); + } + styles[key] = color; } } @@ -50,20 +60,6 @@ export function attachStyler(themeService: IThemeServic return themeService.onDidColorThemeChange(applyStyles); } -export interface IToggleStyleOverrides extends IStyleOverrides { - inputActiveOptionBorderColor?: ColorIdentifier; - inputActiveOptionForegroundColor?: ColorIdentifier; - inputActiveOptionBackgroundColor?: ColorIdentifier; -} - -export function attachToggleStyler(widget: IThemable, themeService: IThemeService, style?: IToggleStyleOverrides): IDisposable { - return attachStyler(themeService, { - inputActiveOptionBorder: style?.inputActiveOptionBorderColor || inputActiveOptionBorder, - inputActiveOptionForeground: style?.inputActiveOptionForegroundColor || inputActiveOptionForeground, - inputActiveOptionBackground: style?.inputActiveOptionBackgroundColor || inputActiveOptionBackground - } as IToggleStyleOverrides, widget); -} - export interface IBadgeStyleOverrides extends IStyleOverrides { badgeBackground?: ColorIdentifier; badgeForeground?: ColorIdentifier; @@ -238,55 +234,6 @@ export const defaultListStyles: IColorMapping = { inputValidationErrorBorder, }; -export interface IButtonStyleOverrides extends IStyleOverrides { - buttonForeground?: ColorIdentifier; - buttonSeparator?: ColorIdentifier; - buttonBackground?: ColorIdentifier; - buttonHoverBackground?: ColorIdentifier; - buttonSecondaryForeground?: ColorIdentifier; - buttonSecondaryBackground?: ColorIdentifier; - buttonSecondaryHoverBackground?: ColorIdentifier; - buttonBorder?: ColorIdentifier; - // {{SQL CARBON EDIT}} - buttonSecondaryBorder?: ColorIdentifier; - buttonDisabledForeground?: ColorIdentifier; - buttonDisabledBackground?: ColorIdentifier; - buttonDisabledBorder?: ColorIdentifier; -} - -// {{SQL CARBON EDIT}} -export const defaultButtonStyles: IButtonStyleOverrides = { - buttonForeground: buttonForeground, - buttonBackground: buttonBackground, - buttonHoverBackground: buttonHoverBackground, - buttonSecondaryForeground: buttonSecondaryForeground, - buttonSecondaryBackground: buttonSecondaryBackground, - buttonSecondaryHoverBackground: buttonSecondaryHoverBackground, - buttonBorder: buttonBorder, - buttonSecondaryBorder: buttonSecondaryBorder, - buttonDisabledBorder: buttonDisabledBorder, - buttonDisabledBackground: buttonDisabledBackground, - buttonDisabledForeground: buttonDisabledForeground -}; - -export function attachButtonStyler(widget: IThemable, themeService: IThemeService, style?: IButtonStyleOverrides): IDisposable { - return attachStyler(themeService, { - buttonForeground: style?.buttonForeground || buttonForeground, - buttonSeparator: style?.buttonSeparator || buttonSeparator, - buttonBackground: style?.buttonBackground || buttonBackground, - buttonHoverBackground: style?.buttonHoverBackground || buttonHoverBackground, - buttonSecondaryForeground: style?.buttonSecondaryForeground || buttonSecondaryForeground, - buttonSecondaryBackground: style?.buttonSecondaryBackground || buttonSecondaryBackground, - buttonSecondaryHoverBackground: style?.buttonSecondaryHoverBackground || buttonSecondaryHoverBackground, - buttonBorder: style?.buttonBorder || buttonBorder, - // {{SQL CARBON EDIT}} - buttonSecondaryBorder: style?.buttonSecondaryBorder || buttonSecondaryBorder, - buttonDisabledBorder: style?.buttonDisabledBorder || buttonDisabledBorder, - buttonDisabledBackground: style?.buttonDisabledBackground || buttonDisabledBackground, - buttonDisabledForeground: style?.buttonDisabledForeground || buttonDisabledForeground - } as IButtonStyleOverrides, widget); -} - export interface IKeybindingLabelStyleOverrides extends IStyleOverrides { keybindingLabelBackground?: ColorIdentifier; keybindingLabelForeground?: ColorIdentifier; @@ -369,7 +316,7 @@ export function attachMenuStyler(widget: IThemable, themeService: IThemeService, return attachStyler(themeService, { ...defaultMenuStyles, ...style }, widget); } -export interface IDialogStyleOverrides extends IButtonStyleOverrides { +export interface IDialogStyleOverrides { dialogForeground?: ColorIdentifier; dialogBackground?: ColorIdentifier; dialogShadow?: ColorIdentifier; diff --git a/src/sql/workbench/api/common/extHostModelViewTree.ts b/src/sql/workbench/api/common/extHostModelViewTree.ts index 5aea9d8f91..92bb69bbe0 100644 --- a/src/sql/workbench/api/common/extHostModelViewTree.ts +++ b/src/sql/workbench/api/common/extHostModelViewTree.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import { ExtHostModelViewTreeViewsShape, MainThreadModelViewShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; import { ITreeComponentItem } from 'sql/workbench/common/views'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; -import { IMainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { CheckboxUpdate, IMainContext } from 'vs/workbench/api/common/extHost.protocol'; import * as azdata from 'azdata'; import * as vsTreeExt from 'vs/workbench/api/common/extHostTreeViews'; import { Emitter } from 'vs/base/common/event'; @@ -93,6 +93,12 @@ export class ExtHostModelViewTreeViews implements ExtHostModelViewTreeViewsShape return Promise.resolve(undefined); } + $setFocus(treeViewId: string, treeItemHandle: string): void { + } + + $changeCheckboxState(treeViewId: string, checkboxUpdates: CheckboxUpdate[]): void { + } + private createExtHostTreeViewer(handle: number, id: string, dataProvider: azdata.TreeComponentDataProvider, extension: IExtensionDescription, logService: ILogService): ExtHostTreeView { const treeView = new ExtHostTreeView(handle, id, dataProvider, this._proxy, undefined, extension, logService); this.treeViews.set(`${handle}-${id}`, treeView); diff --git a/src/sql/workbench/api/common/sqlExtHost.protocol.ts b/src/sql/workbench/api/common/sqlExtHost.protocol.ts index 7ff9ed8653..dca78bc5fc 100644 --- a/src/sql/workbench/api/common/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/common/sqlExtHost.protocol.ts @@ -21,7 +21,7 @@ import { ISerializationManagerDetails, IErrorDialogOptions } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { IUndoStopOptions } from 'vs/workbench/api/common/extHost.protocol'; +import { CheckboxUpdate, IUndoStopOptions } from 'vs/workbench/api/common/extHost.protocol'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor'; import { ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry'; @@ -811,6 +811,8 @@ export interface ExtHostModelViewTreeViewsShape { $setVisible(treeViewId: string, visible: boolean): void; $hasResolve(treeViewId: string): Promise; $resolve(treeViewId: string, treeItemHandle: string): Promise; + $setFocus(treeViewId: string, treeItemHandle: string): void; + $changeCheckboxState(treeViewId: string, checkboxUpdates: CheckboxUpdate[]): void; } export interface ExtHostBackgroundTaskManagementShape { diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 9f60cf66b3..e9ed46df23 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -120,7 +120,7 @@ export enum AlertType { } export enum FrequencyTypes { - Unknown, + Unknown = 0, OneTime = 1 << 1, Daily = 1 << 2, Weekly = 1 << 3, diff --git a/src/sql/workbench/browser/actions/layoutActions.ts b/src/sql/workbench/browser/actions/layoutActions.ts index 5dbc530b37..45f674cfd7 100644 --- a/src/sql/workbench/browser/actions/layoutActions.ts +++ b/src/sql/workbench/browser/actions/layoutActions.ts @@ -3,39 +3,43 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Action } from 'vs/base/common/actions'; +import { Action2 } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingRule } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; // --- Toggle View with Command -export abstract 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 - ) { - super(id, label, cssClass); +export abstract class ToggleViewAction extends Action2 { + private viewId: string; + constructor(id: string, labelOrg: string, label: string, keybinding?: Omit) { + super({ + id: id, + title: { value: label, original: labelOrg }, + category: 'View', + f1: true, + keybinding: keybinding, + }); + this.viewId = id; } - override async run(): Promise { - const focusedViewId = FocusedViewContext.getValue(this.contextKeyService); + run(accessor: ServicesAccessor): void { + const contextKeyService = accessor.get(IContextKeyService); + const viewDescriptorService = accessor.get(IViewDescriptorService); + const viewsService = accessor.get(IViewsService); + const layoutService = accessor.get(IWorkbenchLayoutService); + const focusedViewId = FocusedViewContext.getValue(contextKeyService); if (focusedViewId === this.viewId) { - if (this.viewDescriptorService.getViewLocationById(this.viewId) === ViewContainerLocation.Sidebar) { - this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART); + if (viewDescriptorService.getViewLocationById(this.viewId) === ViewContainerLocation.Sidebar) { + layoutService.setPartHidden(true, Parts.SIDEBAR_PART); } else { - this.layoutService.setPartHidden(true, Parts.PANEL_PART); + layoutService.setPartHidden(true, Parts.PANEL_PART); } } else { - this.viewsService.openView(this.viewId, true); + viewsService.openView(this.viewId, true); } } } diff --git a/src/sql/workbench/browser/designer/designer.ts b/src/sql/workbench/browser/designer/designer.ts index 041b23b51d..6129812642 100644 --- a/src/sql/workbench/browser/designer/designer.ts +++ b/src/sql/workbench/browser/designer/designer.ts @@ -17,8 +17,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IInputBoxStyles, InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import 'vs/css!./media/designer'; import { ITableStyles } from 'sql/base/browser/ui/table/interfaces'; -import { IThemable } from 'vs/base/common/styler'; -import { Checkbox, ICheckboxStyles } from 'sql/base/browser/ui/checkbox/checkbox'; +import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox'; import { Table } from 'sql/base/browser/ui/table/table'; import { ISelectBoxStyles, SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; @@ -27,7 +26,6 @@ import { TableCellEditorFactory } from 'sql/base/browser/ui/table/tableCellEdito import { CheckBoxColumn } from 'sql/base/browser/ui/table/plugins/checkboxColumn.plugin'; import { DesignerTabPanelView } from 'sql/workbench/browser/designer/designerTabPanelView'; import { DesignerPropertiesPane } from 'sql/workbench/browser/designer/designerPropertiesPane'; -import { Button, IButtonStyles } from 'sql/base/browser/ui/button/button'; import { ButtonColumn } from 'sql/base/browser/ui/table/plugins/buttonColumn.plugin'; import { Codicon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; @@ -56,14 +54,15 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { defaultCheckboxStyles } from 'sql/platform/theme/browser/defaultStyles'; export interface IDesignerStyle { tabbedPanelStyles?: ITabbedPanelStyles; inputBoxStyles?: IInputBoxStyles; tableStyles?: ITableStyles; selectBoxStyles?: ISelectBoxStyles; - checkboxStyles?: ICheckboxStyles; - buttonStyles?: IButtonStyles; dropdownStyles?: IListStyles & IInputBoxStyles & IDropdownStyles; paneSeparator?: Color; groupHeaderBackground?: Color; @@ -82,7 +81,7 @@ interface DesignerTableCellContext { const ScriptTabId = 'scripts'; const IssuesTabId = 'issues'; -export class Designer extends Disposable implements IThemable { +export class Designer extends Disposable { private _loadingSpinner: LoadingSpinner; private _horizontalSplitViewContainer: HTMLElement; private _verticalSplitViewContainer: HTMLElement; @@ -217,18 +216,15 @@ export class Designer extends Disposable implements IThemable { }, this._instantiationService); } - private styleComponent(component: TabbedPanel | InputBox | Checkbox | Table | SelectBox | Button | Dropdown): void { + private styleComponent(component: TabbedPanel | InputBox | Checkbox | Table | SelectBox | Dropdown): void { if (component instanceof InputBox) { component.style(this._styles.inputBoxStyles); } else if (component instanceof Checkbox) { - component.style(this._styles.checkboxStyles); } else if (component instanceof TabbedPanel) { component.style(this._styles.tabbedPanelStyles); } else if (component instanceof Table) { this.removeTableSelectionStyles(); component.style(this._styles.tableStyles); - } else if (component instanceof Button) { - component.style(this._styles.buttonStyles); } else if (component instanceof Dropdown) { component.style(this._styles.dropdownStyles); } else { @@ -256,8 +252,10 @@ export class Designer extends Disposable implements IThemable { public style(styles: IDesignerStyle): void { this._styles = styles; this._componentMap.forEach((value, key, map) => { - if (value.component.style) { - this.styleComponent(value.component); + if (!(value.component instanceof Checkbox)) { + if (value.component.style) { + this.styleComponent(value.component); + } } }); this._propertiesPane.componentMap.forEach((value) => { @@ -765,7 +763,8 @@ export class Designer extends Disposable implements IThemable { const input = new InputBox(inputContainer, this._contextViewProvider, { ariaLabel: inputProperties.title, type: inputProperties.inputType, - ariaDescription: componentDefinition.description + ariaDescription: componentDefinition.description, + inputBoxStyles: defaultInputBoxStyles }); input.onLoseFocus((args) => { if (args.hasChanged) { @@ -829,7 +828,7 @@ export class Designer extends Disposable implements IThemable { container.appendChild(DOM.$('')).appendChild(DOM.$('span.component-label')).innerText = componentDefinition.componentProperties?.title ?? ''; const checkboxContainer = container.appendChild(DOM.$('')); const checkboxProperties = componentDefinition.componentProperties as CheckBoxProperties; - const checkbox = new Checkbox(checkboxContainer, { label: '', ariaLabel: checkboxProperties.title, ariaDescription: componentDefinition.description }); + const checkbox = new Checkbox(checkboxContainer, { ...defaultCheckboxStyles, label: '', ariaLabel: checkboxProperties.title, ariaDescription: componentDefinition.description }); checkbox.onChange((newValue) => { this.handleEdit({ type: DesignerEditType.Update, path: propertyPath, value: newValue, source: view }); }); @@ -876,7 +875,7 @@ export class Designer extends Disposable implements IThemable { const moveRowsPlugin = new RowMoveManager({ cancelEditOnDrag: true, id: 'moveRow', - iconCssClass: Codicon.grabber.classNames, + iconCssClass: ThemeIcon.asClassName(Codicon.grabber), name: localize('designer.moveRowText', 'Move'), width: 50, resizable: true, @@ -946,7 +945,7 @@ export class Designer extends Disposable implements IThemable { const removeText = localize('designer.removeRowText', "Remove"); const deleteRowColumn = new ButtonColumn({ id: 'deleteRow', - iconCssClass: Codicon.trash.classNames, + iconCssClass: ThemeIcon.asClassName(Codicon.trash), name: removeText, title: removeText, width: 60, @@ -978,7 +977,7 @@ export class Designer extends Disposable implements IThemable { const moreActionsText = localize('designer.actions', "More Actions"); const actionsColumn = new ButtonColumn({ id: 'actions', - iconCssClass: Codicon.ellipsis.classNames, + iconCssClass: ThemeIcon.asClassName(Codicon.ellipsis), name: moreActionsText, title: moreActionsText, width: 100, diff --git a/src/sql/workbench/browser/designer/designerIssuesTabPanelView.ts b/src/sql/workbench/browser/designer/designerIssuesTabPanelView.ts index ff2261c116..5608dd9d9f 100644 --- a/src/sql/workbench/browser/designer/designerIssuesTabPanelView.ts +++ b/src/sql/workbench/browser/designer/designerIssuesTabPanelView.ts @@ -12,11 +12,12 @@ import { IListAccessibilityProvider, List } from 'vs/base/browser/ui/list/listWi import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { localize } from 'vs/nls'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { problemsErrorIconForeground, problemsInfoIconForeground, problemsWarningIconForeground } from 'vs/platform/theme/common/colorRegistry'; import { Codicon } from 'vs/base/common/codicons'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Link } from 'vs/platform/opener/browser/link'; +import { ThemeIcon } from 'vs/base/common/themables'; export class DesignerIssuesTabPanelView extends Disposable implements IPanelView { private _container: HTMLElement; @@ -116,13 +117,13 @@ class TableFilterListRenderer implements IListRenderer { return this._sql.resolve(); } + public override async resolve(options?: IEditorOptions): Promise { + return this._sql.resolve(); + } + public getEncoding(): string | undefined { return this._sql.getEncoding(); } public override getName(): string { return this._sql.getName(); } public get hasAssociatedFilePath(): boolean { return this._sql.model.hasAssociatedFilePath; } diff --git a/src/sql/workbench/browser/editData/editDataResultsInput.ts b/src/sql/workbench/browser/editData/editDataResultsInput.ts index f2c5156853..b0f13a47a8 100644 --- a/src/sql/workbench/browser/editData/editDataResultsInput.ts +++ b/src/sql/workbench/browser/editData/editDataResultsInput.ts @@ -6,6 +6,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { IEditorModel, IEditorOptions } from 'vs/platform/editor/common/editor'; export interface IGridPanel { readonly onRefreshComplete: Promise; @@ -57,7 +58,7 @@ export class EditDataResultsInput extends EditorInput { return false; } - override resolve(refresh?: boolean): Promise { + override async resolve(options?: IEditorOptions): Promise { return Promise.resolve(null); } diff --git a/src/sql/workbench/browser/modal/dialogHelper.ts b/src/sql/workbench/browser/modal/dialogHelper.ts index c509b0f15b..8532652cfc 100644 --- a/src/sql/workbench/browser/modal/dialogHelper.ts +++ b/src/sql/workbench/browser/modal/dialogHelper.ts @@ -10,6 +10,7 @@ import * as types from 'vs/base/common/types'; import * as azdata from 'azdata'; import { wrapStringWithNewLine } from 'sql/workbench/common/sqlWorkbenchUtils'; import { RequiredIndicatorClassName } from 'sql/base/browser/ui/label/label'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export function appendRow(container: HTMLElement, label: string, labelClass: string, cellContainerClass: string, rowContainerClass?: string | Array, showRequiredIndicator: boolean = false, title?: string, titleMaxWidth?: number): HTMLElement { let rowContainer = append(container, $('tr')); @@ -41,7 +42,7 @@ export function appendRowLink(container: HTMLElement, label: string, labelClass: let rowContainer = append(container, $('tr')); append(append(rowContainer, $(`td.${labelClass}`)), $('div')).innerText = label; let buttonContainer = append(append(rowContainer, $(`td.${cellContainerClass}`)), $('div')); - let rowButton = new Button(buttonContainer); + let rowButton = new Button(buttonContainer, defaultButtonStyles); return rowButton.element; } diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index bb963ee732..7117f51f8e 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -17,7 +17,6 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { mixin } from 'vs/base/common/objects'; -import { IThemable } from 'vs/base/common/styler'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; @@ -26,10 +25,12 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ILogService } from 'vs/platform/log/common/log'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; +import { IThemable } from 'sql/platform/theme/common/vsstyler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Emitter } from 'vs/base/common/event'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { defaultButtonStyles, getButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; export enum MessageLevel { Error = 0, @@ -269,10 +270,13 @@ export abstract class Modal extends Disposable implements IThemable { this._modalHeaderSection = DOM.append(this._modalContent, DOM.$('.modal-header')); if (this._modalOptions.hasBackButton) { const container = DOM.append(this._modalHeaderSection, DOM.$('.modal-go-back')); - this._backButton = new Button(container, { secondary: true }); - this._backButton.icon = { - id: 'backButtonIcon' - }; + this._backButton = new Button(container, + getButtonStyles({ + buttonBackground: SIDE_BAR_BACKGROUND, + buttonHoverBackground: SIDE_BAR_BACKGROUND + }) + ); + this._backButton.icon = 'backButtonIcon'; this._backButton.title = localize('modal.back', "Back"); } @@ -291,31 +295,21 @@ export abstract class Modal extends Disposable implements IThemable { this._messageIcon = DOM.append(headerContainer, DOM.$('.dialog-message-icon')); this._messageSeverity = DOM.append(headerContainer, DOM.$('.dialog-message-severity')); this._detailsButtonContainer = DOM.append(headerContainer, DOM.$('.dialog-message-button')); - this._toggleMessageDetailButton = new Button(this._detailsButtonContainer); - this._toggleMessageDetailButton.icon = { - id: 'message-details-icon' - }; + this._toggleMessageDetailButton = new Button(this._detailsButtonContainer, defaultButtonStyles); + this._toggleMessageDetailButton.icon = 'message-details-icon'; this._toggleMessageDetailButton.label = SHOW_DETAILS_TEXT; this._register(this._toggleMessageDetailButton.onDidClick(() => this.toggleMessageDetail())); const copyMessageButtonContainer = DOM.append(headerContainer, DOM.$('.dialog-message-button')); - this._copyMessageButton = new Button(copyMessageButtonContainer); - this._copyMessageButton.icon = { - id: 'copy-message-icon' - }; + this._copyMessageButton = new Button(copyMessageButtonContainer, defaultButtonStyles); + this._copyMessageButton.icon = 'copy-message-icon'; this._copyMessageButton.label = COPY_TEXT; this._register(this._copyMessageButton.onDidClick(() => this._clipboardService.writeText(this.getTextForClipboard()))); const closeMessageButtonContainer = DOM.append(headerContainer, DOM.$('.dialog-message-button')); - this._closeMessageButton = new Button(closeMessageButtonContainer); - this._closeMessageButton.icon = { - id: 'close-message-icon' - }; + this._closeMessageButton = new Button(closeMessageButtonContainer, defaultButtonStyles); + this._closeMessageButton.icon = 'close-message-icon'; this._closeMessageButton.label = CLOSE_TEXT; this._register(this._closeMessageButton.onDidClick(() => this.setError(undefined))); - this._register(attachButtonStyler(this._toggleMessageDetailButton, this._themeService)); - this._register(attachButtonStyler(this._copyMessageButton, this._themeService)); - this._register(attachButtonStyler(this._closeMessageButton, this._themeService)); - this._messageBody = DOM.append(this._messageElement, DOM.$('.dialog-message-body')); this._messageSummary = DOM.append(this._messageBody, DOM.$('.dialog-message-summary')); this._register(DOM.addDisposableListener(this._messageSummary, DOM.EventType.CLICK, () => this.toggleMessageDetail())); @@ -538,7 +532,7 @@ export abstract class Modal extends Disposable implements IThemable { */ protected addFooterButton(label: string, onSelect: () => void, position: 'left' | 'right' = 'right', isSecondary: boolean = false, index?: number): Button { let footerButton = DOM.$('.footer-button'); - let button = this._register(new Button(footerButton, { secondary: isSecondary })); + let button = this._register(new Button(footerButton, { secondary: isSecondary, ...defaultButtonStyles })); button.label = label; button.onDidClick(() => onSelect()); // @todo this should be registered to dispose but that brakes some dialogs const container = position === 'left' ? this._leftFooter! : this._rightFooter!; @@ -549,7 +543,6 @@ export abstract class Modal extends Disposable implements IThemable { } else { DOM.append(container, footerButton); } - attachButtonStyler(button, this._themeService); this._footerButtons.push(button); return button; } diff --git a/src/sql/workbench/browser/modal/optionsDialog.ts b/src/sql/workbench/browser/modal/optionsDialog.ts index 0f5c3975c0..6c8ea4433c 100644 --- a/src/sql/workbench/browser/modal/optionsDialog.ts +++ b/src/sql/workbench/browser/modal/optionsDialog.ts @@ -12,12 +12,12 @@ import * as OptionsDialogHelper from './optionsDialogHelper'; import * as azdata from 'azdata'; import { Event, Emitter } from 'vs/base/common/event'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +//import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { localize } from 'vs/nls'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import * as styler from 'vs/platform/theme/common/styler'; +//import * as styler from 'sql/platform/theme/common/vsstyler'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -75,13 +75,16 @@ export class OptionsDialog extends Modal { attachModalDialogStyler(this, this._themeService); if (this.backButton) { this.backButton.onDidClick(() => this.cancel()); - styler.attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }); + //styler.attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }); } - let okButton = this.addFooterButton(localize('optionsDialog.ok', "OK"), () => this.ok()); - let closeButton = this.addFooterButton(this.options.cancelLabel || localize('optionsDialog.cancel', "Cancel"), () => this.cancel(), 'right', true); + // {{SQL CARBON TODO}} - theming + this.addFooterButton(localize('optionsDialog.ok', "OK"), () => this.ok()); + this.addFooterButton(this.options.cancelLabel || localize('optionsDialog.cancel', "Cancel"), () => this.cancel(), 'right', true); + // let okButton = this.addFooterButton(localize('optionsDialog.ok', "OK"), () => this.ok()); + // let closeButton = this.addFooterButton(this.options.cancelLabel || localize('optionsDialog.cancel', "Cancel"), () => this.cancel(), 'right', true); // Theme styler - styler.attachButtonStyler(okButton, this._themeService); - styler.attachButtonStyler(closeButton, this._themeService); + //styler.attachButtonStyler(okButton, this._themeService); + //styler.attachButtonStyler(closeButton, this._themeService); this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e))); this.updateTheme(this._themeService.getColorTheme()); } @@ -135,17 +138,18 @@ export class OptionsDialog extends Modal { private registerStyling(): void { // Theme styler for (let optionName in this._optionElements) { - let widget: Widget = this._optionElements[optionName].optionWidget; + // {{SQL CARBON TODO}} - styling + //let widget: Widget = this._optionElements[optionName].optionWidget; let option = this._optionElements[optionName].option; switch (option.valueType) { case ServiceOptionType.category: case ServiceOptionType.boolean: - this.disposableStore.add(styler.attachSelectBoxStyler(widget, this._themeService)); + //this.disposableStore.add(styler.attachSelectBoxStyler(widget, this._themeService)); break; case ServiceOptionType.string: case ServiceOptionType.password: case ServiceOptionType.number: - this.disposableStore.add(styler.attachInputBoxStyler(widget, this._themeService)); + //this.disposableStore.add(styler.attachInputBoxStyler(widget, this._themeService)); } } } diff --git a/src/sql/workbench/browser/modal/optionsDialogHelper.ts b/src/sql/workbench/browser/modal/optionsDialogHelper.ts index 26f15274bd..d1f5440152 100644 --- a/src/sql/workbench/browser/modal/optionsDialogHelper.ts +++ b/src/sql/workbench/browser/modal/optionsDialogHelper.ts @@ -12,6 +12,7 @@ import * as types from 'vs/base/common/types'; import * as azdata from 'azdata'; import { localize } from 'vs/nls'; import { ServiceOptionType } from 'sql/platform/connection/common/interfaces'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface IOptionElement { optionWidget: any; @@ -42,7 +43,8 @@ export function createOptionElement(option: azdata.ServiceOption, rowContainer: } }, ariaLabel: option.displayName, - placeholder: option.placeholder + placeholder: option.placeholder, + inputBoxStyles: defaultInputBoxStyles }, option.name); optionWidget.value = optionValue; inputElement = findElement(rowContainer, 'input'); @@ -56,7 +58,8 @@ export function createOptionElement(option: azdata.ServiceOption, rowContainer: validation: (value: string) => (!value && option.isRequired) ? ({ type: MessageType.ERROR, content: option.displayName + missingErrorMessage }) : null }, ariaLabel: option.displayName, - placeholder: option.placeholder + placeholder: option.placeholder, + inputBoxStyles: defaultInputBoxStyles }, option.name); optionWidget.value = optionValue; if (option.valueType === ServiceOptionType.password) { diff --git a/src/sql/workbench/browser/modelComponents/button.component.ts b/src/sql/workbench/browser/modelComponents/button.component.ts index b5e456b4dd..1328dc1ec5 100644 --- a/src/sql/workbench/browser/modelComponents/button.component.ts +++ b/src/sql/workbench/browser/modelComponents/button.component.ts @@ -10,13 +10,11 @@ import { convertSize } from 'sql/base/browser/dom'; import { Button } from 'sql/base/browser/ui/button/button'; import { InfoButton } from 'sql/base/browser/ui/infoButton/infoButton'; import { ComponentEventType, IComponent, IComponentDescriptor, IModelStore } from 'sql/platform/dashboard/browser/interfaces'; -import { attachInfoButtonStyler } from 'sql/platform/theme/common/styler'; import { ComponentWithIconBase } from 'sql/workbench/browser/modelComponents/componentWithIconBase'; import { createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUtils'; -import { IDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultInfoButtonStyles } from 'sql/platform/theme/browser/defaultStyles'; enum ButtonType { File = 'File', @@ -46,7 +44,6 @@ export default class ButtonComponent extends ComponentWithIconBase ChangeDetectorRef)) changeRef: ChangeDetectorRef, - @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @Inject(forwardRef(() => ElementRef)) el: ElementRef, @Inject(ILogService) logService: ILogService ) { @@ -77,9 +73,9 @@ export default class ButtonComponent extends ComponentWithIconBase { if (this._fileInputContainer) { const self = this; @@ -176,27 +171,10 @@ export default class ButtonComponent extends ComponentWithIconBase ChangeDetectorRef)) changeRef: ChangeDetectorRef, - @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @Inject(ILogService) logService: ILogService, @Inject(forwardRef(() => ElementRef)) el: ElementRef) { super(changeRef, el, logService); @@ -43,6 +41,7 @@ export default class CheckBoxComponent extends ComponentBase !this.required || this.checked); } this.baseInit(); diff --git a/src/sql/workbench/browser/modelComponents/diffeditor.component.ts b/src/sql/workbench/browser/modelComponents/diffeditor.component.ts index edf2585cde..4a35aab6ff 100644 --- a/src/sql/workbench/browser/modelComponents/diffeditor.component.ts +++ b/src/sql/workbench/browser/modelComponents/diffeditor.component.ts @@ -153,9 +153,10 @@ export default class DiffEditorComponent extends ComponentBase ElementRef)) el: ElementRef, @Inject(IInstantiationService) private _instantiationService: IInstantiationService, @Inject(IModelService) private _modelService: IModelService, - @Inject(ILanguageService) private _languageService: ILanguageService, + // @Inject(ILanguageService) private _languageService: ILanguageService, @Inject(ILogService) private _logService: ILogService, @Inject(IEditorService) private readonly editorService: IEditorService, @Inject(ILogService) logService: ILogService @@ -141,8 +141,9 @@ export default class EditorComponent extends ComponentBase { + public override async resolve(options?: IEditorOptions): Promise { return undefined; } diff --git a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts index 7888c14945..46aeac29bc 100644 --- a/src/sql/workbench/browser/modelComponents/queryTextEditor.ts +++ b/src/sql/workbench/browser/modelComponents/queryTextEditor.ts @@ -57,7 +57,7 @@ export class QueryTextEditor extends AbstractTextCodeEditor imple return this._tabs; } - override onItemsUpdated(): void { + protected override onItemsUpdated(): void { if (this.items.length === 0) { this._itemIndexToProcess = 0; this._tabs = []; @@ -121,7 +121,7 @@ export default class TabbedPanelComponent extends ContainerBase imple } } - override onItemLayoutUpdated(item: ItemDescriptor): void { + protected override onItemLayoutUpdated(item: ItemDescriptor): void { this._panel.updateTab(item.config.id, { title: item.config.title, iconClass: item.config.icon ? createIconCssClass(item.config.icon) : undefined }); } diff --git a/src/sql/workbench/browser/modelComponents/table.component.ts b/src/sql/workbench/browser/modelComponents/table.component.ts index 60e065fb72..7b69d8260a 100644 --- a/src/sql/workbench/browser/modelComponents/table.component.ts +++ b/src/sql/workbench/browser/modelComponents/table.component.ts @@ -41,6 +41,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; import { deepClone, equals } from 'vs/base/common/objects'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export enum ColumnSizingMode { ForceFit = 0, // all columns will be sized to fit in viewable space, no horiz scroll bar @@ -606,7 +607,7 @@ export default class TableComponent extends ComponentBase(this.contextViewService); + const filterPlugin = new HeaderFilter({ buttonStyles: defaultButtonStyles }, this.contextViewService); this._register(attachTableFilterStyler(filterPlugin, this.themeService)); this._filterPlugin = filterPlugin; this._filterPlugin.onFilterApplied.subscribe((e, args) => { diff --git a/src/sql/workbench/browser/modelComponents/tree.component.ts b/src/sql/workbench/browser/modelComponents/tree.component.ts index 544847890a..49f7cd4743 100644 --- a/src/sql/workbench/browser/modelComponents/tree.component.ts +++ b/src/sql/workbench/browser/modelComponents/tree.component.ts @@ -15,7 +15,7 @@ import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBa import { Tree } from 'sql/base/parts/tree/browser/treeImpl'; import { TreeComponentRenderer } from 'sql/workbench/browser/modelComponents/treeComponentRenderer'; import { TreeComponentDataSource } from 'sql/workbench/browser/modelComponents/treeDataSource'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'sql/base/parts/tree/browser/treeDefaults'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITreeComponentItem } from 'sql/workbench/common/views'; diff --git a/src/sql/workbench/browser/modelComponents/treeViewDataProvider.ts b/src/sql/workbench/browser/modelComponents/treeViewDataProvider.ts index 570fa511e1..7291d3c1ce 100644 --- a/src/sql/workbench/browser/modelComponents/treeViewDataProvider.ts +++ b/src/sql/workbench/browser/modelComponents/treeViewDataProvider.ts @@ -3,12 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// eslint-disable-next-line code-import-patterns import { ExtHostModelViewTreeViewsShape } from 'sql/workbench/api/common/sqlExtHost.protocol'; -// eslint-disable-next-line code-import-patterns import { IModelViewTreeViewDataProvider, ITreeComponentItem } from 'sql/workbench/common/views'; import { INotificationService } from 'vs/platform/notification/common/notification'; -// eslint-disable-next-line code-import-patterns import * as vsTreeView from 'vs/workbench/api/browser/mainThreadTreeViews'; import { ResolvableTreeItem } from 'vs/workbench/common/views'; import { deepClone } from 'vs/base/common/objects'; diff --git a/src/sql/workbench/browser/parts/activitybar/activitybarActions.ts b/src/sql/workbench/browser/parts/activitybar/activitybarActions.ts index df43083786..2d84551497 100644 --- a/src/sql/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/sql/workbench/browser/parts/activitybar/activitybarActions.ts @@ -29,7 +29,8 @@ export class AccountsActionViewItem extends ActivityActionViewItem { @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService, ) { - super(action, { draggable: false, colors, icon: true, hoverOptions: hoverOptions }, themeService, hoverService, configurationService, keybindingService); + super(action, { draggable: false, colors, icon: true, hoverOptions: hoverOptions }, + (/*badgesEnabledForCompositeId*/) => false, themeService, hoverService, configurationService, keybindingService); } override render(container: HTMLElement): void { diff --git a/src/sql/workbench/browser/ui/infoBox/infoBox.ts b/src/sql/workbench/browser/ui/infoBox/infoBox.ts index 2cff499cdf..95b866db68 100644 --- a/src/sql/workbench/browser/ui/infoBox/infoBox.ts +++ b/src/sql/workbench/browser/ui/infoBox/infoBox.ts @@ -7,7 +7,6 @@ import 'vs/css!./media/infoBox'; import * as azdata from 'azdata'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { alert, status } from 'vs/base/browser/ui/aria/aria'; -import { IThemable } from 'vs/base/common/styler'; import { Color } from 'vs/base/common/color'; import * as DOM from 'vs/base/browser/dom'; import { Event, Emitter } from 'vs/base/common/event'; @@ -16,6 +15,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ILogService } from 'vs/platform/log/common/log'; +import { ThemeIcon } from 'vs/base/common/themables'; export interface IInfoBoxStyles { informationBackground?: Color; @@ -35,7 +35,7 @@ export interface InfoBoxOptions { clickableButtonAriaLabel?: string; } -export class InfoBox extends Disposable implements IThemable { +export class InfoBox extends Disposable { private _imageElement: HTMLDivElement; private _textElement: HTMLDivElement; private _infoBoxElement: HTMLDivElement; @@ -72,7 +72,7 @@ export class InfoBox extends Disposable implements IThemable { this._infoBoxElement.appendChild(this._imageElement); this._infoBoxElement.appendChild(this._textElement); this._clickableIndicator = DOM.$('a'); - this._clickableIndicator.classList.add('infobox-clickable-arrow', ...Codicon.arrowRight.classNamesArray); + this._clickableIndicator.classList.add('infobox-clickable-arrow', ...ThemeIcon.asClassNameArray(Codicon.arrowRight)); this._infoBoxElement.appendChild(this._clickableIndicator); if (options) { diff --git a/src/sql/workbench/common/editor/query/queryEditorInput.ts b/src/sql/workbench/common/editor/query/queryEditorInput.ts index 85c187e798..15802519ca 100644 --- a/src/sql/workbench/common/editor/query/queryEditorInput.ts +++ b/src/sql/workbench/common/editor/query/queryEditorInput.ts @@ -185,7 +185,7 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab })); this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectedKeys.indexOf('queryEditor') > -1) { + if (e.affectedKeys.has('queryEditor')) { this._onDidChangeLabel.fire(); } })); diff --git a/src/sql/workbench/common/editor/query/queryResultsInput.ts b/src/sql/workbench/common/editor/query/queryResultsInput.ts index 7e326d444a..e025789b58 100644 --- a/src/sql/workbench/common/editor/query/queryResultsInput.ts +++ b/src/sql/workbench/common/editor/query/queryResultsInput.ts @@ -13,6 +13,7 @@ import { GridPanelState } from 'sql/workbench/common/editor/query/gridTableState import { QueryModelViewState } from 'sql/workbench/common/editor/query/modelViewState'; import { URI } from 'vs/base/common/uri'; import { ExecutionPlanState } from 'sql/workbench/common/editor/query/executionPlanState'; +import { IEditorModel, IEditorOptions } from 'vs/platform/editor/common/editor'; export class ResultsViewState { public readonly gridPanelState: GridPanelState = new GridPanelState(); @@ -68,7 +69,7 @@ export class QueryResultsInput extends EditorInput { return false; } - override resolve(refresh?: boolean): Promise { + override async resolve(options?: IEditorOptions): Promise { return Promise.resolve(null); } diff --git a/src/sql/workbench/common/styler.ts b/src/sql/workbench/common/styler.ts index fc3c419257..e445222337 100644 --- a/src/sql/workbench/common/styler.ts +++ b/src/sql/workbench/common/styler.ts @@ -7,8 +7,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as cr from 'vs/platform/theme/common/colorRegistry'; import * as sqlcr from 'sql/platform/theme/common/colorRegistry'; -import { IThemable } from 'vs/base/common/styler'; -import { attachStyler, IStyleOverrides } from 'vs/platform/theme/common/styler'; +import { IThemable, attachStyler, IStyleOverrides } from 'sql/platform/theme/common/vsstyler'; import { SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND diff --git a/src/sql/workbench/contrib/assessment/browser/asmtResultsView.component.ts b/src/sql/workbench/contrib/assessment/browser/asmtResultsView.component.ts index 4095b1e448..109d949649 100644 --- a/src/sql/workbench/contrib/assessment/browser/asmtResultsView.component.ts +++ b/src/sql/workbench/contrib/assessment/browser/asmtResultsView.component.ts @@ -51,6 +51,7 @@ import { DASHBOARD_BORDER } from 'sql/workbench/common/theme'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { defaultButtonStyles, defaultListStyles } from 'vs/platform/theme/browser/defaultStyles'; export const ASMTRESULTSVIEW_SELECTOR: string = 'asmt-results-view-component'; export const ROW_HEIGHT: number = 25; @@ -325,7 +326,7 @@ export class AsmtResultsViewComponent extends TabChild implements IAssessmentCom columnDef.formatter = (row, cell, value, columnDef, dataContext) => this.detailSelectionFormatter(row, cell, value, columnDef, dataContext as ExtendedItem); columns.unshift(columnDef); - let filterPlugin = new HeaderFilter(this._contextViewService); + let filterPlugin = new HeaderFilter({ buttonStyles: defaultButtonStyles }, this._contextViewService); this._register(attachTableFilterStyler(filterPlugin, this._themeService)); this.filterPlugin = filterPlugin; this.filterPlugin.onFilterApplied.subscribe((e, args) => { @@ -596,8 +597,10 @@ export class AsmtResultsViewComponent extends TabChild implements IAssessmentCom private _updateStyles(theme: IColorTheme): void { this.actionBarContainer.nativeElement.style.borderTopColor = theme.getColor(DASHBOARD_BORDER, true).toString(); + // {{SQL CARBON TODO}} - do defaultListStyles work here? let tableStyle: ITableStyles = { - tableHeaderBackground: theme.getColor(themeColors.PANEL_BACKGROUND) + tableHeaderBackground: theme.getColor(themeColors.PANEL_BACKGROUND), + ...defaultListStyles }; this._table.style(tableStyle); const rowExclSelector = '.asmtview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row'; diff --git a/src/sql/workbench/contrib/assessment/test/browser/asmtActions.test.ts b/src/sql/workbench/contrib/assessment/test/browser/asmtActions.test.ts index 7a0c93e671..dbfba8a049 100644 --- a/src/sql/workbench/contrib/assessment/test/browser/asmtActions.test.ts +++ b/src/sql/workbench/contrib/assessment/test/browser/asmtActions.test.ts @@ -251,7 +251,8 @@ suite('Assessment Actions', () => { resource: fileUri, size: 42, readonly: false, - children: [] + children: [], + locked: false }); }); diff --git a/src/sql/workbench/contrib/backup/browser/backup.component.ts b/src/sql/workbench/contrib/backup/browser/backup.component.ts index 0985d00f11..256a1bdbb1 100644 --- a/src/sql/workbench/contrib/backup/browser/backup.component.ts +++ b/src/sql/workbench/contrib/backup/browser/backup.component.ts @@ -11,7 +11,7 @@ import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox'; import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { ListBox } from 'sql/base/browser/ui/listBox/listBox'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; -import { attachListBoxStyler, attachInputBoxStyler, attachSelectBoxStyler, attachCheckboxStyler } from 'sql/platform/theme/common/styler'; +import { attachListBoxStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'sql/platform/theme/common/styler'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import * as BackupConstants from 'sql/workbench/contrib/backup/common/constants'; import { IBackupService, TaskExecutionMode } from 'sql/platform/backup/common/backupService'; @@ -34,10 +34,11 @@ import { AngularDisposable } from 'sql/base/browser/lifecycle'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { fileFiltersSet } from 'sql/workbench/services/restore/common/constants'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { DatabaseEngineEdition } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IBackupRestoreUrlBrowserDialogService } from 'sql/workbench/services/backupRestoreUrlBrowser/common/urlBrowserDialogService'; +import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultCheckboxStyles } from 'sql/platform/theme/browser/defaultStyles'; export const BACKUP_SELECTOR: string = 'backup-component'; @@ -236,7 +237,8 @@ export class BackupComponent extends AngularDisposable { this.recoveryBox = this._register(new InputBox(this.recoveryModelElement!.nativeElement, this.contextViewService, { placeholder: this.recoveryModel, - ariaLabel: LocalizedStrings.RECOVERY_MODEL + ariaLabel: LocalizedStrings.RECOVERY_MODEL, + inputBoxStyles: defaultInputBoxStyles })); // Set backup type this.backupTypeSelectBox = this._register(new SelectBox([], '', this.contextViewService, undefined, { ariaLabel: this.localizedStrings.BACKUP_TYPE })); @@ -244,6 +246,7 @@ export class BackupComponent extends AngularDisposable { // Set copy-only check box this.copyOnlyCheckBox = this._register(new Checkbox(this.copyOnlyElement!.nativeElement, { + ...defaultCheckboxStyles, label: LocalizedStrings.COPY_ONLY, checked: false, onChange: (viaKeyboard) => { }, @@ -252,6 +255,7 @@ export class BackupComponent extends AngularDisposable { // Set to url check box this.toUrlCheckBox = this._register(new Checkbox(this.toUrlElement!.nativeElement, { + ...defaultCheckboxStyles, label: LocalizedStrings.TO_URL, checked: false, onChange: () => this.onChangeToUrl(), @@ -260,6 +264,7 @@ export class BackupComponent extends AngularDisposable { // Encryption checkbox this.encryptCheckBox = this._register(new Checkbox(this.encryptElement!.nativeElement, { + ...defaultCheckboxStyles, label: LocalizedStrings.ENCRYPTION, checked: false, onChange: () => this.onChangeEncrypt(), @@ -268,6 +273,7 @@ export class BackupComponent extends AngularDisposable { // Verify backup checkbox this.verifyCheckBox = this._register(new Checkbox(this.verifyElement!.nativeElement, { + ...defaultCheckboxStyles, label: LocalizedStrings.VERIFY_CONTAINER, checked: false, onChange: () => { }, @@ -276,6 +282,7 @@ export class BackupComponent extends AngularDisposable { // Perform checksum checkbox this.checksumCheckBox = this._register(new Checkbox(this.checksumElement!.nativeElement, { + ...defaultCheckboxStyles, label: LocalizedStrings.CHECKSUM_CONTAINER, checked: false, onChange: () => { }, @@ -284,6 +291,7 @@ export class BackupComponent extends AngularDisposable { // Continue on error checkbox this.continueOnErrorCheckBox = this._register(new Checkbox(this.continueOnErrorElement!.nativeElement, { + ...defaultCheckboxStyles, label: LocalizedStrings.CONTINUE_ON_ERROR_CONTAINER, checked: false, onChange: () => { }, @@ -292,12 +300,14 @@ export class BackupComponent extends AngularDisposable { // Set backup name this.backupNameBox = this._register(new InputBox(this.backupNameElement!.nativeElement, this.contextViewService, { - ariaLabel: LocalizedStrings.BACKUP_NAME + ariaLabel: LocalizedStrings.BACKUP_NAME, + inputBoxStyles: defaultInputBoxStyles })); // Set backup path list this.urlInputBox = this._register(new InputBox(this.urlPathElement!.nativeElement, this.contextViewService, { - ariaLabel: LocalizedStrings.BACKUP_URL + ariaLabel: LocalizedStrings.BACKUP_URL, + inputBoxStyles: defaultInputBoxStyles })); this._register(this.urlInputBox.onDidChange((value) => this.onUrlInputBoxChanged(value))); @@ -324,13 +334,13 @@ export class BackupComponent extends AngularDisposable { this.pathListBox.render(this.filePathElement!.nativeElement); // Set backup path add/remove buttons - this.addUrlPathButton = this._register(new Button(this.addUrlPathElement!.nativeElement, { secondary: true })); + this.addUrlPathButton = this._register(new Button(this.addUrlPathElement!.nativeElement, { secondary: true, ...defaultButtonStyles })); this.addUrlPathButton.label = localize('backupBrowseButton', "Browse"); this.addUrlPathButton.title = localize('addUrl', "Add URL"); - this.addFilePathButton = this._register(new Button(this.addFilePathElement!.nativeElement, { secondary: true })); + this.addFilePathButton = this._register(new Button(this.addFilePathElement!.nativeElement, { secondary: true, ...defaultButtonStyles })); this.addFilePathButton.label = '+'; this.addFilePathButton.title = localize('addFile', "Add File"); - this.removeFilePathButton = this._register(new Button(this.removeFilePathElement!.nativeElement, { secondary: true })); + this.removeFilePathButton = this._register(new Button(this.removeFilePathElement!.nativeElement, { secondary: true, ...defaultButtonStyles })); this.removeFilePathButton.label = '-'; this.removeFilePathButton.title = localize('removeFile', "Remove files"); @@ -351,12 +361,14 @@ export class BackupComponent extends AngularDisposable { validationOptions: { validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: LocalizedStrings.MEDIA_NAME_REQUIRED_ERROR }) : null }, - ariaLabel: LocalizedStrings.NEW_MEDIA_SET_NAME + ariaLabel: LocalizedStrings.NEW_MEDIA_SET_NAME, + inputBoxStyles: defaultInputBoxStyles } )); this.mediaDescriptionBox = this._register(new InputBox(this.mediaDescriptionElement!.nativeElement, this.contextViewService, { - ariaLabel: LocalizedStrings.NEW_MEDIA_SET_DESCRIPTION + ariaLabel: LocalizedStrings.NEW_MEDIA_SET_DESCRIPTION, + inputBoxStyles: defaultInputBoxStyles })); // Set backup retain days @@ -376,7 +388,8 @@ export class BackupComponent extends AngularDisposable { } } }, - ariaLabel: LocalizedStrings.SET_BACKUP_RETAIN_DAYS + ariaLabel: LocalizedStrings.SET_BACKUP_RETAIN_DAYS, + inputBoxStyles: defaultInputBoxStyles })); // Disable elements @@ -441,24 +454,24 @@ export class BackupComponent extends AngularDisposable { private addFooterButtons(): void { // Set script footer button - this.scriptButton = this._register(new Button(this.scriptButtonElement!.nativeElement, { secondary: true })); + this.scriptButton = this._register(new Button(this.scriptButtonElement!.nativeElement, { secondary: true, ...defaultButtonStyles })); this.scriptButton.label = localize('backupComponent.script', "Script"); this._register(this.scriptButton.onDidClick(() => this.onScript())); - this._register(attachButtonStyler(this.scriptButton, this.themeService)); + this._register(this.scriptButton); this.scriptButton.enabled = false; // Set backup footer button - this.backupButton = this._register(new Button(this.backupButtonElement!.nativeElement)); + this.backupButton = this._register(new Button(this.backupButtonElement!.nativeElement, defaultButtonStyles)); this.backupButton.label = localize('backupComponent.backup', "Backup"); this._register(this.backupButton.onDidClick(() => this.onOk())); - this._register(attachButtonStyler(this.backupButton, this.themeService)); + this._register(this.backupButton); this.backupEnabled = false; // Set cancel footer button - this.cancelButton = this._register(new Button(this.cancelButtonElement!.nativeElement, { secondary: true })); + this.cancelButton = this._register(new Button(this.cancelButtonElement!.nativeElement, { secondary: true, ...defaultButtonStyles })); this.cancelButton.label = localize('backupComponent.cancel', "Cancel"); this._register(this.cancelButton.onDidClick(() => this.onCancel())); - this._register(attachButtonStyler(this.cancelButton, this.themeService)); + this._register(this.cancelButton); } private initialize(isMetadataPopulated: boolean): void { @@ -586,9 +599,9 @@ export class BackupComponent extends AngularDisposable { this.urlInputBox!.value = ''; this.pathListBox!.setValidation(true); - this.cancelButton!.applyStyles(); - this.scriptButton!.applyStyles(); - this.backupButton!.applyStyles(); + // this.cancelButton!.applyStyles(); + // this.scriptButton!.applyStyles(); + // this.backupButton!.applyStyles(); } private registerListeners(): void { @@ -597,9 +610,6 @@ export class BackupComponent extends AngularDisposable { this._register(attachInputBoxStyler(this.recoveryBox!, this.themeService)); this._register(attachSelectBoxStyler(this.backupTypeSelectBox!, this.themeService)); this._register(attachListBoxStyler(this.pathListBox!, this.themeService)); - this._register(attachButtonStyler(this.addUrlPathButton!, this.themeService)); - this._register(attachButtonStyler(this.addFilePathButton!, this.themeService)); - this._register(attachButtonStyler(this.removeFilePathButton!, this.themeService)); this._register(attachSelectBoxStyler(this.compressionSelectBox!, this.themeService)); this._register(attachSelectBoxStyler(this.algorithmSelectBox!, this.themeService)); this._register(attachSelectBoxStyler(this.encryptorSelectBox!, this.themeService)); @@ -607,12 +617,6 @@ export class BackupComponent extends AngularDisposable { this._register(attachInputBoxStyler(this.urlInputBox!, this.themeService)); this._register(attachInputBoxStyler(this.mediaDescriptionBox!, this.themeService)); this._register(attachInputBoxStyler(this.backupRetainDaysBox!, this.themeService)); - this._register(attachCheckboxStyler(this.copyOnlyCheckBox!, this.themeService)); - this._register(attachCheckboxStyler(this.toUrlCheckBox!, this.themeService)); - this._register(attachCheckboxStyler(this.encryptCheckBox!, this.themeService)); - this._register(attachCheckboxStyler(this.verifyCheckBox!, this.themeService)); - this._register(attachCheckboxStyler(this.checksumCheckBox!, this.themeService)); - this._register(attachCheckboxStyler(this.continueOnErrorCheckBox!, this.themeService)); this._register(this.backupTypeSelectBox!.onDidSelect(selected => this.onBackupTypeChanged())); this._register(this.addUrlPathButton!.onDidClick(() => this.onAddUrlClick())); diff --git a/src/sql/workbench/contrib/charts/browser/chartView.ts b/src/sql/workbench/contrib/charts/browser/chartView.ts index 5ddd57c770..5e8cb21f33 100644 --- a/src/sql/workbench/contrib/charts/browser/chartView.ts +++ b/src/sql/workbench/contrib/charts/browser/chartView.ts @@ -25,10 +25,11 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Registry } from 'vs/platform/registry/common/platform'; -import { attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachInputBoxStyler, attachSelectBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ChartOptions, ControlType, IChartOption } from './chartOptions'; import { Insight } from './insight'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; const insightRegistry = Registry.as(Extensions.InsightContribution); @@ -366,8 +367,10 @@ export class ChartView extends Disposable implements IPanelView { this.optionDisposables.push(attachSelectBoxStyler(dropdown, this._themeService)); break; case ControlType.input: - let input = new InputBox(optionInput, this._contextViewService); - input.setAriaLabel(option.label); + let input = new InputBox(optionInput, this._contextViewService, { + ariaLabel: option.label, + inputBoxStyles: defaultInputBoxStyles + }); input.value = value || ''; input.onDidChange(e => { if (this._options[entry] !== e) { @@ -385,8 +388,11 @@ export class ChartView extends Disposable implements IPanelView { this.optionDisposables.push(attachInputBoxStyler(input, this._themeService)); break; case ControlType.numberInput: - let numberInput = new InputBox(optionInput, this._contextViewService, { type: 'number' }); - numberInput.setAriaLabel(option.label); + let numberInput = new InputBox(optionInput, this._contextViewService, { + type: 'number', + ariaLabel: option.label, + inputBoxStyles: defaultInputBoxStyles + }); numberInput.value = value || ''; numberInput.onDidChange(e => { if (this._options[entry] !== e) { @@ -405,8 +411,12 @@ export class ChartView extends Disposable implements IPanelView { this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService)); break; case ControlType.dateInput: - let dateInput = new InputBox(optionInput, this._contextViewService, { type: 'datetime-local' }); - dateInput.setAriaLabel(option.label); + let dateInput = new InputBox(optionInput, this._contextViewService, { + type: 'datetime-local', + ariaLabel: option.label, + inputBoxStyles: defaultInputBoxStyles + }); + dateInput.value = value || ''; dateInput.onDidChange(e => { if (this._options[entry] !== e) { diff --git a/src/sql/workbench/contrib/charts/browser/configureChartDialog.ts b/src/sql/workbench/contrib/charts/browser/configureChartDialog.ts index 61d4414152..2e009fb6bc 100644 --- a/src/sql/workbench/contrib/charts/browser/configureChartDialog.ts +++ b/src/sql/workbench/contrib/charts/browser/configureChartDialog.ts @@ -14,7 +14,6 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { ILogService } from 'vs/platform/log/common/log'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; export class ConfigureChartDialog extends Modal { constructor( @@ -39,9 +38,7 @@ export class ConfigureChartDialog extends Modal { public override render() { super.render(); attachModalDialogStyler(this, this._themeService); - - let closeButton = this.addFooterButton(localize('configureChartDialog.close', "Close"), () => this.close()); - attachButtonStyler(closeButton, this._themeService); + this.addFooterButton(localize('configureChartDialog.close', "Close"), () => this.close()); } protected renderBody(container: HTMLElement) { diff --git a/src/sql/workbench/contrib/charts/browser/interfaces.ts b/src/sql/workbench/contrib/charts/browser/interfaces.ts index 9ba076fc5e..9b1c5ebb23 100644 --- a/src/sql/workbench/contrib/charts/browser/interfaces.ts +++ b/src/sql/workbench/contrib/charts/browser/interfaces.ts @@ -20,7 +20,7 @@ export interface IPointDataSet { export function customMixin(destination: any, source: any, overwrite?: boolean): any { if (types.isObject(source)) { mixin(destination, source, overwrite, customMixin); - } else if (types.isArray(source)) { + } else if (Array.isArray(source)) { for (let i = 0; i < source.length; i++) { if (destination[i]) { mixin(destination[i], source[i], overwrite, customMixin); diff --git a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts index 6717c65dff..632f7f2963 100644 --- a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts +++ b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts @@ -80,7 +80,7 @@ class TestParsedArgs implements NativeParsedArgs { 'install-source'?: string; 'list-extensions'?: boolean; locale?: string; - log?: string; + log?: string[]; logExtensionHostCommunication?: boolean; 'max-memory'?: string; 'new-window'?: boolean; diff --git a/src/sql/workbench/contrib/connection/browser/connection.contribution.ts b/src/sql/workbench/contrib/connection/browser/connection.contribution.ts index 420d613997..61b2ffc376 100644 --- a/src/sql/workbench/contrib/connection/browser/connection.contribution.ts +++ b/src/sql/workbench/contrib/connection/browser/connection.contribution.ts @@ -7,8 +7,7 @@ import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platf import { Registry } from 'vs/platform/registry/common/platform'; import { ClearRecentConnectionsAction, GetCurrentConnectionStringAction } from 'sql/workbench/services/connection/browser/connectionActions'; import * as azdata from 'azdata'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { Action2, MenuId, MenuRegistry, SyncActionDescriptor, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; import { ConnectionStatusbarItem } from 'sql/workbench/contrib/connection/browser/connectionStatus'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -35,44 +34,16 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; // Connection Dashboard registration -const actionRegistry = Registry.as(Extensions.WorkbenchActions); // Connection Actions -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - ClearRecentConnectionsAction, - ClearRecentConnectionsAction.ID, - ClearRecentConnectionsAction.LABEL - ), - ClearRecentConnectionsAction.LABEL -); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - AddServerGroupAction, - AddServerGroupAction.ID, - AddServerGroupAction.LABEL - ), - AddServerGroupAction.LABEL -); +registerAction2(ClearRecentConnectionsAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - ActiveConnectionsFilterAction, - ActiveConnectionsFilterAction.ID, - ActiveConnectionsFilterAction.SHOW_ACTIVE_CONNECTIONS_LABEL - ), - ActiveConnectionsFilterAction.SHOW_ACTIVE_CONNECTIONS_LABEL -); +registerAction2(AddServerGroupAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - AddServerAction, - AddServerAction.ID, - AddServerAction.LABEL - ), - AddServerAction.LABEL -); +registerAction2(ActiveConnectionsFilterAction); + +registerAction2(AddServerAction); MenuRegistry.appendMenuItem(MenuId.ViewTitle, { group: 'navigation', @@ -182,14 +153,7 @@ CommandsRegistry.registerCommand('azdata.connect', } }); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - GetCurrentConnectionStringAction, - GetCurrentConnectionStringAction.ID, - GetCurrentConnectionStringAction.LABEL - ), - GetCurrentConnectionStringAction.LABEL -); +registerAction2(GetCurrentConnectionStringAction); const configurationRegistry = Registry.as(ConfigExtensions.Configuration); configurationRegistry.registerConfiguration({ diff --git a/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts b/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts index d308221712..fa335d9eef 100644 --- a/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts +++ b/src/sql/workbench/contrib/connection/common/connectionProviderExtension.ts @@ -13,7 +13,6 @@ import { ConnectionProviderProperties, ICapabilitiesService } from 'sql/platform import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import type { IDisposable } from 'vs/base/common/lifecycle'; -import { isArray } from 'vs/base/common/types'; const ConnectionProviderContrib: IJSONSchema = { type: 'object', @@ -170,7 +169,7 @@ class ConnectionProviderHandler implements IWorkbenchContribution { delta.added.forEach(added => { resolveIconPath(added); - if (isArray(added.value)) { + if (Array.isArray(added.value)) { for (const provider of added.value) { this.disposables.set(provider, handleProvider(provider)); } @@ -179,7 +178,7 @@ class ConnectionProviderHandler implements IWorkbenchContribution { } }); delta.removed.forEach(removed => { - if (isArray(removed.value)) { + if (Array.isArray(removed.value)) { for (const provider of removed.value) { this.disposables.get(provider)!.dispose(); } diff --git a/src/sql/workbench/contrib/connection/common/connectionTreeProviderExentionPoint.ts b/src/sql/workbench/contrib/connection/common/connectionTreeProviderExentionPoint.ts index 5a3f089312..6be4555d6b 100644 --- a/src/sql/workbench/contrib/connection/common/connectionTreeProviderExentionPoint.ts +++ b/src/sql/workbench/contrib/connection/common/connectionTreeProviderExentionPoint.ts @@ -6,7 +6,7 @@ import { IConnectionTreeDescriptor, IConnectionTreeService } from 'sql/workbench/services/connection/common/connectionTreeService'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { isArray, isString } from 'vs/base/common/types'; +import { isString } from 'vs/base/common/types'; import { localize } from 'vs/nls'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -44,7 +44,7 @@ class ConnectionTreeProviderHandle implements IWorkbenchContribution { delta.added.forEach(added => { // resolveIconPath(added); - if (!isArray(added.value)) { + if (!Array.isArray(added.value)) { added.collector.error('Value must be array'); return; } diff --git a/src/sql/workbench/contrib/dashboard/browser/contents/webviewContent.component.ts b/src/sql/workbench/contrib/dashboard/browser/contents/webviewContent.component.ts index 1fbec76f09..381b17650a 100644 --- a/src/sql/workbench/contrib/dashboard/browser/contents/webviewContent.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/contents/webviewContent.component.ts @@ -79,7 +79,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo public setHtml(html: string): void { this._html = html; if (this._webview) { - this._webview.html = html; + this._webview.setHtml(html); } } @@ -99,7 +99,8 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo } this._webview = this.webviewService.createWebviewElement({ - id: this.id, + providedViewType: this.id, + title: this.id, contentOptions: { allowScripts: true, }, @@ -113,7 +114,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo this._onMessage.fire(e.message); }); if (this._html) { - this._webview.html = this._html; + this._webview.setHtml(this._html); } } } diff --git a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts index 94ce95d500..9e76915cf0 100644 --- a/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/core/dashboardPage.component.ts @@ -182,7 +182,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig let secondary: IAction[] = []; const menu = this.menuService.createMenu(MenuId.DashboardToolbar, this.contextKeyService); let groups = menu.getActions({ arg: this.connectionManagementService.connectionInfo.connectionProfile.toIConnectionProfile(), shouldForwardArgs: true }); - fillInActions(groups, { primary, secondary }, false, g => g === '', Number.MAX_SAFE_INTEGER, (action: SubmenuAction, group: string, groupSize: number) => group === undefined || group === ''); + fillInActions(groups, { primary, secondary }, false, g => g === '', (action: SubmenuAction, group: string, groupSize: number) => group === undefined || group === ''); primary.forEach(a => { if (a instanceof MenuItemAction) { @@ -219,7 +219,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig const toolbarTasks = this.tabToolbarActionsConfig.get(tabId); let tasks = TaskRegistry.getTasks(); let content = []; - if (types.isArray(toolbarTasks.actions) && toolbarTasks.actions.length > 0) { + if (Array.isArray(toolbarTasks.actions) && toolbarTasks.actions.length > 0) { tasks = toolbarTasks.actions.map(i => { if (types.isString(i)) { if (tasks.some(x => x === i)) { @@ -514,7 +514,7 @@ export abstract class DashboardPage extends AngularDisposable implements IConfig return properties ? [this.propertiesWidget] : []; } else if (types.isString(properties) && properties === 'collapsed') { return [this.propertiesWidget]; - } else if (types.isArray(properties)) { + } else if (Array.isArray(properties)) { return properties.map((item) => { const retVal = Object.assign({}, this.propertiesWidget); retVal.edition = item.edition; diff --git a/src/sql/workbench/contrib/dashboard/browser/dashboardEditor.ts b/src/sql/workbench/contrib/dashboard/browser/dashboardEditor.ts index cb4239f34b..5485d5ac03 100644 --- a/src/sql/workbench/contrib/dashboard/browser/dashboardEditor.ts +++ b/src/sql/workbench/contrib/dashboard/browser/dashboardEditor.ts @@ -52,7 +52,7 @@ export class DashboardEditor extends EditorPane { /** * Called to create the editor in the parent element. */ - public createEditor(parent: HTMLElement): void { + protected createEditor(parent: HTMLElement): void { } /** diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerActions.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerActions.ts index a55ce687e2..67ef918edf 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerActions.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerActions.ts @@ -6,7 +6,6 @@ import { ManageAction, ManageActionContext } from 'sql/workbench/browser/actions'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { IAngularEventingService } from 'sql/platform/angularEventing/browser/angularEventingService'; -import { ExecuteCommandAction } from 'vs/workbench/browser/parts/editor/editorActions'; export class ExplorerManageAction extends ManageAction { public static override readonly ID = 'explorerwidget.manage'; @@ -22,11 +21,3 @@ export class ExplorerManageAction extends ManageAction { await super.run(actionContext); } } - -export class CustomExecuteCommandAction extends ExecuteCommandAction { - override run(): Promise { - // {{SQL CARBON TODO}} - //return super.run(context.profile); - return super.run(); - } -} diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.component.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.component.ts index 9734d269c4..712a23c20d 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerWidget.component.ts @@ -27,13 +27,14 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ILogService } from 'vs/platform/log/common/log'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; -import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { getFlavor } from 'sql/workbench/contrib/dashboard/browser/dashboardRegistry'; import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { getInputBoxStyle } from 'vs/platform/theme/browser/defaultStyles'; +import { settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; @Component({ selector: 'explorer-widget', @@ -84,7 +85,12 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget, const inputOptions: IInputOptions = { placeholder: placeholderLabel, - ariaLabel: placeholderLabel + ariaLabel: placeholderLabel, + inputBoxStyles: getInputBoxStyle({ + inputBackground: settingsTextInputBackground, + inputForeground: settingsTextInputForeground, + inputBorder: settingsTextInputBorder + }) }; this._input = new InputBox(this._inputContainer.nativeElement, this.contextViewService, inputOptions); this._table = new ExplorerTable(this._tableContainer.nativeElement, @@ -103,7 +109,6 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget, this.quickInputService, this.componentContextService); this._register(this._input); - this._register(attachInputBoxStyler(this._input, this.themeService)); this._register(this._table); this._register(this._input.onDidChange(e => { this._filterDelayer.trigger(async () => { diff --git a/src/sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component.ts b/src/sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component.ts index d70e62c6c4..1d11099573 100644 --- a/src/sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component.ts +++ b/src/sql/workbench/contrib/dashboard/browser/widgets/webview/webviewWidget.component.ts @@ -61,7 +61,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, public setHtml(html: string): void { this._html = html; if (this._webview) { - this._webview.html = html; + this._webview.setHtml(html); } } @@ -99,8 +99,10 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, this._onMessageDisposable.dispose(); } + // {{SQL CARBON TODO}} - are the id & title values correct with new createWebviewElement API this._webview = this.webviewService.createWebviewElement({ - id: this.id, + providedViewType: this.id, + title: this.id, contentOptions: { allowScripts: true, }, @@ -113,7 +115,7 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, this._onMessage.fire(e.message); }); if (this._html) { - this._webview.html = this._html; + this._webview.setHtml(this._html); } } } diff --git a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts index 6824d624bb..0b157315b0 100644 --- a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts +++ b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts @@ -55,11 +55,11 @@ export class ConnectionViewletPanel extends ViewPane { super.renderHeader(container); } - override renderHeaderTitle(container: HTMLElement): void { + protected override renderHeaderTitle(container: HTMLElement): void { super.renderHeaderTitle(container, this.options.title); } - override renderBody(container: HTMLElement): void { + protected override renderBody(container: HTMLElement): void { const viewletContainer = DOM.append(container, DOM.$('div.server-explorer-viewlet')); const viewContainer = DOM.append(viewletContainer, DOM.$('div.object-explorer-view')); this._serverTreeView.renderBody(viewContainer).then(undefined, error => { @@ -72,7 +72,7 @@ export class ConnectionViewletPanel extends ViewPane { return this._serverTreeView.tree; } - override layoutBody(size: number): void { + protected override layoutBody(size: number): void { this._serverTreeView.layout(size); this._root!.classList.toggle('narrow', this._root!.clientWidth < 300); } diff --git a/src/sql/workbench/contrib/editData/browser/editDataActions.ts b/src/sql/workbench/contrib/editData/browser/editDataActions.ts index cd54221875..e0f10b7472 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataActions.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataActions.ts @@ -14,7 +14,7 @@ import * as dom from 'vs/base/browser/dom'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { INotificationService } from 'vs/platform/notification/common/notification'; import Severity from 'vs/base/common/severity'; -import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachSelectBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; const $ = dom.$; diff --git a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts index da9b30aade..ec78b6a76f 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataEditor.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataEditor.ts @@ -213,7 +213,7 @@ export class EditDataEditor extends EditorPane { /** * Sets this editor and the sub-editors to visible. */ - public override setEditorVisible(visible: boolean, group: IEditorGroup): void { + protected override setEditorVisible(visible: boolean, group: IEditorGroup): void { if (this._resultsEditor) { this._resultsEditor.setVisible(visible, group); } diff --git a/src/sql/workbench/contrib/editData/browser/editDataResultsEditor.ts b/src/sql/workbench/contrib/editData/browser/editDataResultsEditor.ts index a126a0eaa8..6a8dc6fdd4 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataResultsEditor.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataResultsEditor.ts @@ -52,7 +52,7 @@ export class EditDataResultsEditor extends EditorPane { return this._input; } - public createEditor(parent: HTMLElement): void { + protected createEditor(parent: HTMLElement): void { parent.appendChild(this.styleSheet); } diff --git a/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts b/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts index fa0af4933c..d0c47051ef 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/azdataGraphView.ts @@ -62,7 +62,7 @@ export class AzdataGraphView extends Disposable { this.initializeGraphEvents(); configurationService.onDidChangeConfiguration(e => { - if (e.affectedKeys.includes('executionPlan.tooltips.enableOnHoverTooltips')) { + if (e.affectedKeys.has('executionPlan.tooltips.enableOnHoverTooltips')) { const enableHoverOnTooltip = configurationService.getValue('executionPlan.tooltips.enableOnHoverTooltips'); if (this._diagram) { this._diagram.setShowTooltipOnClick(!enableHoverOnTooltip); diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts index 53080c6591..3c7d5a6553 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonEditorView.ts @@ -33,8 +33,8 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { ExecutionPlanWidgetController } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetController'; import { NodeSearchWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget'; import { Button } from 'sql/base/browser/ui/button/button'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { Disposable } from 'vs/base/common/lifecycle'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; const ADD_EXECUTION_PLAN_STRING = localize('epCompare.addExecutionPlanLabel', 'Add execution plan'); @@ -214,8 +214,7 @@ export class ExecutionPlanComparisonEditorView extends Disposable { this._placeholderInfoboxContainer = DOM.$('.placeholder-infobox'); - this._placeholderButton = this._register(new Button(this._placeholderInfoboxContainer, { secondary: true })); - this._register(attachButtonStyler(this._placeholderButton, this.themeService)); + this._placeholderButton = this._register(new Button(this._placeholderInfoboxContainer, { secondary: true, ...defaultButtonStyles })); this._placeholderButton.label = ADD_EXECUTION_PLAN_STRING; this._placeholderButton.ariaLabel = ADD_EXECUTION_PLAN_STRING; this._placeholderButton.enabled = true; diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts index 23b8fe98fa..a35f536a2a 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanComparisonPropertiesView.ts @@ -21,6 +21,7 @@ import { deepClone } from 'vs/base/common/objects'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { ThemeIcon } from 'vs/base/common/themables'; export enum ExecutionPlanCompareOrientation { Horizontal = 'horizontal', @@ -391,8 +392,8 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti diffIcon.iconClass = executionPlanComparisonPropertiesDifferent; } else { diffIcon = (parseFloat(v.primaryProp.displayValue) > parseFloat(v.secondaryProp.displayValue)) - ? { iconClass: Codicon.chevronRight.classNames, title: greaterThanTitle } - : { iconClass: Codicon.chevronLeft.classNames, title: lessThanTitle }; + ? { iconClass: ThemeIcon.asClassName(Codicon.chevronRight), title: greaterThanTitle } + : { iconClass: ThemeIcon.asClassName(Codicon.chevronLeft), title: lessThanTitle }; } break; case sqlExtHostType.executionPlan.ExecutionPlanGraphElementPropertyDataType.String: diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanEditor.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanEditor.ts index 3cab4eedd1..c0fcdcb139 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanEditor.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanEditor.ts @@ -39,7 +39,7 @@ export class ExecutionPlanEditor extends EditorPane { /** * Called to create the editor in the parent element. */ - public createEditor(parent: HTMLElement): void { + protected createEditor(parent: HTMLElement): void { this._parentContainer = parent; //Enable scrollbars when drawing area is larger than viewport parent.style.overflow = 'auto'; diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanInput.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanInput.ts index 7894a87272..64cf6f10bd 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanInput.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanInput.ts @@ -7,11 +7,11 @@ import * as path from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import * as azdata from 'azdata'; import { ExecutionPlanEditor } from 'sql/workbench/contrib/executionPlan/browser/executionPlanEditor'; +import { IEditorModel, IEditorOptions } from 'vs/platform/editor/common/editor'; export class ExecutionPlanInput extends EditorInput { @@ -86,7 +86,7 @@ export class ExecutionPlanInput extends EditorInput { return false; } - public override async resolve(refresh?: boolean): Promise { + public override async resolve(options?: IEditorOptions): Promise { if (!this.executionPlanGraphinfo.graphFileContent) { this.executionPlanGraphinfo.graphFileContent = (await this._fileService.read(this._uri, { acceptTextOnly: true })).value; } diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts index 9079b9079a..a7bda7d593 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase.ts @@ -28,6 +28,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ThemeIcon } from 'vs/base/common/themables'; export abstract class ExecutionPlanPropertiesViewBase extends Disposable implements IVerticalSashLayoutProvider { // Title bar with close button action @@ -143,7 +145,8 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme this._propertiesSearchInput = this._register(new InputBox(this._propertiesSearchInputContainer, this._contextViewService, { ariaDescription: propertiesSearchDescription, - placeholder: searchPlaceholder + placeholder: searchPlaceholder, + inputBoxStyles: defaultInputBoxStyles })); this._register(attachInputBoxStyler(this._propertiesSearchInput, this._themeService)); @@ -403,7 +406,7 @@ export class ClosePropertyViewAction extends Action { public static LABEL = localize('executionPlanPropertyViewClose', "Close"); constructor() { - super(ClosePropertyViewAction.ID, ClosePropertyViewAction.LABEL, Codicon.close.classNames); + super(ClosePropertyViewAction.ID, ClosePropertyViewAction.LABEL, ThemeIcon.asClassName(Codicon.close)); } public override async run(context: ExecutionPlanPropertiesViewBase): Promise { @@ -465,7 +468,7 @@ export class ExpandAllPropertiesAction extends Action { public static LABEL = localize('executionPlanExpandAllProperties', 'Expand All'); constructor() { - super(ExpandAllPropertiesAction.ID, ExpandAllPropertiesAction.LABEL, Codicon.expandAll.classNames); + super(ExpandAllPropertiesAction.ID, ExpandAllPropertiesAction.LABEL, ThemeIcon.asClassName(Codicon.expandAll)); } public override async run(context: ExecutionPlanPropertiesViewBase): Promise { @@ -478,7 +481,7 @@ export class CollapseAllPropertiesAction extends Action { public static LABEL = localize('executionPlanCollapseAllProperties', 'Collapse All'); constructor() { - super(CollapseAllPropertiesAction.ID, CollapseAllPropertiesAction.LABEL, Codicon.collapseAll.classNames); + super(CollapseAllPropertiesAction.ID, CollapseAllPropertiesAction.LABEL, ThemeIcon.asClassName(Codicon.collapseAll)); } public override async run(context: ExecutionPlanPropertiesViewBase): Promise { diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts index 2ed39bab53..c59d6a142a 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanView.ts @@ -171,12 +171,12 @@ export class ExecutionPlanView extends Disposable implements IHorizontalSashLayo this._register(this._instantiationService.createInstance(SavePlanFile)), this._register(new OpenPlanFile()), this._register(this._instantiationService.createInstance(OpenQueryAction, 'ActionBar')), - this._register(new Separator()), + new Separator(), this._register(this._instantiationService.createInstance(ZoomInAction, 'ActionBar')), this._register(this._instantiationService.createInstance(ZoomOutAction, 'ActionBar')), this._register(this._instantiationService.createInstance(ZoomToFitAction, 'ActionBar')), this._register(this._instantiationService.createInstance(CustomZoomAction, 'ActionBar')), - this._register(new Separator()), + new Separator(), this._register(this._instantiationService.createInstance(SearchNodeAction, 'ActionBar')), this._register(this._instantiationService.createInstance(PropertiesAction, 'ActionBar')), this._register(this._instantiationService.createInstance(CompareExecutionPlanAction, 'ActionBar')), @@ -189,18 +189,18 @@ export class ExecutionPlanView extends Disposable implements IHorizontalSashLayo this._register(this._instantiationService.createInstance(SavePlanFile)), this._register(new OpenPlanFile()), this._register(this._instantiationService.createInstance(OpenQueryAction, 'ContextMenu')), - this._register(new Separator()), + new Separator(), this._register(this._instantiationService.createInstance(ZoomInAction, 'ContextMenu')), this._register(this._instantiationService.createInstance(ZoomOutAction, 'ContextMenu')), this._register(this._instantiationService.createInstance(ZoomToFitAction, 'ContextMenu')), this._register(this._instantiationService.createInstance(CustomZoomAction, 'ContextMenu')), - this._register(new Separator()), + new Separator(), this._register(this._instantiationService.createInstance(SearchNodeAction, 'ContextMenu')), this._register(this._instantiationService.createInstance(PropertiesAction, 'ContextMenu')), this._register(this._instantiationService.createInstance(CompareExecutionPlanAction, 'ContextMenu')), this._register(this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ContextMenu')), this.contextMenuToggleTooltipAction, - this._register(new Separator()), + new Separator(), ]; if (this._queryResultsView) { diff --git a/src/sql/workbench/contrib/executionPlan/browser/executionPlanViewHeader.ts b/src/sql/workbench/contrib/executionPlan/browser/executionPlanViewHeader.ts index 666e572c4a..2f2ac073de 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/executionPlanViewHeader.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/executionPlanViewHeader.ts @@ -12,6 +12,7 @@ import { RunQueryOnConnectionMode } from 'sql/platform/connection/common/connect import { Button } from 'sql/base/browser/ui/button/button'; import { removeLineBreaks } from 'sql/base/common/strings'; import { Disposable } from 'vs/base/common/lifecycle'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export class ExecutionPlanViewHeader extends Disposable { @@ -106,6 +107,7 @@ export class ExecutionPlanViewHeader extends Disposable { const link = this._register(new Button(this._recommendationsContainer, { title: r.displayString, secondary: true, + ...defaultButtonStyles })); link.label = r.displayString; diff --git a/src/sql/workbench/contrib/executionPlan/browser/topOperationsTab.ts b/src/sql/workbench/contrib/executionPlan/browser/topOperationsTab.ts index e4e70d0c2c..9af8d36509 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/topOperationsTab.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/topOperationsTab.ts @@ -33,6 +33,7 @@ import { filterIconClassNames, searchPlaceholder, topOperationsSearchDescription import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; const TABLE_SORT_COLUMN_KEY = 'tableCostColumnForSorting'; @@ -182,7 +183,8 @@ export class TopOperationsTabView extends Disposable implements IPanelView { const topOperationsSearchInput = this._register(new InputBox(headerSearchBarContainer, this._contextViewService, { ariaDescription: topOperationsSearchDescription, - placeholder: searchPlaceholder + placeholder: searchPlaceholder, + inputBoxStyles: defaultInputBoxStyles, })); this._register(attachInputBoxStyler(topOperationsSearchInput, this._themeService)); topOperationsSearchInput.element.classList.add('codicon', filterIconClassNames); diff --git a/src/sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget.ts b/src/sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget.ts index d12109bb2e..5061d94022 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget.ts @@ -18,6 +18,8 @@ import { zoomIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/ import { Button } from 'sql/base/browser/ui/button/button'; import { AzdataGraphView } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView'; import { ExecutionPlanWidgetController } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetController'; +import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ThemeIcon } from 'vs/base/common/themables'; export class CustomZoomWidget extends ExecutionPlanWidgetBase { private _actionBar: ActionBar; @@ -38,7 +40,8 @@ export class CustomZoomWidget extends ExecutionPlanWidgetBase { this.customZoomInputBox = this._register(new InputBox(this.container, this.contextViewService, { type: 'number', ariaLabel: zoomValueLabel, - flexibleWidth: false + flexibleWidth: false, + inputBoxStyles: defaultInputBoxStyles })); this._register(attachInputBoxStyler(this.customZoomInputBox, this.themeService)); @@ -58,7 +61,8 @@ export class CustomZoomWidget extends ExecutionPlanWidgetBase { })); const applyButton = this._register(new Button(this.container, { - title: localize('customZoomApplyButtonTitle', "Apply Zoom") + title: localize('customZoomApplyButtonTitle', "Apply Zoom"), + ...defaultButtonStyles })); applyButton.setWidth('60px'); applyButton.label = localize('customZoomApplyButton', "Apply"); @@ -105,12 +109,10 @@ export class CancelZoom extends Action { public static LABEL = localize('cancelCustomZoomAction', "Close"); constructor() { - super(CancelZoom.ID, CancelZoom.LABEL, Codicon.chromeClose.classNames); + super(CancelZoom.ID, CancelZoom.LABEL, ThemeIcon.asClassName(Codicon.chromeClose)); } public override async run(context: CustomZoomWidget): Promise { context.widgetController.removeWidget(context); } } - - diff --git a/src/sql/workbench/contrib/executionPlan/browser/widgets/highlightExpensiveNodeWidget.ts b/src/sql/workbench/contrib/executionPlan/browser/widgets/highlightExpensiveNodeWidget.ts index e4f1c5596d..ffd471c645 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/widgets/highlightExpensiveNodeWidget.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/widgets/highlightExpensiveNodeWidget.ts @@ -21,6 +21,8 @@ import { Button } from 'sql/base/browser/ui/button/button'; import { searchIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; const SELECT_EXPENSE_METRIC_TITLE = localize('executionPlanSelectExpenseMetricTitle', 'Select expense metric'); @@ -125,7 +127,8 @@ export class HighlightExpensiveOperationWidget extends ExecutionPlanWidgetBase { const self = this; const applyButton = this._register(new Button(this.container, { - title: localize('highlightExpensiveOperationButtonTitle', 'Highlight Expensive Operation') + title: localize('highlightExpensiveOperationButtonTitle', 'Highlight Expensive Operation'), + ...defaultButtonStyles })); applyButton.label = localize('highlightExpensiveOperationApplyButton', 'Apply'); @@ -326,7 +329,7 @@ export class CancelHIghlightExpensiveOperationAction extends Action { public static LABEL = localize('cancelExpensiveOperationAction', 'Close'); constructor() { - super(CancelHIghlightExpensiveOperationAction.ID, CancelHIghlightExpensiveOperationAction.LABEL, Codicon.chromeClose.classNames); + super(CancelHIghlightExpensiveOperationAction.ID, CancelHIghlightExpensiveOperationAction.LABEL, ThemeIcon.asClassName(Codicon.chromeClose)); } public override async run(context: HighlightExpensiveOperationWidget): Promise { diff --git a/src/sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget.ts b/src/sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget.ts index 0cf897ce37..26d40b1841 100644 --- a/src/sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget.ts +++ b/src/sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget.ts @@ -17,6 +17,8 @@ import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { AzdataGraphView, SearchType } from 'sql/workbench/contrib/executionPlan/browser/azdataGraphView'; import { ExecutionPlanWidgetController } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetController'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ThemeIcon } from 'vs/base/common/themables'; const SELECT_PROPERTY_TITLE = localize('executionPlanSelectPropertyTitle', 'Select property'); const SELECT_SEARCH_TYPE_TITLE = localize('executionPlanSelectSearchTypeTitle', 'Select search type'); @@ -123,7 +125,10 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase { })); // search text input box - this._searchTextInputBox = this._register(new InputBox(this.container, this.contextViewService, {})); + this._searchTextInputBox = this._register(new InputBox(this.container, this.contextViewService, + { + inputBoxStyles: defaultInputBoxStyles + })); this._searchTextInputBox.setAriaLabel(ENTER_SEARCH_VALUE_TITLE); this._searchTextInputBox.element.style.marginLeft = '5px'; this._register(attachInputBoxStyler(this._searchTextInputBox, this.themeService)); @@ -204,7 +209,7 @@ export class GoToNextMatchAction extends Action { public static LABEL = localize('nextSearchItemAction', "Next Match"); constructor() { - super(GoToNextMatchAction.ID, GoToNextMatchAction.LABEL, Codicon.arrowDown.classNames); + super(GoToNextMatchAction.ID, GoToNextMatchAction.LABEL, ThemeIcon.asClassName(Codicon.arrowDown)); } public override async run(context: NodeSearchWidget): Promise { @@ -217,7 +222,7 @@ export class GoToPreviousMatchAction extends Action { public static LABEL = localize('previousSearchItemAction', "Previous Match"); constructor() { - super(GoToPreviousMatchAction.ID, GoToPreviousMatchAction.LABEL, Codicon.arrowUp.classNames); + super(GoToPreviousMatchAction.ID, GoToPreviousMatchAction.LABEL, ThemeIcon.asClassName(Codicon.arrowUp)); } public override async run(context: NodeSearchWidget): Promise { @@ -230,7 +235,7 @@ export class CancelSearch extends Action { public static LABEL = localize('cancelSearchAction', "Close"); constructor() { - super(CancelSearch.ID, CancelSearch.LABEL, Codicon.chromeClose.classNames); + super(CancelSearch.ID, CancelSearch.LABEL, ThemeIcon.asClassName(Codicon.chromeClose)); } public override async run(context: NodeSearchWidget): Promise { diff --git a/src/sql/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/sql/workbench/contrib/extensions/browser/extensions.contribution.ts index 4ab1acdb23..002d28eba5 100644 --- a/src/sql/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/sql/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ExtensionsLabel, IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { OpenExtensionAuthoringDocsAction } from 'sql/workbench/contrib/extensions/browser/extensionsActions'; import { localize } from 'vs/nls'; import { deepClone } from 'vs/base/common/objects'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; // Global Actions -const actionRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(OpenExtensionAuthoringDocsAction), 'Extensions: Author an Extension...', ExtensionsLabel); + + +registerAction2(OpenExtensionAuthoringDocsAction); // Register Commands CommandsRegistry.registerCommand('azdata.extension.open', (accessor: ServicesAccessor, extension: { id: string }) => { diff --git a/src/sql/workbench/contrib/extensions/browser/extensionsActions.ts b/src/sql/workbench/contrib/extensions/browser/extensionsActions.ts index f1f265af39..6353889d5c 100644 --- a/src/sql/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/sql/workbench/contrib/extensions/browser/extensionsActions.ts @@ -13,6 +13,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { PagedModel } from 'vs/base/common/paging'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; function getScenarioID(scenarioType: string) { return 'workbench.extensions.action.show' + scenarioType; @@ -68,21 +70,23 @@ export class InstallRecommendedExtensionsByScenarioAction extends Action { } } -export class OpenExtensionAuthoringDocsAction extends Action { +export class OpenExtensionAuthoringDocsAction extends Action2 { static readonly ID = 'workbench.extensions.action.openExtensionAuthoringDocs'; + static readonly LABEL_ORG = "Author an Extension..."; static readonly LABEL = localize('openExtensionAuthoringDocs', "Author an Extension..."); private static readonly extensionAuthoringDocsURI = 'https://docs.microsoft.com/sql/azure-data-studio/extension-authoring'; - constructor( - id: string = OpenExtensionAuthoringDocsAction.ID, - label: string = OpenExtensionAuthoringDocsAction.LABEL, - @IOpenerService private readonly openerService: IOpenerService, - ) { - super(id, label); + constructor() { + super({ + id: OpenExtensionAuthoringDocsAction.ID, + title: { value: OpenExtensionAuthoringDocsAction.LABEL, original: OpenExtensionAuthoringDocsAction.LABEL_ORG }, + f1: true, + }); } - override async run(): Promise { - await this.openerService.open(URI.parse(OpenExtensionAuthoringDocsAction.extensionAuthoringDocsURI)); + override async run(accessor: ServicesAccessor): Promise { + const openerService = accessor.get(IOpenerService); + await openerService.open(URI.parse(OpenExtensionAuthoringDocsAction.extensionAuthoringDocsURI)); } } diff --git a/src/sql/workbench/contrib/jobManagement/browser/jobHistory.component.ts b/src/sql/workbench/contrib/jobManagement/browser/jobHistory.component.ts index 5a82d89455..3d7677bf37 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/jobHistory.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/jobHistory.component.ts @@ -22,7 +22,7 @@ import { } from 'sql/workbench/contrib/jobManagement/browser/jobHistoryTree'; import { JobStepsViewRow } from 'sql/workbench/contrib/jobManagement/browser/jobStepsViewTree'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { Tree } from 'sql/base/parts/tree/browser/treeImpl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; diff --git a/src/sql/workbench/contrib/jobManagement/browser/jobStepsView.component.ts b/src/sql/workbench/contrib/jobManagement/browser/jobStepsView.component.ts index cbab8711ce..058000c76f 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/jobStepsView.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/jobStepsView.component.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/jobStepsView'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { OnInit, Component, Inject, forwardRef, ElementRef, ViewChild, AfterContentChecked } from '@angular/core'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { Tree } from 'sql/base/parts/tree/browser/treeImpl'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; diff --git a/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts b/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts index 646b11759e..0d12ecc127 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/jobsView.component.ts @@ -37,6 +37,7 @@ import { attachTableFilterStyler } from 'sql/platform/theme/common/styler'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export const JOBSVIEW_SELECTOR: string = 'jobsview-component'; export const ROW_HEIGHT: number = 45; @@ -188,7 +189,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe }); this.rowDetail = rowDetail; columns.unshift(this.rowDetail.getColumnDefinition()); - let filterPlugin = new HeaderFilter(this._contextViewService); + let filterPlugin = new HeaderFilter({ buttonStyles: defaultButtonStyles }, this._contextViewService); this._register(attachTableFilterStyler(filterPlugin, this._themeService)); this.filterPlugin = filterPlugin; jQuery(this._gridEl.nativeElement).empty(); diff --git a/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts b/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts index db6aa4ad40..3c4bdde71a 100644 --- a/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts +++ b/src/sql/workbench/contrib/jobManagement/browser/notebooksView.component.ts @@ -38,6 +38,7 @@ import { attachTableFilterStyler } from 'sql/platform/theme/common/styler'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export const NOTEBOOKSVIEW_SELECTOR: string = 'notebooksview-component'; @@ -187,7 +188,7 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit, }); this.rowDetail = rowDetail; columns.unshift(this.rowDetail.getColumnDefinition()); - let filterPlugin = new HeaderFilter(this._contextViewService); + let filterPlugin = new HeaderFilter({ buttonStyles: defaultButtonStyles }, this._contextViewService); this._register(attachTableFilterStyler(filterPlugin, this._themeService)); this.filterPlugin = filterPlugin; jQuery(this._gridEl.nativeElement).empty(); diff --git a/src/sql/workbench/contrib/modelView/browser/webview.component.ts b/src/sql/workbench/contrib/modelView/browser/webview.component.ts index f380f45c98..08ed706081 100644 --- a/src/sql/workbench/contrib/modelView/browser/webview.component.ts +++ b/src/sql/workbench/contrib/modelView/browser/webview.component.ts @@ -74,7 +74,8 @@ export default class WebViewComponent extends ComponentBase i private _createWebview(): void { this._webview = this.webviewService.createWebviewElement({ - id: this.id, + providedViewType: this.id, + title: this.id, contentOptions: { allowScripts: true, }, @@ -117,7 +118,7 @@ export default class WebViewComponent extends ComponentBase i private setHtml(): void { if (this._webview && this.html) { this._renderedHtml = this.html; - this._webview.html = this._renderedHtml; + this._webview.setHtml(this._renderedHtml); } } diff --git a/src/sql/workbench/contrib/notebook/browser/calloutDialog/imageCalloutDialog.ts b/src/sql/workbench/contrib/notebook/browser/calloutDialog/imageCalloutDialog.ts index dc7442c19c..3ade683c57 100644 --- a/src/sql/workbench/contrib/notebook/browser/calloutDialog/imageCalloutDialog.ts +++ b/src/sql/workbench/contrib/notebook/browser/calloutDialog/imageCalloutDialog.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/imageCalloutDialog'; import * as DOM from 'vs/base/browser/dom'; -import * as styler from 'vs/platform/theme/common/styler'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import * as constants from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/constants'; import { URI } from 'vs/base/common/uri'; @@ -27,6 +26,7 @@ import { attachCalloutDialogStyler } from 'sql/workbench/common/styler'; import * as path from 'vs/base/common/path'; import { unquoteText } from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/utils'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface IImageCalloutDialogOptions { insertTitle?: string, @@ -149,7 +149,8 @@ export class ImageCalloutDialog extends Modal { this._contextViewService, { placeholder: constants.pathPlaceholder, - ariaLabel: constants.pathInputLabel + ariaLabel: constants.pathInputLabel, + inputBoxStyles: defaultInputBoxStyles }); let browseButtonContainer = DOM.$('.button-icon'); this._imageBrowseButton = DOM.$('a.codicon.masked-icon.browse-local'); @@ -194,8 +195,9 @@ export class ImageCalloutDialog extends Modal { } private registerListeners(): void { - this._register(styler.attachInputBoxStyler(this._imageUrlInputBox, this._themeService)); - this._register(styler.attachToggleStyler(this._imageEmbedCheckbox, this._themeService)); + // {{SQL CARBON TODO}} - attach styles? + // this._register(styler.attachInputBoxStyler(this._imageUrlInputBox, this._themeService)); + // this._register(styler.attachToggleStyler(this._imageEmbedCheckbox, this._themeService)); } public insert(): void { diff --git a/src/sql/workbench/contrib/notebook/browser/calloutDialog/linkCalloutDialog.ts b/src/sql/workbench/contrib/notebook/browser/calloutDialog/linkCalloutDialog.ts index 3f0a68ec38..a04e988b05 100644 --- a/src/sql/workbench/contrib/notebook/browser/calloutDialog/linkCalloutDialog.ts +++ b/src/sql/workbench/contrib/notebook/browser/calloutDialog/linkCalloutDialog.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/linkCalloutDialog'; import * as DOM from 'vs/base/browser/dom'; -import * as styler from 'vs/platform/theme/common/styler'; import * as constants from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/constants'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { Modal, IDialogProperties, DialogPosition, DialogWidth } from 'sql/workbench/browser/modal/modal'; @@ -22,6 +21,7 @@ import { attachCalloutDialogStyler } from 'sql/workbench/common/styler'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { escapeLabel, escapeUrl, unquoteText } from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/utils'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface ILinkCalloutDialogOptions { insertTitle?: string, @@ -116,7 +116,8 @@ export class LinkCalloutDialog extends Modal { this._contextViewService, { placeholder: constants.linkTextPlaceholder, - ariaLabel: constants.linkTextLabel + ariaLabel: constants.linkTextLabel, + inputBoxStyles: defaultInputBoxStyles }); this._linkTextInputBox.value = this._defaultLabel; DOM.append(linkTextRow, linkTextInputContainer); @@ -133,15 +134,17 @@ export class LinkCalloutDialog extends Modal { this._contextViewService, { placeholder: constants.linkAddressPlaceholder, - ariaLabel: constants.linkAddressLabel + ariaLabel: constants.linkAddressLabel, + inputBoxStyles: defaultInputBoxStyles }); this._linkUrlInputBox.value = this._defaultLinkUrl; DOM.append(linkAddressRow, linkAddressInputContainer); } private registerListeners(): void { - this._register(styler.attachInputBoxStyler(this._linkTextInputBox, this._themeService)); - this._register(styler.attachInputBoxStyler(this._linkUrlInputBox, this._themeService)); + // {{SQL CARBON TODO}} - apply styles + // this._register(styler.attachInputBoxStyler(this._linkTextInputBox, this._themeService)); + // this._register(styler.attachInputBoxStyler(this._linkUrlInputBox, this._themeService)); } protected override onAccept(e?: StandardKeyboardEvent) { diff --git a/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts b/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts index 89a66f05c9..80425c1fc4 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellToolbarActions.ts @@ -397,22 +397,22 @@ export class CellToggleMoreActionViewItem extends DropdownMenuActionViewItem { this.setActionContext(this._cellContext); this._actions = [ instantiationService.createInstance(ConvertCellAction, 'convertCell', localize('convertCell', "Convert Cell")), - new Separator(), + new Separator(), instantiationService.createInstance(RunCellsAction, 'runAllAbove', localize('runAllAbove', "Run Cells Above"), false), instantiationService.createInstance(RunCellsAction, 'runAllBelow', localize('runAllBelow', "Run Cells Below"), true), - new Separator(), + new Separator(), instantiationService.createInstance(AddCellFromContextAction, 'codeAbove', localize('codeAbove', "Insert Code Above"), CellTypes.Code, false), instantiationService.createInstance(AddCellFromContextAction, 'codeBelow', localize('codeBelow', "Insert Code Below"), CellTypes.Code, true), - new Separator(), + new Separator(), instantiationService.createInstance(AddCellFromContextAction, 'markdownAbove', localize('markdownAbove', "Insert Text Above"), CellTypes.Markdown, false), instantiationService.createInstance(AddCellFromContextAction, 'markdownBelow', localize('markdownBelow', "Insert Text Below"), CellTypes.Markdown, true), - new Separator(), + new Separator(), instantiationService.createInstance(CollapseCellAction, 'collapseCell', localize('collapseCell', "Collapse Cell"), true), instantiationService.createInstance(CollapseCellAction, 'expandCell', localize('expandCell', "Expand Cell"), false), - new Separator(), + new Separator(), instantiationService.createInstance(ParametersCellAction, 'makeParameterCell', localize('makeParameterCell', "Make parameter cell"), true), instantiationService.createInstance(ParametersCellAction, 'removeParameterCell', localize('RemoveParameterCell', "Remove parameter cell"), false), - new Separator(), + new Separator(), instantiationService.createInstance(ClearCellOutputAction, 'clear', localize('clear', "Clear Result")), ]; } diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts index f54469ccbd..64a783d959 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/code.component.ts @@ -406,8 +406,9 @@ export class CodeComponent extends CellView implements OnInit, OnChanges { private updateLanguageMode(): void { if (this._editorModel && this._editor) { - let modeValue = this._languageService.createById(this.cellModel.language); - this._modelService.setMode(this._editorModel, modeValue); + // let modeValue = this._languageService.createById(this.cellModel.language); + // {{SQL CARBON TODO}} - do we still need this + // this._modelService.setMode(this._editorModel, modeValue); } } diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/linkHandler.directive.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/linkHandler.directive.ts index 037136f931..5845c455cc 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/linkHandler.directive.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/linkHandler.directive.ts @@ -76,7 +76,7 @@ export class LinkHandlerDirective { // Store fragment before converting, since asFileUri removes the uri fragment const fragment = uri.fragment; // Convert vscode-file protocol URIs to file since that's what Notebooks expect to work with - uri = FileAccess.asFileUri(uri); + uri = FileAccess.uriToFileUri(uri); if (this.isSupportedLink(uri)) { if (fragment && fragment.length > 0 && uri.fsPath === this.workbenchFilePath.fsPath) { this.notebookService.navigateTo(this.notebookUri, fragment); diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts index 045b350c66..b4f45e28ad 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/markdownToolbar.component.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./markdownToolbar'; import * as DOM from 'vs/base/browser/dom'; -import { Button, IButtonStyles } from 'sql/base/browser/ui/button/button'; +import { Button } from 'sql/base/browser/ui/button/button'; import { Component, Input, Inject, ViewChild, ElementRef, HostListener } from '@angular/core'; import { localize } from 'vs/nls'; import { CellEditModes, ICellModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; @@ -28,6 +28,7 @@ import { NotebookLinkHandler } from 'sql/workbench/contrib/notebook/browser/note import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { FileAccess } from 'vs/base/common/network'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export const MARKDOWN_TOOLBAR_SELECTOR: string = 'markdown-toolbar-component'; const linksRegex = /\[(?.+)\]\((?[^ ]+)(?: "(?.+)")?\)/; @@ -117,13 +118,15 @@ export class MarkdownToolbarComponent extends AngularDisposable { private initActionBar() { let linkButtonContainer = DOM.$('li.action-item'); linkButtonContainer.setAttribute('role', 'presentation'); - let linkButton = new Button(linkButtonContainer); + let linkButton = new Button(linkButtonContainer, defaultButtonStyles); linkButton.title = this.insertLink; linkButton.element.setAttribute('class', 'action-label codicon insert-link masked-icon'); - let buttonStyle: IButtonStyles = { - buttonBackground: null - }; - linkButton.style(buttonStyle); + + // {{SQL CARBON TODO}} - reenable + // let buttonStyle: IButtonStyles = { + // buttonBackground: null + // }; + // linkButton.style(buttonStyle); this._register(DOM.addDisposableListener(linkButtonContainer, DOM.EventType.CLICK, async e => { await this.onInsertButtonClick(e, MarkdownButtonType.LINK_PREVIEW); @@ -131,11 +134,12 @@ export class MarkdownToolbarComponent extends AngularDisposable { let imageButtonContainer = DOM.$('li.action-item'); imageButtonContainer.setAttribute('role', 'presentation'); - let imageButton = new Button(imageButtonContainer); + let imageButton = new Button(imageButtonContainer, defaultButtonStyles); imageButton.title = this.insertImage; imageButton.element.setAttribute('class', 'action-label codicon insert-image masked-icon'); - imageButton.style(buttonStyle); + // {{SQL CARBON TODO}} - reenable + //imageButton.style(buttonStyle); this._register(DOM.addDisposableListener(imageButtonContainer, DOM.EventType.CLICK, async e => { await this.onInsertButtonClick(e, MarkdownButtonType.IMAGE_PREVIEW); @@ -263,7 +267,7 @@ export class MarkdownToolbarComponent extends AngularDisposable { // VS Code blocks loading directly from the file protocol - we have to transform it to a vscode-file URI // first. Currently we assume that the path here is always going to be a path since we don't support // embedding images from web links. - const uri = FileAccess.asBrowserUri(URI.file(imageCalloutResult.imagePath)); + const uri = FileAccess.uriToBrowserUri(URI.file(imageCalloutResult.imagePath)); let base64String = await this.getFileContentBase64(uri); let mimeType = await this.getFileMimeType(uri); const originalImageName: string = path.basename(imageCalloutResult.imagePath).replace(/\s/g, ''); diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/output.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/output.component.ts index 83e8677d68..69209a2c39 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/output.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/output.component.ts @@ -35,7 +35,7 @@ const componentRegistry = <IMimeComponentRegistry>Registry.as(Extensions.MimeCom templateUrl: decodeURI(require.toUrl('./output.component.html')) }) export class OutputComponent extends CellView implements OnInit, AfterViewInit { - @ViewChild('output', { read: ElementRef }) override output: ElementRef; + @ViewChild('output', { read: ElementRef }) protected override output: ElementRef; @ViewChild(ComponentHostDirective) componentHost: ComponentHostDirective; @Input() cellOutput: nb.ICellOutput; @Input() cellModel: ICellModel; diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/stdin.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/stdin.component.ts index d1feb2e69e..8ad7922cee 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/stdin.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/stdin.component.ts @@ -24,6 +24,7 @@ import { attachInputBoxStyler } from 'sql/platform/theme/common/styler'; import { AngularDisposable } from 'sql/base/browser/lifecycle'; import { Deferred } from 'sql/base/common/promise'; import { ICellModel, CellExecutionState } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export const STDIN_SELECTOR: string = 'stdin-component'; @Component({ @@ -52,7 +53,8 @@ export class StdInComponent extends AngularDisposable implements AfterViewInit { ngAfterViewInit(): void { let inputOptions: IInputOptions = { placeholder: '', - ariaLabel: this.prompt + ariaLabel: this.prompt, + inputBoxStyles: defaultInputBoxStyles }; this._input = new InputBox(this._inputContainer.nativeElement, this.contextViewService, inputOptions); if (this.password) { diff --git a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts index b6d31cd9b0..8aca88b5e4 100644 --- a/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/cellViews/textCell.component.ts @@ -41,7 +41,7 @@ const USER_SELECT_CLASS = 'actionselect'; templateUrl: decodeURI(require.toUrl('./textCell.component.html')) }) export class TextCellComponent extends CellView implements OnInit, OnChanges { - @ViewChild('preview', { read: ElementRef }) override output: ElementRef; + @ViewChild('preview', { read: ElementRef }) protected override output: ElementRef; @ViewChildren(CodeComponent) private markdowncodeCell: QueryList<CodeComponent>; @Input() cellModel: ICellModel; diff --git a/src/sql/workbench/contrib/notebook/browser/find/notebookFindWidget.ts b/src/sql/workbench/contrib/notebook/browser/find/notebookFindWidget.ts index acdb016b1c..809746f87a 100644 --- a/src/sql/workbench/contrib/notebook/browser/find/notebookFindWidget.ts +++ b/src/sql/workbench/contrib/notebook/browser/find/notebookFindWidget.ts @@ -12,7 +12,7 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; +import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Sash, ISashEvent, Orientation, IVerticalSashLayoutProvider } from 'vs/base/browser/ui/sash/sash'; @@ -22,9 +22,9 @@ import { FIND_IDS, CONTEXT_FIND_INPUT_FOCUSED } from 'vs/editor/contrib/find/bro import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/browser/findState'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import * as colors from 'vs/platform/theme/common/colorRegistry'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -300,19 +300,20 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } private _applyTheme(theme: IColorTheme) { - let inputStyles: IFindInputStyles = { - inputActiveOptionBorder: theme.getColor(colors.inputActiveOptionBorder), - inputBackground: theme.getColor(colors.inputBackground), - inputForeground: theme.getColor(colors.inputForeground), - inputBorder: theme.getColor(colors.inputBorder), - inputValidationInfoBackground: theme.getColor(colors.inputValidationInfoBackground), - inputValidationInfoBorder: theme.getColor(colors.inputValidationInfoBorder), - inputValidationWarningBackground: theme.getColor(colors.inputValidationWarningBackground), - inputValidationWarningBorder: theme.getColor(colors.inputValidationWarningBorder), - inputValidationErrorBackground: theme.getColor(colors.inputValidationErrorBackground), - inputValidationErrorBorder: theme.getColor(colors.inputValidationErrorBorder) - }; - this._findInput.style(inputStyles); + // {{SQL CARBON TODO}} - reenable styles + // let inputStyles: IFindInputStyles = { + // inputActiveOptionBorder: theme.getColor(colors.inputActiveOptionBorder), + // inputBackground: theme.getColor(colors.inputBackground), + // inputForeground: theme.getColor(colors.inputForeground), + // inputBorder: theme.getColor(colors.inputBorder), + // inputValidationInfoBackground: theme.getColor(colors.inputValidationInfoBackground), + // inputValidationInfoBorder: theme.getColor(colors.inputValidationInfoBorder), + // inputValidationWarningBackground: theme.getColor(colors.inputValidationWarningBackground), + // inputValidationWarningBorder: theme.getColor(colors.inputValidationWarningBorder), + // inputValidationErrorBackground: theme.getColor(colors.inputValidationErrorBackground), + // inputValidationErrorBorder: theme.getColor(colors.inputValidationErrorBorder) + // }; + //this._findInput.style(inputStyles); } // ----- Public @@ -380,13 +381,15 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL private _buildFindPart(): HTMLElement { // Find input - this._findInput = this._register(new FindInput(null, this._contextViewProvider, true, { + this._findInput = this._register(new FindInput(null, this._contextViewProvider, { width: FIND_INPUT_AREA_WIDTH, label: NLS_FIND_INPUT_LABEL, placeholder: NLS_FIND_INPUT_PLACEHOLDER, appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand), appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand), + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles, validation: (value: string): InputBoxMessage => { if (value.length === 0) { return null; diff --git a/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts b/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts index 9f16c75d93..58f307ce67 100644 --- a/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts +++ b/src/sql/workbench/contrib/notebook/browser/htmlMarkdownConverter.ts @@ -133,7 +133,7 @@ export class HTMLMarkdownConverter { if (node?.src) { // Image URIs are converted to vscode-file URIs for the underlying HTML so that they can be loaded by ADS, // but we want to convert them back to their file URI when converting back to markdown for displaying to the user - let imgUri = FileAccess.asFileUri(URI.parse(node.src)); + let imgUri = FileAccess.uriToFileUri(URI.parse(node.src)); const notebookFolder: string = this.notebookUri ? path.join(path.dirname(this.notebookUri.fsPath), path.sep) : ''; let relativePath = findPathRelativeToContent(notebookFolder, imgUri); if (relativePath) { diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts index 1719f5b1e6..674047ab1f 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts @@ -42,6 +42,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; import { isEqual } from 'vs/base/common/resources'; import { NotebookEditor } from 'sql/workbench/contrib/notebook/browser/notebookEditor'; +import { IAttachedView } from 'vs/editor/common/model'; export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>; const languageAssociationRegistry = Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations); @@ -239,7 +240,7 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu private _modelResolveInProgress: boolean = false; private _modelResolved: Deferred<void> = new Deferred<void>(); private _containerResolved: Deferred<void> = new Deferred<void>(); - + private _attachedView: IAttachedView; private _notebookFindModel: NotebookFindModel; constructor(private _title: string, @@ -445,19 +446,19 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu let textOrUntitledEditorModel: ITextFileEditorModel | IUntitledTextEditorModel | TextResourceEditorModel; if (this.resource.scheme === Schemas.untitled) { if (this._untitledEditorModel) { - this._untitledEditorModel.textEditorModel.onBeforeAttached(); + this._attachedView = this._untitledEditorModel.textEditorModel.onBeforeAttached(); textOrUntitledEditorModel = this._untitledEditorModel; } else { let resolvedInput = await this._textInput.resolve(); if (!(resolvedInput instanceof BinaryEditorModel)) { - (resolvedInput as ITextEditorModel).textEditorModel.onBeforeAttached(); + this._attachedView = (resolvedInput as ITextEditorModel).textEditorModel.onBeforeAttached(); } textOrUntitledEditorModel = resolvedInput as TextFileEditorModel | UntitledTextEditorModel | TextResourceEditorModel; } } else { const textEditorModelReference = await this.textModelService.createModelReference(this.resource); this._register(textEditorModelReference); - textEditorModelReference.object.textEditorModel.onBeforeAttached(); + this._attachedView = textEditorModelReference.object.textEditorModel.onBeforeAttached(); await textEditorModelReference.object.resolve(); textOrUntitledEditorModel = textEditorModelReference.object as TextFileEditorModel | TextResourceEditorModel; } @@ -509,8 +510,8 @@ export abstract class NotebookInput extends EditorInput implements INotebookInpu } public override dispose(): void { - if (this._model && this._model.editorModel && this._model.editorModel.textEditorModel) { - this._model.editorModel.textEditorModel.onBeforeDetached(); + if (this._model && this._model.editorModel && this._model.editorModel.textEditorModel && this._attachedView) { + this._model.editorModel.textEditorModel.onBeforeDetached(this._attachedView); } this._disposeContainer(); super.dispose(); diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts index 1161d4e84f..d8d8fff697 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.component.ts @@ -13,7 +13,7 @@ import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachSelectBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IAction, Action, SubmenuAction } from 'vs/base/common/actions'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -59,6 +59,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { debounce } from 'vs/base/common/decorators'; import { ToggleAddCellDropdownAction } from 'sql/workbench/contrib/notebook/browser/cellToolbarActions'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export const NOTEBOOK_SELECTOR: string = 'notebook-component'; const PRIORITY = 105; @@ -632,7 +633,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe private addButton(label: string, onDidClick?: () => void, enabled?: boolean): void { const container = DOM.append(this.bookNav.nativeElement, DOM.$('.dialog-message-button')); - let button = new Button(container); + let button = new Button(container, defaultButtonStyles); button.label = label; if (onDidClick) { this._register(button.onDidClick(onDidClick)); @@ -669,7 +670,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe let secondary: IAction[] = []; let notebookBarMenu = this.menuService.createMenu(MenuId.NotebookToolbar, this.contextKeyService); let groups = notebookBarMenu.getActions({ arg: null, shouldForwardArgs: true }); - fillInActions(groups, { primary, secondary }, false, g => g === '', Number.MAX_SAFE_INTEGER, (action: SubmenuAction, group: string, groupSize: number) => group === undefined || group === ''); + fillInActions(groups, { primary, secondary }, false, g => g === '', (action: SubmenuAction, group: string, groupSize: number) => group === undefined || group === ''); this._actionBar.clear(); this._actionBar.setContent(this._initialToolbarContent); diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts index f6e74cd77e..b92a99e72d 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -13,12 +13,9 @@ import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensio import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/browser/models/untitledNotebookInput'; import { FileNotebookInput } from 'sql/workbench/contrib/notebook/browser/models/fileNotebookInput'; import { FileNoteBookEditorSerializer, NotebookEditorLanguageAssociation, UntitledNotebookEditorSerializer } from 'sql/workbench/contrib/notebook/browser/models/notebookEditorFactory'; -import { IWorkbenchActionRegistry, Extensions as WorkbenchActionsExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor, registerAction2, MenuRegistry, MenuId, Action2 } from 'vs/platform/actions/common/actions'; import { NotebookEditor } from 'sql/workbench/contrib/notebook/browser/notebookEditor'; import { NewNotebookAction, NewNotebookTask } from 'sql/workbench/contrib/notebook/browser/notebookActions'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IConfigurationRegistry, Extensions as ConfigExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { GridOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/gridOutput.component'; import { PlotlyOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/plotlyOutput.component'; @@ -71,6 +68,7 @@ import { ConnectionType, IConnectableInput, IConnectionCompletionOptions, IConne import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory) .registerEditorSerializer(FileNotebookInput.ID, FileNoteBookEditorSerializer); @@ -88,18 +86,8 @@ Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(NotebookThemingContribution, LifecyclePhase.Restored); // Global Actions -const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionsExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - NewNotebookAction, - NewNotebookAction.ID, - NewNotebookAction.LABEL, - { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyN }, - - ), - NewNotebookAction.LABEL -); +registerAction2(NewNotebookAction); // New Notebook new NewNotebookTask().registerTask(); @@ -154,7 +142,7 @@ CommandsRegistry.registerCommand({ id: OE_NEW_NOTEBOOK_COMMAND_ID, handler: (accessor, actionContext: ObjectExplorerActionsContext) => { const instantiationService = accessor.get(IInstantiationService); - return instantiationService.createInstance(NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL).run(actionContext); + return instantiationService.createInstance(NewNotebookAction).run(accessor, actionContext); } }); @@ -172,7 +160,7 @@ const ExplorerNotebookActionID = 'explorer.notebook'; CommandsRegistry.registerCommand(ExplorerNotebookActionID, (accessor, context: ManageActionContext) => { const instantiationService = accessor.get(IInstantiationService); const connectedContext: ConnectedContext = { connectionProfile: context.profile }; - instantiationService.createInstance(NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL).run({ connectionProfile: connectedContext.connectionProfile, isConnectionNode: false, nodeInfo: undefined }); + instantiationService.createInstance(NewNotebookAction).run(accessor, { connectionProfile: connectedContext.connectionProfile, isConnectionNode: false, nodeInfo: undefined }); }); MenuRegistry.appendMenuItem(MenuId.ExplorerWidgetContext, { @@ -190,7 +178,7 @@ const toggleTabFocusAction = new ToggleTabFocusModeAction(); CommandsRegistry.registerCommand({ id: TOGGLE_TAB_FOCUS_COMMAND_ID, handler: (accessor) => { - toggleTabFocusAction.run(accessor, undefined); + toggleTabFocusAction.run(accessor); } }); @@ -290,7 +278,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_TAB_FOCUS_COMMAND_ID, - title: toggleTabFocusAction.label, + title: ToggleTabFocusModeAction.LABEL, }, when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookEditor.ID)) }); diff --git a/src/sql/workbench/contrib/notebook/browser/notebookActions.ts b/src/sql/workbench/contrib/notebook/browser/notebookActions.ts index e9f8b408f3..98de8306fa 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookActions.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookActions.ts @@ -36,11 +36,14 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { KernelsLanguage } from 'sql/workbench/services/notebook/common/notebookConstants'; import { INotebookViews } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews'; -import { Schemas } from 'vs/base/common/network'; +import { FileAccess, Schemas } from 'vs/base/common/network'; import { CONFIG_WORKBENCH_ENABLEPREVIEWFEATURES, CONFIG_WORKBENCH_USEVSCODENOTEBOOKS } from 'sql/workbench/common/constants'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Task } from 'sql/workbench/services/tasks/browser/tasksRegistry'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; const msgLoading = localize('loading', "Loading kernels..."); export const msgChanging = localize('changing', "Changing kernel..."); @@ -904,34 +907,40 @@ export class NewNotebookTask extends Task { } } -export class NewNotebookAction extends Action { - +export class NewNotebookAction extends Action2 { public static readonly ID = 'notebook.command.new'; + public static readonly LABEL_ORG = 'New Notebook'; public static readonly LABEL = localize('newNotebookAction', "New Notebook"); - constructor( - id: string, - label: string, - @IObjectExplorerService private objectExplorerService: IObjectExplorerService, - @IAdsTelemetryService private _telemetryService: IAdsTelemetryService, - @INotebookService private _notebookService: INotebookService, - @IConfigurationService private _configurationService: IConfigurationService, - @ICommandService private _commandService: ICommandService, - ) { - super(id, label); - this.class = 'notebook-action new-notebook'; + constructor() { + super({ + id: NewNotebookAction.ID, + icon: { + light: FileAccess.asBrowserUri(`sql/workbench/services/connection/browser/media/light/new_notebook.svg`), + dark: FileAccess.asBrowserUri(`sql/workbench/services/connection/browser/media/dark/new_notebook_inverse.svg`) + }, + title: { value: NewNotebookAction.LABEL, original: NewNotebookAction.LABEL_ORG }, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyN }, + f1: true + }); } - override async run(context?: azdata.ObjectExplorerContext): Promise<void> { + public override async run(accessor: ServicesAccessor, context?: azdata.ObjectExplorerContext): Promise<void> { + const objectExplorerService = accessor.get(IObjectExplorerService); + const telemetryService = accessor.get(IAdsTelemetryService); + const notebookService = accessor.get(INotebookService); + const configurationService = accessor.get(IConfigurationService); + const commandService = accessor.get(ICommandService); + let connProfile: azdata.IConnectionProfile; if (context && context.nodeInfo) { - let node = await this.objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath); + let node = await objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath); connProfile = TreeUpdateUtils.getConnectionProfile(node).toIConnectionProfile(); } else if (context && context.connectionProfile) { connProfile = context.connectionProfile; } - await openNewNotebook(this._telemetryService, this._notebookService, this._configurationService, this._commandService, connProfile); + await openNewNotebook(telemetryService, notebookService, configurationService, commandService, connProfile); } } diff --git a/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.ts b/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.ts index 38052f1f5f..d399d8778a 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookEditor.component.ts @@ -202,7 +202,7 @@ export class NotebookEditorComponent extends AngularDisposable { let secondary: IAction[] = []; let notebookBarMenu = this.menuService.createMenu(MenuId.NotebookToolbar, this.contextKeyService); let groups = notebookBarMenu.getActions({ arg: null, shouldForwardArgs: true }); - fillInActions(groups, { primary, secondary }, false, g => g === '', Number.MAX_SAFE_INTEGER, (action: SubmenuAction, group: string, groupSize: number) => group === undefined || group === ''); + fillInActions(groups, { primary, secondary }, false, g => g === '', (action: SubmenuAction, group: string, groupSize: number) => group === undefined || group === ''); } private get modelFactory(): IModelFactory { diff --git a/src/sql/workbench/contrib/notebook/browser/notebookEditor.ts b/src/sql/workbench/contrib/notebook/browser/notebookEditor.ts index f827803d73..cb8d4341dd 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookEditor.ts @@ -152,7 +152,7 @@ export class NotebookEditor extends EditorPane implements IFindNotebookControlle /** * @param parent Called to create the editor in the parent element. */ - public createEditor(parent: HTMLElement): void { + protected createEditor(parent: HTMLElement): void { this._overlay = document.createElement('div'); this._overlay.className = 'overlayWidgets monaco-editor'; this._overlay.style.width = '100%'; diff --git a/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet.ts b/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet.ts index c9d7a96284..e9ce80bc74 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet.ts @@ -26,7 +26,7 @@ import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneCont import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { NotebookSearchWidget, INotebookExplorerSearchOptions } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearchWidget'; import * as Constants from 'sql/workbench/common/constants'; -import { IChangeEvent } from 'vs/workbench/contrib/search/common/searchModel'; +import { IChangeEvent } from 'vs/workbench/contrib/search/browser/searchModel'; import { Delayer } from 'vs/base/common/async'; import { ITextQuery, IPatternInfo } from 'vs/workbench/services/search/common/search'; import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -42,6 +42,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Codicon } from 'vs/base/common/codicons'; +import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; export const VIEWLET_ID = 'workbench.view.notebooks'; @@ -122,7 +123,9 @@ export class NotebookExplorerViewPaneContainer extends ViewPaneContainer { isWholeWords: false, searchHistory: [], replaceHistory: [], - preserveCase: false + preserveCase: false, + inputBoxStyles: defaultInputBoxStyles, + toggleStyles: defaultToggleStyles })); diff --git a/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearch.ts b/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearch.ts index ac43e952ae..8cff7d3680 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearch.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearch.ts @@ -12,9 +12,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { ISearchWorkbenchService, Match, FileMatch, SearchModel, IChangeEvent, searchMatchComparer, RenderableMatch, FolderMatch, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { ISearchWorkbenchService, Match, FileMatch, SearchModel, IChangeEvent, searchMatchComparer, RenderableMatch, FolderMatch, SearchResult } from 'vs/workbench/contrib/search/browser/searchModel'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/browser/replace'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -30,7 +30,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import * as dom from 'vs/base/browser/dom'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { ISearchComplete, SearchCompletionExitCode, ITextQuery, SearchSortOrder, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -47,6 +47,8 @@ import { SearchUIState } from 'vs/workbench/contrib/search/common/search'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ILogService } from 'vs/platform/log/common/log'; const $ = dom.$; @@ -87,10 +89,17 @@ export class NotebookSearchView extends SearchView { @IOpenerService openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @ICommandService commandService: ICommandService, + @INotebookService notebookService: INotebookService, + @ILogService logService: ILogService, @IAdsTelemetryService private _telemetryService: IAdsTelemetryService, ) { - super(options, fileService, editorService, codeEditorService, progressService, notificationService, dialogService, commandService, contextViewService, instantiationService, viewDescriptorService, configurationService, contextService, searchWorkbenchService, contextKeyService, replaceService, textFileService, preferencesService, themeService, searchHistoryService, contextMenuService, menuService, accessibilityService, keybindingService, storageService, openerService, telemetryService); + super(options, fileService, editorService, codeEditorService, progressService, + notificationService, dialogService, commandService, contextViewService, instantiationService, + viewDescriptorService, configurationService, contextService, searchWorkbenchService, contextKeyService, + replaceService, textFileService, preferencesService, themeService, searchHistoryService, contextMenuService, + menuService, accessibilityService, keybindingService, storageService, openerService, telemetryService, + notebookService, logService); this.memento = new Memento(this.id, storageService); this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE, StorageTarget.MACHINE); @@ -148,13 +157,13 @@ export class NotebookSearchView extends SearchView { e.browserEvent.stopPropagation(); const actions: IAction[] = []; - const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true }, actions); + createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true }, actions); this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, getActions: () => actions, getActionsContext: () => e.element, - onHide: () => dispose(actionsDisposable) + onHide: () => { } }); } @@ -184,7 +193,7 @@ export class NotebookSearchView extends SearchView { } } - override renderBody(parent: HTMLElement): void { + protected override renderBody(parent: HTMLElement): void { super.callRenderBody(parent); this.container = dom.append(parent, dom.$('.search-view')); diff --git a/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearchWidget.ts b/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearchWidget.ts index 8dbbf11ed5..0ce0da27ec 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearchWidget.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookExplorer/notebookSearchWidget.ts @@ -11,18 +11,18 @@ import { IFocusTracker, append, $, trackFocus } from 'vs/base/browser/dom'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Emitter, Event } from 'vs/base/common/event'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import * as Constants from 'sql/workbench/common/constants'; -import { IMessage } from 'vs/base/browser/ui/inputbox/inputBox'; -import { appendKeyBindingLabel } from 'vs/workbench/contrib/search/browser/searchActions'; -import { attachFindReplaceInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { IInputBoxStyles, IMessage } from 'vs/base/browser/ui/inputbox/inputBox'; +import { appendKeyBindingLabel } from 'vs/workbench/contrib/search/browser/searchActionsBase'; import { isMacintosh } from 'vs/base/common/platform'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ContextScopedFindInput } from 'vs/platform/history/browser/contextScopedHistoryWidget'; +import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; +import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; export interface INotebookExplorerSearchOptions { value?: string; @@ -33,6 +33,8 @@ export interface INotebookExplorerSearchOptions { replaceHistory?: string[]; preserveCase?: boolean; showContextToggle?: boolean; + inputBoxStyles: IInputBoxStyles; + toggleStyles: IToggleStyles; } const ctrlKeyMod = (isMacintosh ? KeyMod.WinCtrl : KeyMod.CtrlCmd); @@ -83,7 +85,6 @@ export class NotebookSearchWidget extends Widget { container: HTMLElement, options: INotebookExplorerSearchOptions, @IContextViewService private readonly contextViewService: IContextViewService, - @IThemeService private readonly themeService: IThemeService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IKeybindingService private readonly keyBindingService: IKeybindingService, @IClipboardService private readonly clipboardServce: IClipboardService, @@ -133,16 +134,17 @@ export class NotebookSearchWidget extends Widget { label: localize('label.Search', 'Search: Type Search Term and press Enter to search or Escape to cancel'), validation: (value: string) => this.validateSearchInput(value), placeholder: localize('search.placeHolder', "Search"), - appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId), this.keyBindingService), - appendWholeWordsLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId), this.keyBindingService), - appendRegexLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleRegexCommandId), this.keyBindingService), + appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId)), + appendWholeWordsLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId)), + appendRegexLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleRegexCommandId)), history: options.searchHistory, - flexibleHeight: true + flexibleHeight: true, + inputBoxStyles: options.inputBoxStyles ?? defaultInputBoxStyles, + toggleStyles: options.toggleStyles ?? defaultToggleStyles }; const searchInputContainer = append(parent, $('.search-container.input-box')); - this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.contextKeyService, true)); - this._register(attachFindReplaceInputBoxStyler(this.searchInput, this.themeService)); + this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.contextKeyService)); this.searchInput.onKeyDown((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyDown(keyboardEvent)); this.searchInput.setValue(options.value || ''); this.searchInput.setRegex(!!options.isRegex); diff --git a/src/sql/workbench/contrib/notebook/browser/notebookViews/insertCellsModal.ts b/src/sql/workbench/contrib/notebook/browser/notebookViews/insertCellsModal.ts index 4eee4df656..4dec78ac10 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookViews/insertCellsModal.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookViews/insertCellsModal.ts @@ -24,7 +24,6 @@ import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/boots import { localize } from 'vs/nls'; import { NotebookViewsExtension } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViewsExtension'; import { InsertCellsModule } from 'sql/workbench/contrib/notebook/browser/notebookViews/insertCellsModal.module'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { truncate } from 'vs/base/common/strings'; import { toJpeg } from 'html-to-image'; import { IComponentEventArgs } from 'sql/platform/dashboard/browser/interfaces'; @@ -97,7 +96,6 @@ export class InsertCellsModal extends Modal { public viewModel: CellOptionsModel; private _submitButton: Button; - private _cancelButton: Button; private _maxTitleLength: number = 20; private _moduleRef?: NgModuleRef<typeof InsertCellsModule>; @@ -210,10 +208,7 @@ export class InsertCellsModal extends Modal { super.render(); this._submitButton = this.addFooterButton(localize('insertCellsModal.Insert', "Insert"), () => this.onSubmitHandler()); - this._cancelButton = this.addFooterButton(localize('insertCellsModal.Cancel', "Cancel"), () => this.onCancelHandler(), 'right', true); - - this._register(attachButtonStyler(this._submitButton, this._themeService)); - this._register(attachButtonStyler(this._cancelButton, this._themeService)); + this.addFooterButton(localize('insertCellsModal.Cancel', "Cancel"), () => this.onCancelHandler(), 'right', true); attachModalDialogStyler(this, this._themeService); this.validate(); diff --git a/src/sql/workbench/contrib/notebook/browser/notebookViews/viewOptionsModal.ts b/src/sql/workbench/contrib/notebook/browser/notebookViews/viewOptionsModal.ts index d7d4459b29..9bcd789e3a 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebookViews/viewOptionsModal.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebookViews/viewOptionsModal.ts @@ -15,17 +15,16 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import * as DOM from 'vs/base/browser/dom'; import { attachInputBoxStyler } from 'sql/platform/theme/common/styler'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { localize } from 'vs/nls'; import { IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { INotebookView } from 'sql/workbench/services/notebook/browser/notebookViews/notebookViews'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export class ViewOptionsModal extends Modal { private _submitButton: Button; - private _cancelButton: Button; private _optionsMap: { [name: string]: InputBox | Checkbox } = {}; private _viewNameInput: InputBox; @@ -81,7 +80,8 @@ export class ViewOptionsModal extends Modal { return undefined; } }, - ariaLabel: localize('viewOptionsModal.name', "View Name") + ariaLabel: localize('viewOptionsModal.name', "View Name"), + inputBoxStyles: defaultInputBoxStyles }); } @@ -97,11 +97,9 @@ export class ViewOptionsModal extends Modal { super.render(); this._submitButton = this.addFooterButton(localize('save', "Save"), () => this.onSubmitHandler()); - this._cancelButton = this.addFooterButton(localize('cancel', "Cancel"), () => this.onCancelHandler(), 'right', true); + this.addFooterButton(localize('cancel', "Cancel"), () => this.onCancelHandler(), 'right', true); this._register(attachInputBoxStyler(this._viewNameInput!, this._themeService)); - this._register(attachButtonStyler(this._submitButton, this._themeService)); - this._register(attachButtonStyler(this._cancelButton, this._themeService)); this._register(this._viewNameInput.onDidChange(v => this.validate())); diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/notebookMarkdown.ts b/src/sql/workbench/contrib/notebook/browser/outputs/notebookMarkdown.ts index 38c0335021..3cc9bf7905 100644 --- a/src/sql/workbench/contrib/notebook/browser/outputs/notebookMarkdown.ts +++ b/src/sql/workbench/contrib/notebook/browser/outputs/notebookMarkdown.ts @@ -109,7 +109,7 @@ export class NotebookMarkdownRenderer { uri.scheme === Schemas.data || uri.scheme === Schemas.attachment || uri.scheme === Schemas.vscodeFileResource)) { - uri = FileAccess.asBrowserUri(URI.file(href)); + uri = FileAccess.uriToBrowserUri(URI.file(href)); } attributes.push(`src="${uri.toString(true)}"`); } diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/plotlyOutput.component.ts b/src/sql/workbench/contrib/notebook/browser/outputs/plotlyOutput.component.ts index 6bd866d3d1..d81a072af6 100644 --- a/src/sql/workbench/contrib/notebook/browser/outputs/plotlyOutput.component.ts +++ b/src/sql/workbench/contrib/notebook/browser/outputs/plotlyOutput.component.ts @@ -13,7 +13,9 @@ import { ICellModel } from 'sql/workbench/services/notebook/browser/models/model import { MimeModel } from 'sql/workbench/services/notebook/browser/outputs/mimemodel'; import { getErrorMessage } from 'vs/base/common/errors'; import * as Plotly from 'plotly.js'; -import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellWidgets'; +import * as DOM from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IDimension } from 'vs/base/browser/dom'; type ObjectType = object; interface FigureLayout extends ObjectType { @@ -147,3 +149,67 @@ export class PlotlyOutputComponent extends AngularDisposable implements IMimeCom } } + +// port the below code from vscode cellWidgets file since it was deleted +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: ResizeObserver; + 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!); + } + + override dispose(): void { + this.observer.disconnect(); + super.dispose(); + } +} + +export function getResizesObserver(referenceDomElement: HTMLElement | null, dimension: IDimension | undefined, changeCallback: () => void): IResizeObserver { + return new BrowserResizeObserver(referenceDomElement, dimension, changeCallback); +} diff --git a/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts index a2e7e49142..636691b51c 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/cellToolbarActions.test.ts @@ -20,7 +20,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell'; import { IProductService } from 'vs/platform/product/common/productService'; -import { Separator } from 'vs/base/common/actions'; +import { Action, Separator } from 'vs/base/common/actions'; import { ICellModel, INotebookModelOptions } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import { NotebookModel } from 'sql/workbench/services/notebook/browser/models/notebookModel'; import { NotebookEditorContentLoader } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; @@ -36,11 +36,12 @@ import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; import { Emitter } from 'vs/base/common/event'; import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { mock } from 'vs/base/test/common/mock'; +import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; suite('CellToolbarActions', function (): void { suite('removeDuplicatedAndStartingSeparators', function (): void { @@ -59,12 +60,12 @@ suite('CellToolbarActions', function (): void { assert(actions.length === 3); }); test('Array with only separators is cleared', function (): void { - const actions = [new Separator(), new Separator(), new Separator()]; + const actions = <Action[]>[new Separator(), new Separator(), new Separator()]; removeDuplicatedAndStartingSeparators(actions); assert(actions.length === 0); }); test('Array with separators not on the ends is unchanged', function (): void { - const actions = [ + const actions = <Action[]>[ TypeMoq.Mock.ofType(RunCellsAction).object, new Separator(), TypeMoq.Mock.ofType(AddCellFromContextAction).object, @@ -75,7 +76,7 @@ suite('CellToolbarActions', function (): void { assert(actions.length === 5); }); test('Duplicate separators are removed', function (): void { - const actions = [ + const actions = <Action[]>[ TypeMoq.Mock.ofType(RunCellsAction).object, new Separator(), new Separator(), @@ -89,7 +90,7 @@ suite('CellToolbarActions', function (): void { assert(actions.length === 5); }); test('Starting and ending separators are removed', function (): void { - const actions = [ + const actions = <Action[]>[ new Separator(), new Separator(), TypeMoq.Mock.ofType(RunCellsAction).object, diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookEditor.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookEditor.test.ts index 82876639c8..5ca4746a12 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookEditor.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookEditor.test.ts @@ -28,7 +28,6 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { IOverlayWidget, IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser'; -import { getRandomString } from 'vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; import { DidUninstallExtensionEvent, IExtensionManagementService, InstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -59,6 +58,20 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { FindReplaceStateChangedEvent, INewFindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService'; +import { CharCode } from 'vs/base/common/charCode'; + +function getRandomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function getRandomString(minLength: number, maxLength: number): string { + const length = getRandomInt(minLength, maxLength); + let r = ''; + for (let i = 0; i < length; i++) { + r += String.fromCharCode(getRandomInt(CharCode.a, CharCode.z)); + } + return r; +} class NotebookModelStub extends stubs.NotebookModelStub { public contentChangedEmitter = new Emitter<NotebookContentChange>(); diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookService.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookService.test.ts index 2e60144e54..063a6e6718 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookService.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookService.test.ts @@ -498,8 +498,8 @@ suite.skip('NotebookService:', function (): void { const methodName = 'removeContributedProvidersFromCache'; await notebookService.registrationComplete; const providerId = 'providerId1'; - extensionServiceMock.setup(x => x.getExtensions()).returns(() => { - return Promise.resolve([ + extensionServiceMock.setup(x => x.extensions).returns(() => { + return [ <IExtensionDescription>{ name: 'testExtension', publisher: 'Test', @@ -521,7 +521,7 @@ suite.skip('NotebookService:', function (): void { forceReload: true, targetPlatform: undefined } - ]); + ]; }); const extensionIdentifier = (<DidUninstallExtensionEvent>{ identifier: { @@ -542,7 +542,7 @@ suite.skip('NotebookService:', function (): void { }; errorHandler.setUnexpectedErrorHandler(onUnexpectedErrorVerifier); await notebookService.registrationComplete; - extensionServiceMock.setup(x => x.getExtensions()).returns(() => undefined); + extensionServiceMock.setup(x => x.extensions).returns(() => undefined); const extensionIdentifier = (<DidUninstallExtensionEvent>{ identifier: { id: 'id1' diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts index 92517c9d87..8839bfde94 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewModel.test.ts @@ -35,8 +35,9 @@ import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; import { Emitter } from 'vs/base/common/event'; import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; let initialNotebookContent: nb.INotebookContents = { cells: [{ diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts index 8359a22f5b..4d8304602c 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsActions.test.ts @@ -35,9 +35,10 @@ import { INotebookService, SQL_NOTEBOOK_PROVIDER } from 'sql/workbench/services/ import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; import { Emitter } from 'vs/base/common/event'; import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils'; -import { ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; let initialNotebookContent: nb.INotebookContents = { cells: [{ diff --git a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts index b9a2554159..4517fa7ada 100644 --- a/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts +++ b/src/sql/workbench/contrib/notebook/test/browser/notebookViewsExtension.test.ts @@ -38,8 +38,9 @@ import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; import { Emitter } from 'vs/base/common/event'; import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; let initialNotebookContent: nb.INotebookContents = { cells: [{ diff --git a/src/sql/workbench/contrib/notebook/test/calloutDialog/linkCalloutDialog.test.ts b/src/sql/workbench/contrib/notebook/test/calloutDialog/linkCalloutDialog.test.ts index a68f756bd0..c67e71803a 100644 --- a/src/sql/workbench/contrib/notebook/test/calloutDialog/linkCalloutDialog.test.ts +++ b/src/sql/workbench/contrib/notebook/test/calloutDialog/linkCalloutDialog.test.ts @@ -18,7 +18,7 @@ import { Deferred } from 'sql/base/common/promise'; import { escapeLabel, escapeUrl, unquoteText } from 'sql/workbench/contrib/notebook/browser/calloutDialog/common/utils'; import { IDialogProperties } from 'sql/workbench/browser/modal/modal'; -suite('Link Callout Dialog', function (): void { +suite.skip('Link Callout Dialog', function (): void { // {{SQL CARBON TODO}} - reenable these tests let layoutService: ILayoutService; let themeService: IThemeService; let telemetryService: IAdsTelemetryService; @@ -33,7 +33,7 @@ suite('Link Callout Dialog', function (): void { contextKeyService = new MockContextKeyService(); }); - test('Should return empty markdown on cancel', async function (): Promise<void> { + test('Should return empty markdown on cancel', async function (done): Promise<void> { let linkCalloutDialog = new LinkCalloutDialog('Title', 'below', defaultDialogProperties, 'defaultLabel', 'defaultLinkLabel', undefined, themeService, layoutService, telemetryService, contextKeyService, undefined, undefined, undefined); linkCalloutDialog.render(); @@ -50,9 +50,10 @@ suite('Link Callout Dialog', function (): void { assert.strictEqual(result.insertUnescapedLinkLabel, 'defaultLabel', 'Label not returned correctly'); assert.strictEqual(result.insertUnescapedLinkUrl, undefined, 'URL not returned correctly'); assert.strictEqual(result.insertEscapedMarkdown, '', 'Markdown not returned correctly'); + done(); }); - test('Should return expected values on insert', async function (): Promise<void> { + test('Should return expected values on insert', async function (done): Promise<void> { const defaultLabel = 'defaultLabel'; const sampleUrl = 'https://www.aka.ms/azuredatastudio'; let linkCalloutDialog = new LinkCalloutDialog('Title', 'below', defaultDialogProperties, defaultLabel, sampleUrl, @@ -129,7 +130,7 @@ suite('Link Callout Dialog', function (): void { assert.strictEqual(unquoteText(undefined), undefined); }); - test('Should return absolute file link properly', async function (): Promise<void> { + test('Should return absolute file link properly', async function (done): Promise<void> { const defaultLabel = 'defaultLabel'; const sampleUrl = 'C:/Test/Test.ipynb'; let linkCalloutDialog = new LinkCalloutDialog('Title', 'below', defaultDialogProperties, defaultLabel, sampleUrl, @@ -149,6 +150,7 @@ suite('Link Callout Dialog', function (): void { assert.strictEqual(result.insertUnescapedLinkLabel, defaultLabel, 'Label not returned correctly'); assert.strictEqual(result.insertUnescapedLinkUrl, sampleUrl, 'URL not returned correctly'); assert.strictEqual(result.insertEscapedMarkdown, `[${defaultLabel}](${sampleUrl})`, 'Markdown not returned correctly'); + done(); }); test('Should return relative file link properly', async function (): Promise<void> { diff --git a/src/sql/workbench/contrib/notebook/test/common/nbformat.test.ts b/src/sql/workbench/contrib/notebook/test/common/nbformat.test.ts index b394381cd8..117c9289e2 100644 --- a/src/sql/workbench/contrib/notebook/test/common/nbformat.test.ts +++ b/src/sql/workbench/contrib/notebook/test/common/nbformat.test.ts @@ -7,7 +7,7 @@ import { nbformat } from 'sql/workbench/services/notebook/common/nbformat'; import * as assert from 'assert'; suite('nbformat', function (): void { - let sampleOutput: nbformat.IOutput = { + let sampleOutput: nbformat.IOutput = <any>{ data: undefined, ename: '', evalue: undefined, diff --git a/src/sql/workbench/contrib/notebook/test/common/quickInputServiceMock.ts b/src/sql/workbench/contrib/notebook/test/common/quickInputServiceMock.ts index b2193e9e08..93e05cff39 100644 --- a/src/sql/workbench/contrib/notebook/test/common/quickInputServiceMock.ts +++ b/src/sql/workbench/contrib/notebook/test/common/quickInputServiceMock.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as Types from 'vs/base/common/types'; import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputService, IQuickNavigateConfiguration, IQuickPick, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { Event } from 'vs/base/common/event'; @@ -20,7 +19,7 @@ export class MockQuickInputService implements IQuickInputService { public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: true }, token?: CancellationToken): Promise<T[]>; public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: IPickOptions<T> & { canPickMany: false }, token?: CancellationToken): Promise<T>; public pick<T extends IQuickPickItem>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options?: Omit<IPickOptions<T>, 'canPickMany'>, token?: CancellationToken): Promise<T | undefined> { - if (Types.isArray(picks)) { + if (Array.isArray(picks)) { return Promise.resolve(<any>{ label: 'selectedPick', description: 'pick description', value: 'selectedPick' }); } else { return Promise.resolve(undefined); diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts index be16efbefe..58f31e3175 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/cell.test.ts @@ -21,13 +21,14 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; -import { ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { ControlType, IChartOption } from 'sql/workbench/contrib/charts/browser/chartOptions'; import { CellModel } from 'sql/workbench/services/notebook/browser/models/cell'; import { IModelContentChangedEvent } from 'vs/editor/common/textModelEvents'; import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { mock } from 'vs/base/test/common/mock'; +import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; let instantiationService: TestInstantiationService; diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts index 910e935a88..3435fd876a 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/contentManagers.test.ts @@ -57,14 +57,15 @@ suite('Local Content Manager', function (): void { override async readFile(resource: URI, options?: IReadFileOptions | undefined): Promise<IFileContent> { const content = await promisify(fs.readFile)(resource.fsPath); - return { name: ',', size: 0, etag: '', mtime: 0, value: VSBuffer.fromString(content.toString()), resource, ctime: 0, readonly: false }; + return { name: ',', size: 0, etag: '', mtime: 0, value: VSBuffer.fromString(content.toString()), resource, ctime: 0, readonly: false, locked: false }; } override async writeFile(resource: URI, bufferOrReadable: VSBuffer | VSBufferReadable, options?: IWriteFileOptions): Promise<IFileStatWithMetadata> { await pfs.Promises.writeFile(resource.fsPath, bufferOrReadable.toString()); return { resource: resource, mtime: 0, etag: '', size: 0, name: '', isDirectory: false, ctime: 0, isFile: true, isSymbolicLink: false, - readonly: false, children: [] + readonly: false, children: [], + locked: false }; } }; diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts index 6462172e2e..5f6abf49fc 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookEditorModel.test.ts @@ -28,7 +28,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { TestLifecycleService, TestTextFileService, workbenchInstantiationService, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; +import { ITestTextFileEditorModelManager, TestLifecycleService, TestTextFileService, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Range } from 'vs/editor/common/core/range'; import { nb } from 'azdata'; import { Emitter } from 'vs/base/common/event'; @@ -354,7 +354,8 @@ suite('Notebook Editor Model', function (): void { isFlush: false, isRedoing: false, isUndoing: false, - versionId: 2 + versionId: 2, + isEolChange: false } }; @@ -400,7 +401,8 @@ suite('Notebook Editor Model', function (): void { isFlush: false, isRedoing: false, isUndoing: false, - versionId: 2 + versionId: 2, + isEolChange: false } }; @@ -528,7 +530,8 @@ suite('Notebook Editor Model', function (): void { isFlush: false, isRedoing: false, isUndoing: false, - versionId: 2 + versionId: 2, + isEolChange: false } }; @@ -545,7 +548,8 @@ suite('Notebook Editor Model', function (): void { isFlush: false, isRedoing: false, isUndoing: false, - versionId: 3 + versionId: 3, + isEolChange: false } }; @@ -588,7 +592,8 @@ suite('Notebook Editor Model', function (): void { isFlush: false, isRedoing: false, isUndoing: false, - versionId: 2 + versionId: 2, + isEolChange: false } }; @@ -612,7 +617,8 @@ suite('Notebook Editor Model', function (): void { isFlush: false, isRedoing: false, isUndoing: false, - versionId: 3 + versionId: 3, + isEolChange: false } }; @@ -659,7 +665,8 @@ suite('Notebook Editor Model', function (): void { isFlush: false, isRedoing: false, isUndoing: false, - versionId: 2 + versionId: 2, + isEolChange: false } }; @@ -997,7 +1004,7 @@ suite('Notebook Editor Model', function (): void { async function createTextEditorModel(self: Mocha.Context): Promise<NotebookEditorModel> { let textFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(self, defaultUri.toString()), 'utf8', undefined); - (<TestTextFileEditorModelManager>accessor.textFileService.files).add(textFileEditorModel.resource, textFileEditorModel); + (<ITestTextFileEditorModelManager>accessor.textFileService.files).add(textFileEditorModel.resource, textFileEditorModel); await textFileEditorModel.resolve(); return new NotebookEditorModel(defaultUri, textFileEditorModel, mockNotebookService.object, testResourcePropertiesService); } @@ -1029,7 +1036,8 @@ suite('Notebook Editor Model', function (): void { isFlush: false, isRedoing: false, isUndoing: false, - versionId: 2 + versionId: 2, + isEolChange: false } }; diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts index b42d058b1b..f8ca9137d0 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookFindModel.test.ts @@ -35,8 +35,9 @@ import { NBFORMAT, NBFORMAT_MINOR } from 'sql/workbench/common/constants'; import { Emitter } from 'vs/base/common/event'; import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; let expectedNotebookContent: nb.INotebookContents = { cells: [{ diff --git a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts index e2e7084208..15ebcebf6b 100644 --- a/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts +++ b/src/sql/workbench/contrib/notebook/test/electron-browser/notebookModel.test.ts @@ -46,7 +46,8 @@ import { Emitter } from 'vs/base/common/event'; import { IStandardKernelWithProvider } from 'sql/workbench/services/notebook/browser/models/notebookUtils'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ICommandService, NullCommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; let expectedNotebookContent: nb.INotebookContents = { cells: [{ diff --git a/src/sql/workbench/contrib/notebook/test/stubs.ts b/src/sql/workbench/contrib/notebook/test/stubs.ts index 6d5f560d20..023f9eed29 100644 --- a/src/sql/workbench/contrib/notebook/test/stubs.ts +++ b/src/sql/workbench/contrib/notebook/test/stubs.ts @@ -605,58 +605,58 @@ export class NodeStub implements Node { replaceChild<T extends Node>(newChild: Node, oldChild: T): T { throw new Error('Method not implemented.'); } - get ATTRIBUTE_NODE(): number { + get ATTRIBUTE_NODE(): any { throw new Error('Method not implemented.'); } - get CDATA_SECTION_NODE(): number { + get CDATA_SECTION_NODE(): any { throw new Error('Method not implemented.'); } - get COMMENT_NODE(): number { + get COMMENT_NODE(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_FRAGMENT_NODE(): number { + get DOCUMENT_FRAGMENT_NODE(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_NODE(): number { + get DOCUMENT_NODE(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_POSITION_CONTAINED_BY(): number { + get DOCUMENT_POSITION_CONTAINED_BY(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_POSITION_CONTAINS(): number { + get DOCUMENT_POSITION_CONTAINS(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_POSITION_DISCONNECTED(): number { + get DOCUMENT_POSITION_DISCONNECTED(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_POSITION_FOLLOWING(): number { + get DOCUMENT_POSITION_FOLLOWING(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC(): number { + get DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_POSITION_PRECEDING(): number { + get DOCUMENT_POSITION_PRECEDING(): any { throw new Error('Method not implemented.'); } - get DOCUMENT_TYPE_NODE(): number { + get DOCUMENT_TYPE_NODE(): any { throw new Error('Method not implemented.'); } - get ELEMENT_NODE(): number { + get ELEMENT_NODE(): any { throw new Error('Method not implemented.'); } - get ENTITY_NODE(): number { + get ENTITY_NODE(): any { throw new Error('Method not implemented.'); } - get ENTITY_REFERENCE_NODE(): number { + get ENTITY_REFERENCE_NODE(): any { throw new Error('Method not implemented.'); } - get NOTATION_NODE(): number { + get NOTATION_NODE(): any { throw new Error('Method not implemented.'); } - get PROCESSING_INSTRUCTION_NODE(): number { + get PROCESSING_INSTRUCTION_NODE(): any { throw new Error('Method not implemented.'); } - get TEXT_NODE(): number { + get TEXT_NODE(): any { throw new Error('Method not implemented.'); } addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void { diff --git a/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts b/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts index 5245668a4b..cec4b1020e 100644 --- a/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts +++ b/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts @@ -8,7 +8,7 @@ import * as errors from 'vs/base/common/errors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import Severity from 'vs/base/common/severity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachButtonStyler, attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { ISelectionEvent, ITree } from 'sql/base/parts/tree/browser/tree'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; @@ -50,6 +50,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { FilterDialog } from 'sql/workbench/services/objectExplorer/browser/filterDialog/filterDialog'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export const CONTEXT_SERVER_TREE_VIEW = new RawContextKey<ServerTreeViewView>('serverTreeView.view', ServerTreeViewView.all); export const CONTEXT_SERVER_TREE_HAS_CONNECTIONS = new RawContextKey<boolean>('serverTreeView.hasConnections', false); @@ -186,9 +187,8 @@ export class ServerTreeView extends Disposable implements IServerTreeView { if (!this._connectionManagementService.hasRegisteredServers()) { this._buttonSection = append(container, $('.button-section')); - const connectButton = new Button(this._buttonSection); + const connectButton = new Button(this._buttonSection, defaultButtonStyles); connectButton.label = localize('serverTree.newConnection', "New Connection"); - this._register(attachButtonStyler(connectButton, this._themeService)); this._register(connectButton.onDidClick(() => { this._connectionManagementService.showConnectionDialog(undefined, { showDashboard: true, diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts index 62031a1c00..c0490c3feb 100644 --- a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts +++ b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts @@ -16,13 +16,13 @@ import { TestErrorMessageService } from 'sql/platform/errorMessage/test/common/t import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServerTreeView } from 'sql/workbench/contrib/objectExplorer/browser/serverTreeView'; import * as LocalizedConstants from 'sql/workbench/services/connection/browser/localizedConstants'; -import { ObjectExplorerService, ObjectExplorerNodeEventArgs, ServerTreeViewView } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; +import { ObjectExplorerService, ObjectExplorerNodeEventArgs, ServerTreeViewView, IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService'; import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode'; import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType'; import { Emitter, Event } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; import { ObjectExplorerActionsContext } from 'sql/workbench/services/objectExplorer/browser/objectExplorerActions'; -import { IConnectionResult, IConnectionParams } from 'sql/platform/connection/common/connectionManagement'; +import { IConnectionResult, IConnectionParams, IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { TreeSelectionHandler } from 'sql/workbench/services/objectExplorer/browser/treeSelectionHandler'; import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService'; import { UNSAVED_GROUP_ID, mssqlProviderName, AuthenticationType } from 'sql/platform/connection/common/constants'; @@ -37,7 +37,7 @@ import { TestConfigurationService } from 'sql/platform/connection/test/common/te import { ServerTreeDataSource } from 'sql/workbench/services/objectExplorer/browser/serverTreeDataSource'; import { Tree } from 'sql/base/parts/tree/browser/treeImpl'; import { AsyncServerTree } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree'; -import { ConsoleLogger, LogService } from 'vs/platform/log/common/log'; +import { ConsoleLogger } from 'vs/platform/log/common/log'; import { TestAccessibilityService } from 'vs/platform/accessibility/test/common/testAccessibilityService'; import { TestEditorService } from 'vs/workbench/test/browser/workbenchTestServices'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -45,6 +45,8 @@ import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogSer import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { workbenchTreeDataPreamble } from 'vs/platform/list/browser/listService'; +import { LogService } from 'vs/platform/log/common/logService'; +import { StaticServiceAccessor } from 'vs/editor/contrib/wordPartOperations/test/browser/utils'; suite('SQL Connection Tree Action tests', () => { let errorMessageService: TypeMoq.Mock<TestErrorMessageService>; @@ -86,15 +88,20 @@ suite('SQL Connection Tree Action tests', () => { /** * Creates a mock dialog service that and select the choice at the given index when show is called. - * @param choiceIndex index of the button in the dialog to be selected starting from 0. + * @param choice index of the button in the dialog to be selected starting from 0. * @returns */ - function createDialogService(choiceIndex: number): TypeMoq.Mock<IDialogService> { + function createDialogService(choice: number | boolean): TypeMoq.Mock<IDialogService> { let dialogService = TypeMoq.Mock.ofType<IDialogService>(TestDialogService, TypeMoq.MockBehavior.Loose); dialogService.callBase = true; - dialogService.setup(x => x.show(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { - return Promise.resolve({ - choice: choiceIndex + dialogService.setup(x => x.prompt(TypeMoq.It.isAny())).returns(() => { + return <any>Promise.resolve({ + choice: choice + }) + }); + dialogService.setup(x => x.confirm(TypeMoq.It.isAny())).returns(() => { + return <any>Promise.resolve({ + confirmed: choice }) }); return dialogService; @@ -287,9 +294,11 @@ suite('SQL Connection Tree Action tests', () => { let connectionManagementService = createConnectionManagementService(true, undefined); connectionManagementService.setup(x => x.showConnectionDialog(undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns( () => new Promise<void>((resolve, reject) => resolve())); - let connectionTreeAction: AddServerAction = new AddServerAction(AddServerAction.ID, AddServerAction.LABEL, connectionManagementService.object); + let connectionTreeAction: AddServerAction = new AddServerAction(); let conProfGroup = new ConnectionProfileGroup('testGroup', undefined, 'testGroup', undefined, undefined); - await connectionTreeAction.run(conProfGroup); + + const serviceAccessor = new StaticServiceAccessor().withService(IConnectionManagementService, connectionManagementService.object); + await connectionTreeAction.run(serviceAccessor, conProfGroup); connectionManagementService.verify(x => x.showConnectionDialog(undefined, TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); }); @@ -305,8 +314,10 @@ suite('SQL Connection Tree Action tests', () => { serverTreeView.setup(x => x.view).returns(() => ServerTreeViewView.all); const mockObjectExplorerService = TypeMoq.Mock.ofType(ObjectExplorerService); mockObjectExplorerService.setup(x => x.getServerTreeView()).returns(() => serverTreeView.object); - let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(ActiveConnectionsFilterAction.ID, ActiveConnectionsFilterAction.SHOW_ACTIVE_CONNECTIONS_LABEL, mockObjectExplorerService.object); - return connectionTreeAction.run().then((value) => { + let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(); + + const serviceAccessor = new StaticServiceAccessor().withService(IObjectExplorerService, mockObjectExplorerService.object); + return connectionTreeAction.run(serviceAccessor).then((value) => { serverTreeView.verify(x => x.showFilteredTree(ServerTreeViewView.active), TypeMoq.Times.once()); }); }); @@ -323,8 +334,10 @@ suite('SQL Connection Tree Action tests', () => { serverTreeView.setup(x => x.view).returns(() => ServerTreeViewView.active); const mockObjectExplorerService = TypeMoq.Mock.ofType(ObjectExplorerService); mockObjectExplorerService.setup(x => x.getServerTreeView()).returns(() => serverTreeView.object); - let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(ActiveConnectionsFilterAction.ID, ActiveConnectionsFilterAction.SHOW_ACTIVE_CONNECTIONS_LABEL, mockObjectExplorerService.object); - return connectionTreeAction.run().then((value) => { + + let connectionTreeAction: ActiveConnectionsFilterAction = new ActiveConnectionsFilterAction(); + const serviceAccessor = new StaticServiceAccessor().withService(IObjectExplorerService, mockObjectExplorerService.object); + return connectionTreeAction.run(serviceAccessor).then((value) => { serverTreeView.verify(x => x.refreshTree(), TypeMoq.Times.once()); }); }); @@ -351,7 +364,7 @@ suite('SQL Connection Tree Action tests', () => { DeleteConnectionAction.DELETE_CONNECTION_LABEL, connection, connectionManagementService.object, - createDialogService(0).object); // Select 'Yes' on the modal dialog + createDialogService(true).object); // Select 'Yes' on the modal dialog return connectionAction.run().then((value) => { connectionManagementService.verify(x => x.deleteConnection(TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce()); @@ -381,7 +394,7 @@ suite('SQL Connection Tree Action tests', () => { DeleteConnectionAction.DELETE_CONNECTION_LABEL, connection, connectionManagementService.object, - createDialogService(1).object); // Selecting 'No' on the modal dialog + createDialogService(false).object); // Selecting 'No' on the modal dialog await connectionAction.run(); connectionManagementService.verify(x => x.deleteConnection(TypeMoq.It.isAny()), TypeMoq.Times.never()); @@ -395,7 +408,7 @@ suite('SQL Connection Tree Action tests', () => { DeleteConnectionAction.DELETE_CONNECTION_LABEL, conProfGroup, connectionManagementService.object, - createDialogService(0).object); // Select 'Yes' on the modal dialog + createDialogService(true).object); // Select 'Yes' on the modal dialog return connectionAction.run().then((value) => { connectionManagementService.verify(x => x.deleteConnectionGroup(TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce()); diff --git a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts index 9153fed37a..34cde72c82 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerEditor.ts @@ -30,7 +30,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ContextKeyExpr, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import * as types from 'vs/base/common/types'; -import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachSelectBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -336,7 +336,7 @@ export class ProfilerEditor extends EditorPane { } }); this._profilerTableEditor = this._instantiationService.createInstance(ProfilerTableEditor); - this._profilerTableEditor.createEditor(profilerTableContainer); + (<any>this._profilerTableEditor).createEditor(profilerTableContainer); this._profilerTableEditor.onSelectedRowsChanged((e, args) => { let data = this.input.data.getItem(args.rows[0]); if (data) { diff --git a/src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts b/src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts index 76539dbd97..5817927bb6 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerFindWidget.ts @@ -12,7 +12,7 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; +import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Sash, ISashEvent, Orientation, IVerticalSashLayoutProvider } from 'vs/base/browser/ui/sash/sash'; @@ -20,11 +20,11 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import * as colors from 'vs/platform/theme/common/colorRegistry'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { IDisposable } from 'vs/base/common/lifecycle'; import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/browser/findState'; import { CONTEXT_FIND_INPUT_FOCUSED, FIND_IDS } from 'vs/editor/contrib/find/browser/findModel'; +import { defaultInputBoxStyles, defaultToggleStyles } from 'vs/platform/theme/browser/defaultStyles'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -291,19 +291,20 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } private _applyTheme(theme: IColorTheme) { - let inputStyles: IFindInputStyles = { - inputActiveOptionBorder: theme.getColor(colors.inputActiveOptionBorder), - inputBackground: theme.getColor(colors.inputBackground), - inputForeground: theme.getColor(colors.inputForeground), - inputBorder: theme.getColor(colors.inputBorder), - inputValidationInfoBackground: theme.getColor(colors.inputValidationInfoBackground), - inputValidationInfoBorder: theme.getColor(colors.inputValidationInfoBorder), - inputValidationWarningBackground: theme.getColor(colors.inputValidationWarningBackground), - inputValidationWarningBorder: theme.getColor(colors.inputValidationWarningBorder), - inputValidationErrorBackground: theme.getColor(colors.inputValidationErrorBackground), - inputValidationErrorBorder: theme.getColor(colors.inputValidationErrorBorder) - }; - this._findInput.style(inputStyles); + // {{SQL CARBON TODO}} - styles + // let inputStyles: IFindInputStyles = { + // inputActiveOptionBorder: theme.getColor(colors.inputActiveOptionBorder), + // inputBackground: theme.getColor(colors.inputBackground), + // inputForeground: theme.getColor(colors.inputForeground), + // inputBorder: theme.getColor(colors.inputBorder), + // inputValidationInfoBackground: theme.getColor(colors.inputValidationInfoBackground), + // inputValidationInfoBorder: theme.getColor(colors.inputValidationInfoBorder), + // inputValidationWarningBackground: theme.getColor(colors.inputValidationWarningBackground), + // inputValidationWarningBorder: theme.getColor(colors.inputValidationWarningBorder), + // inputValidationErrorBackground: theme.getColor(colors.inputValidationErrorBackground), + // inputValidationErrorBorder: theme.getColor(colors.inputValidationErrorBorder) + // }; + // this._findInput.style(inputStyles); } // ----- Public @@ -367,7 +368,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL private _buildFindPart(): HTMLElement { // Find input - this._findInput = this._register(new FindInput(null, this._contextViewProvider, true, { + this._findInput = this._register(new FindInput(null, this._contextViewProvider, { width: FIND_INPUT_AREA_WIDTH, label: NLS_FIND_INPUT_LABEL, placeholder: NLS_FIND_INPUT_PLACEHOLDER, @@ -389,7 +390,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL } catch (e) { return { content: e.message }; } - } + }, + toggleStyles: defaultToggleStyles, + inputBoxStyles: defaultInputBoxStyles })); this._findInput.setRegex(!!this._state.isRegex); this._findInput.setCaseSensitive(!!this._state.matchCase); diff --git a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts index 4b3c1b3d5b..6ac9074281 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerResourceEditor.ts @@ -55,7 +55,7 @@ export class ProfilerResourceEditor extends AbstractTextCodeEditor<editorCommon. super(ProfilerResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorService, editorGroupService, fileService); } - public override createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor { + protected override createEditorControl(parent: HTMLElement, configuration: IEditorOptions): editorCommon.IEditor { this.editorControl = this.instantiationService.createInstance(ProfilerResourceCodeEditor, parent, configuration, {}); return this.editorControl; diff --git a/src/sql/workbench/contrib/profiler/browser/profilerTableEditor.ts b/src/sql/workbench/contrib/profiler/browser/profilerTableEditor.ts index 750f7f065c..f170819732 100644 --- a/src/sql/workbench/contrib/profiler/browser/profilerTableEditor.ts +++ b/src/sql/workbench/contrib/profiler/browser/profilerTableEditor.ts @@ -82,7 +82,7 @@ export class ProfilerTableEditor extends EditorPane implements IProfilerControll this._showStatusBarItem = true; } - public createEditor(parent: HTMLElement): void { + protected createEditor(parent: HTMLElement): void { this._overlay = document.createElement('div'); this._overlay.className = 'overlayWidgets'; diff --git a/src/sql/workbench/contrib/query/browser/flavorStatus.ts b/src/sql/workbench/contrib/query/browser/flavorStatus.ts index 8c46854d16..c192891b36 100644 --- a/src/sql/workbench/contrib/query/browser/flavorStatus.ts +++ b/src/sql/workbench/contrib/query/browser/flavorStatus.ts @@ -7,7 +7,6 @@ import 'vs/css!./media/flavorStatus'; import { Disposable } from 'vs/base/common/lifecycle'; import { IEditorCloseEvent } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Action } from 'vs/base/common/actions'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import * as nls from 'vs/nls'; @@ -20,6 +19,8 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/browser/statusbar'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; export interface ISqlProviderEntry extends IQuickPickItem { providerId: string; @@ -171,57 +172,60 @@ export class SqlFlavorStatusbarItem extends Disposable implements IWorkbenchCont } } -export class ChangeFlavorAction extends Action { +export class ChangeFlavorAction extends Action2 { public static ID = 'sql.action.editor.changeProvider'; + public static LABEL_ORG = 'Change SQL Engine Provider'; public static LABEL = nls.localize('changeSqlProvider', "Change SQL Engine Provider"); - constructor( - actionId: string, - actionLabel: string, - @IEditorService private _editorService: IEditorService, - @IQuickInputService private _quickInputService: IQuickInputService, - @INotificationService private _notificationService: INotificationService, - @IConnectionManagementService private _connectionManagementService: IConnectionManagementService - ) { - super(actionId, actionLabel); + constructor() { + super({ + id: ChangeFlavorAction.ID, + title: { value: ChangeFlavorAction.LABEL, original: ChangeFlavorAction.LABEL_ORG }, + f1: true + }); } - public override run(): Promise<any> { - let activeEditor = this._editorService.activeEditorPane; + override async run(accessor: ServicesAccessor): Promise<void> { + const editorService = accessor.get(IEditorService); + const quickInputService = accessor.get(IQuickInputService); + const notificationService = accessor.get(INotificationService); + const connectionManagementService = accessor.get(IConnectionManagementService); + + let activeEditor = editorService.activeEditorPane; let currentUri = activeEditor?.input.resource?.toString(true); - if (this._connectionManagementService.isConnected(currentUri)) { - let currentProvider = this._connectionManagementService.getProviderIdFromUri(currentUri); - return this._showMessage(Severity.Info, nls.localize('alreadyConnected', + if (connectionManagementService.isConnected(currentUri)) { + let currentProvider = connectionManagementService.getProviderIdFromUri(currentUri); + return this._showMessage(notificationService, Severity.Info, nls.localize('alreadyConnected', "A connection using engine {0} exists. To change please disconnect or change connection", currentProvider)); } const editorWidget = getCodeEditor(activeEditor.getControl()); if (!editorWidget) { - return this._showMessage(Severity.Info, nls.localize('noEditor', "No text editor active at this time")); + return this._showMessage(notificationService, Severity.Info, nls.localize('noEditor', "No text editor active at this time")); } // TODO #1334 use connectionManagementService.GetProviderNames here. The challenge is that the credentials provider is returned // so we need a way to filter this using a capabilities check, with isn't yet implemented - let providerNameToDisplayNameMap = this._connectionManagementService.providerNameToDisplayNameMap; - let providerOptions = Object.keys(this._connectionManagementService.getUniqueConnectionProvidersByNameMap(providerNameToDisplayNameMap)).map(p => new SqlProviderEntry(p)); + let providerNameToDisplayNameMap = connectionManagementService.providerNameToDisplayNameMap; + let providerOptions = Object.keys(connectionManagementService.getUniqueConnectionProvidersByNameMap(providerNameToDisplayNameMap)).map(p => new SqlProviderEntry(p)); - return this._quickInputService.pick(providerOptions, { placeHolder: nls.localize('pickSqlProvider', "Select Language Provider") }).then(provider => { + return quickInputService.pick(providerOptions, { placeHolder: nls.localize('pickSqlProvider', "Select Language Provider") }).then(provider => { if (provider) { - let activeEditor = this._editorService.activeEditorPane.getControl(); + let activeEditor = editorService.activeEditorPane.getControl(); const editorWidget = getCodeEditor(activeEditor); if (editorWidget) { if (currentUri) { - this._connectionManagementService.doChangeLanguageFlavor(currentUri, 'sql', provider.providerId); + connectionManagementService.doChangeLanguageFlavor(currentUri, 'sql', provider.providerId); } } } }); } - private _showMessage(sev: Severity, message: string): Promise<any> { - this._notificationService.notify({ + private _showMessage(notificationService: INotificationService, sev: Severity, message: string): Promise<any> { + notificationService.notify({ severity: sev, message: message }); diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts index 06ea0a19f5..3643043c28 100644 --- a/src/sql/workbench/contrib/query/browser/gridPanel.ts +++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts @@ -60,6 +60,7 @@ import { queryEditorNullBackground } from 'sql/platform/theme/common/colorRegist import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; import { GridRange } from 'sql/base/common/gridRange'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; const ROW_HEIGHT = 29; const HEADER_HEIGHT = 26; @@ -491,7 +492,6 @@ export abstract class GridTableBase<T> extends Disposable implements IView, IQue } if (action) { action.run(this.generateContext()); - action.dispose(); } } @@ -619,10 +619,11 @@ export abstract class GridTableBase<T> extends Disposable implements IView, IQue }; this.table.rerenderGrid(); })); - this.filterPlugin = new HeaderFilter(this.contextViewService, this.notificationService, { + this.filterPlugin = new HeaderFilter({ disabledFilterMessage: localize('resultsGrid.maxRowCountExceeded', "Max row count for filtering/sorting has been exceeded. To update it, navigate to User Settings and change the setting: 'queryEditor.results.inMemoryDataProcessingThreshold'"), - refreshColumns: !autoSizeOnRender // The auto size columns plugin refreshes the columns so we don't need to refresh twice if both plugins are on. - }); + refreshColumns: !autoSizeOnRender, // The auto size columns plugin refreshes the columns so we don't need to refresh twice if both plugins are on. + buttonStyles: defaultButtonStyles + }, this.contextViewService, this.notificationService,); this._register(attachTableFilterStyler(this.filterPlugin, this.themeService)); this._register(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { const nullBackground = theme.getColor(queryEditorNullBackground); diff --git a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts index 4fa9f82b2c..ac916b6b26 100644 --- a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts +++ b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts @@ -24,6 +24,15 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { QueryEditorInput } from 'sql/workbench/common/editor/query/queryEditorInput'; import { ClipboardData, IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { ParseSyntaxCommandId } from 'sql/workbench/contrib/query/browser/queryActions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import * as queryContext from 'sql/workbench/contrib/query/common/queryContext'; + +const QueryEditorVisibleCondition = ContextKeyExpr.has(queryContext.queryEditorVisibleId); const singleQuote = '\''; @@ -60,97 +69,100 @@ function escapeSqlString(input: string, escapeChar: string) { /** * Locates the active editor and call focus() on the editor if it is a QueryEditor. */ -export class FocusOnCurrentQueryKeyboardAction extends Action { +export class FocusOnCurrentQueryKeyboardAction extends Action2 { public static ID = 'focusOnCurrentQueryKeyboardAction'; + public static LABEL_ORG = 'Focus on Current Query'; public static LABEL = nls.localize('focusOnCurrentQueryKeyboardAction', "Focus on Current Query"); - constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: FocusOnCurrentQueryKeyboardAction.ID, + title: { value: FocusOnCurrentQueryKeyboardAction.LABEL, original: FocusOnCurrentQueryKeyboardAction.LABEL_ORG }, + f1: true, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO } + }); } - public override run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor) { editor.focus(); } - return Promise.resolve(null); } } /** * Locates the active editor and calls runQuery() on the editor if it is a QueryEditor. */ -export class RunQueryKeyboardAction extends Action { +export class RunQueryKeyboardAction extends Action2 { public static ID = 'runQueryKeyboardAction'; + public static LABEL_ORG = 'Run Query'; public static LABEL = nls.localize('runQueryKeyboardAction', "Run Query"); - constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: RunQueryKeyboardAction.ID, + title: { value: RunQueryKeyboardAction.LABEL, original: RunQueryKeyboardAction.LABEL_ORG }, + f1: true, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F5 }, + }); } - public override run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor || editor instanceof EditDataEditor) { editor.runQuery(); } - return Promise.resolve(null); } } /** * Locates the active editor and calls runCurrentQuery() on the editor if it is a QueryEditor. */ -export class RunCurrentQueryKeyboardAction extends Action { +export class RunCurrentQueryKeyboardAction extends Action2 { public static ID = 'runCurrentQueryKeyboardAction'; + public static LABEL_ORG = 'Run Current Query'; public static LABEL = nls.localize('runCurrentQueryKeyboardAction', "Run Current Query"); constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService ) { - super(id, label); - this.enabled = true; + super({ + id: RunCurrentQueryKeyboardAction.ID, + title: { value: RunCurrentQueryKeyboardAction.LABEL, original: RunCurrentQueryKeyboardAction.LABEL_ORG }, + f1: true, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.F5 }, + }); } - public override run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor) { editor.runCurrentQuery(); } - return Promise.resolve(null); } } -export class CopyQueryWithResultsKeyboardAction extends Action { +export class CopyQueryWithResultsKeyboardAction extends Action2 { public static ID = 'copyQueryWithResultsKeyboardAction'; + public static LABEL_ORG = 'Copy Query With Results'; public static LABEL = nls.localize('copyQueryWithResultsKeyboardAction', "Copy Query With Results"); - constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService, - @IClipboardService private _clipboardService: IClipboardService, - @IQueryModelService protected readonly queryModelService: IQueryModelService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(id, label); + constructor() { + super({ + id: CopyQueryWithResultsKeyboardAction.ID, + title: { value: CopyQueryWithResultsKeyboardAction.LABEL, original: CopyQueryWithResultsKeyboardAction.LABEL_ORG }, + f1: true, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyV) } + }); } - public async getFormattedResults(editor): Promise<ClipboardData> { - let queryRunner = this.queryModelService.getQueryRunner(editor.input.uri); + public async getFormattedResults(editor, queryModelService: IQueryModelService): Promise<ClipboardData> { + let queryRunner = queryModelService.getQueryRunner(editor.input.uri); let allResults = ''; let allHtmlResults = ''; @@ -186,10 +198,15 @@ export class CopyQueryWithResultsKeyboardAction extends Action { return { text: allResults, html: allHtmlResults }; } - public override async run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + override async run(accessor: ServicesAccessor): Promise<void> { + const editorService = accessor.get(IEditorService); + const clipboardService = accessor.get(IClipboardService); + const queryModelService = accessor.get(IQueryModelService); + const notificationService = accessor.get(INotificationService); + + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor) { - let allResults = await this.getFormattedResults(editor); + let allResults = await this.getFormattedResults(editor, queryModelService); let queryText = editor.getAllText(); let data = { @@ -197,9 +214,9 @@ export class CopyQueryWithResultsKeyboardAction extends Action { html: `<div style="font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 10pt;">${escape(queryText).replace(/\r\n|\n|\r/gm, '<br/>')}</div>${allResults.html}` }; - await this._clipboardService.write(data); + await clipboardService.write(data); - this.notificationService.notify({ + notificationService.notify({ severity: Severity.Info, message: nls.localize('queryActions.queryResultsCopySuccess', "Successfully copied query and results.") }); @@ -207,21 +224,23 @@ export class CopyQueryWithResultsKeyboardAction extends Action { } } -export class EstimatedExecutionPlanKeyboardAction extends Action { +export class EstimatedExecutionPlanKeyboardAction extends Action2 { public static ID = 'estimatedExecutionPlanKeyboardAction'; + public static LABEL_ORG = 'Display Estimated Execution Plan'; public static LABEL = nls.localize('estimatedExecutionPlanKeyboardAction', "Display Estimated Execution Plan"); - constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: EstimatedExecutionPlanKeyboardAction.ID, + title: { value: EstimatedExecutionPlanKeyboardAction.LABEL, original: EstimatedExecutionPlanKeyboardAction.LABEL_ORG }, + f1: true, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyL } + }); } - public override async run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor) { let queryEditor = <QueryEditor>editor; editor.input.runQuery(queryEditor.getSelection(), { displayEstimatedQueryPlan: true }); @@ -229,131 +248,139 @@ export class EstimatedExecutionPlanKeyboardAction extends Action { } } -export class ToggleActualPlanKeyboardAction extends Action { +export class ToggleActualPlanKeyboardAction extends Action2 { public static ID = 'ToggleActualPlanKeyboardAction'; + public static LABEL_ORG = 'Enable/Disable Actual Execution Plan'; public static LABEL = nls.localize('ToggleActualPlanKeyboardAction', "Enable/Disable Actual Execution Plan"); - constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: ToggleActualPlanKeyboardAction.ID, + title: { value: ToggleActualPlanKeyboardAction.LABEL, original: ToggleActualPlanKeyboardAction.LABEL_ORG }, + f1: true, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyM } + }); } - public override run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor) { let toActualPlanState = !editor.input.state.isActualExecutionPlanMode; editor.input.state.isActualExecutionPlanMode = toActualPlanState; } - - return Promise.resolve(null); } } /** * Locates the active editor and calls cancelQuery() on the editor if it is a QueryEditor. */ -export class CancelQueryKeyboardAction extends Action { - +export class CancelQueryKeyboardAction extends Action2 { public static ID = 'cancelQueryKeyboardAction'; + public static LABEL_ORG = 'Cancel Query'; public static LABEL = nls.localize('cancelQueryKeyboardAction', "Cancel Query"); - constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: CancelQueryKeyboardAction.ID, + title: { value: CancelQueryKeyboardAction.LABEL, original: CancelQueryKeyboardAction.LABEL_ORG }, + f1: true, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Alt | KeyCode.PauseBreak } + }); } - public override run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor || editor instanceof EditDataEditor) { editor.cancelQuery(); } - return Promise.resolve(null); } } /** * Refresh the IntelliSense cache */ -export class RefreshIntellisenseKeyboardAction extends Action { +export class RefreshIntellisenseKeyboardAction extends Action2 { public static ID = 'refreshIntellisenseKeyboardAction'; + public static LABEL_ORG = 'Refresh IntelliSense Cache'; public static LABEL = nls.localize('refreshIntellisenseKeyboardAction', "Refresh IntelliSense Cache"); - constructor( - id: string, - label: string, - @IConnectionManagementService private connectionManagementService: IConnectionManagementService, - @IEditorService private editorService: IEditorService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: RefreshIntellisenseKeyboardAction.ID, + title: { value: RefreshIntellisenseKeyboardAction.LABEL, original: RefreshIntellisenseKeyboardAction.LABEL_ORG }, + f1: true + }); } - public override run(): Promise<void> { - const editor = this.editorService.activeEditor; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const connectionManagementService = accessor.get(IConnectionManagementService); + const editor = editorService.activeEditor; if (editor instanceof QueryEditorInput) { - this.connectionManagementService.rebuildIntelliSenseCache(editor.uri); + connectionManagementService.rebuildIntelliSenseCache(editor.uri); } else if (editor instanceof NotebookInput && editor.notebookModel?.activeCell) { - this.connectionManagementService.rebuildIntelliSenseCache(editor.notebookModel.activeCell.cellUri.toString(true)); + connectionManagementService.rebuildIntelliSenseCache(editor.notebookModel.activeCell.cellUri.toString(true)); } - return Promise.resolve(null); } } - /** * Hide the query results */ -export class ToggleQueryResultsKeyboardAction extends Action { +export class ToggleQueryResultsKeyboardAction extends Action2 { public static ID = 'toggleQueryResultsKeyboardAction'; + public static LABEL_ORG = 'Toggle Query Results'; public static LABEL = nls.localize('toggleQueryResultsKeyboardAction', "Toggle Query Results"); - constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: ToggleQueryResultsKeyboardAction.ID, + title: { value: ToggleQueryResultsKeyboardAction.LABEL, original: ToggleQueryResultsKeyboardAction.LABEL_ORG }, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KeyR + }, + precondition: QueryEditorVisibleCondition + }); } - public override run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor) { editor.toggleResultsEditorVisibility(); } - return Promise.resolve(null); } } - - /** * Toggle the focus between query editor and results pane */ -export class ToggleFocusBetweenQueryEditorAndResultsAction extends Action { +export class ToggleFocusBetweenQueryEditorAndResultsAction extends Action2 { public static ID = 'ToggleFocusBetweenQueryEditorAndResultsAction'; + public static LABEL_ORG = 'Toggle Focus Between Query And Results'; public static LABEL = nls.localize('ToggleFocusBetweenQueryEditorAndResultsAction', "Toggle Focus Between Query And Results"); - constructor( - id: string, - label: string, - @IEditorService private _editorService: IEditorService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: ToggleFocusBetweenQueryEditorAndResultsAction.ID, + title: { value: ToggleFocusBetweenQueryEditorAndResultsAction.LABEL, original: ToggleFocusBetweenQueryEditorAndResultsAction.LABEL_ORG }, + f1: true, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KeyF + }, + precondition: QueryEditorVisibleCondition + }); } - public override async run(): Promise<void> { - const editor = this._editorService.activeEditorPane; + run(accessor: ServicesAccessor): void { + const editorService = accessor.get(IEditorService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor) { editor.toggleFocusBetweenQueryEditorAndResults(); } @@ -520,43 +547,45 @@ export class RunQueryShortcutAction extends Action { /** * Action class that parses the query string in the current SQL text document. */ -export class ParseSyntaxAction extends Action { +export class ParseSyntaxAction extends Action2 { + public static LABEL_ORG = 'Parse Query'; public static LABEL = nls.localize('parseSyntaxLabel', "Parse Query"); - constructor( - id: string, - label: string, - @IConnectionManagementService private readonly connectionManagementService: IConnectionManagementService, - @IQueryManagementService private readonly queryManagementService: IQueryManagementService, - @IEditorService private readonly editorService: IEditorService, - @INotificationService private readonly notificationService: INotificationService - ) { - super(id, label); - this.enabled = true; + constructor() { + super({ + id: ParseSyntaxCommandId, + title: { value: ParseSyntaxAction.LABEL, original: ParseSyntaxAction.LABEL_ORG }, + f1: true, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyP } + }); } - public override async run(): Promise<void> { - const editor = this.editorService.activeEditorPane; + override async run(accessor: ServicesAccessor): Promise<void> { + const editorService = accessor.get(IEditorService); + const connectionManagementService = accessor.get(IConnectionManagementService); + const queryManagementService = accessor.get(IQueryManagementService); + const notificationService = accessor.get(INotificationService); + const editor = editorService.activeEditorPane; if (editor instanceof QueryEditor) { if (!editor.isEditorEmpty()) { - if (this.isConnected(editor)) { + if (this.isConnected(editor, connectionManagementService)) { let text = editor.getSelectionText(); if (text === '') { text = editor.getAllText(); } - const result = await this.queryManagementService.parseSyntax(editor.input.uri, text); + const result = await queryManagementService.parseSyntax(editor.input.uri, text); if (result && result.parseable) { - this.notificationService.notify({ + notificationService.notify({ severity: Severity.Info, message: nls.localize('queryActions.parseSyntaxSuccess', "Successfully parsed the query.") }); } else if (result && result.errors.length > 0) { - this.notificationService.error( + notificationService.error( nls.localize('queryActions.parseSyntaxFailure', "Failed to parse the query: {0}", result.errors.map((err, idx) => `${idx + 1}. ${err} `).join(' '))); } } else { - this.notificationService.notify({ + notificationService.notify({ severity: Severity.Error, message: nls.localize('queryActions.notConnected', "Please connect to a server before running this action.") }); @@ -569,10 +598,10 @@ export class ParseSyntaxAction extends Action { * Returns the URI of the given editor if it is not undefined and is connected. * Public for testing only. */ - private isConnected(editor: QueryEditor): boolean { + private isConnected(editor: QueryEditor, connectionManagementService: IConnectionManagementService): boolean { if (!editor || !editor.input) { return false; } - return this.connectionManagementService.isConnected(editor.input.uri); + return connectionManagementService.isConnected(editor.input.uri); } } diff --git a/src/sql/workbench/contrib/query/browser/media/queryActions.css b/src/sql/workbench/contrib/query/browser/media/queryActions.css index 8e61b70aea..30c8118d19 100644 --- a/src/sql/workbench/contrib/query/browser/media/queryActions.css +++ b/src/sql/workbench/contrib/query/browser/media/queryActions.css @@ -5,4 +5,4 @@ .databaseListDropdown { min-width: 150px; -} \ No newline at end of file +} diff --git a/src/sql/workbench/contrib/query/browser/messagePanel.ts b/src/sql/workbench/contrib/query/browser/messagePanel.ts index 9c79f6ab71..a9528014e8 100644 --- a/src/sql/workbench/contrib/query/browser/messagePanel.ts +++ b/src/sql/workbench/contrib/query/browser/messagePanel.ts @@ -9,11 +9,11 @@ import { IQueryMessage } from 'sql/workbench/services/query/common/query'; import { ITreeRenderer, IDataSource, ITreeNode, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { generateUuid } from 'vs/base/common/uuid'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; -import { isArray, isString } from 'vs/base/common/types'; +import { isString } from 'vs/base/common/types'; import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { $, Dimension, createStyleSheet, addStandardDisposableGenericMouseDownListener } from 'vs/base/browser/dom'; import { resultsErrorColor } from 'sql/platform/theme/common/colors'; @@ -213,7 +213,7 @@ export class MessagePanel extends Disposable { } private onMessage(message: IQueryMessage | IQueryMessage[], setInput: boolean = false) { - if (isArray(message)) { + if (Array.isArray(message)) { this.model.messages.push(...message); } else { this.model.messages.push(message); diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index 0bfdfd5744..a960d25f8d 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -6,9 +6,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IConfigurationRegistry, Extensions as ConfigExtensions, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; -import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -32,7 +31,7 @@ import { FileQueryEditorInput } from 'sql/workbench/contrib/query/browser/fileQu import { FileQueryEditorSerializer, QueryEditorLanguageAssociation, UntitledQueryEditorSerializer } from 'sql/workbench/contrib/query/browser/queryEditorFactory'; import { UntitledQueryEditorInput } from 'sql/base/query/browser/untitledQueryEditorInput'; import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/services/languageAssociation/common/languageAssociation'; -import { NewQueryTask, OE_NEW_QUERY_ACTION_ID, DE_NEW_QUERY_COMMAND_ID, CATEGORIES, ParseSyntaxCommandId } from 'sql/workbench/contrib/query/browser/queryActions'; +import { NewQueryTask, OE_NEW_QUERY_ACTION_ID, DE_NEW_QUERY_COMMAND_ID } from 'sql/workbench/contrib/query/browser/queryActions'; import { TreeNodeContextKey } from 'sql/workbench/services/objectExplorer/common/treeNodeContextKey'; import { MssqlNodeContext } from 'sql/workbench/services/objectExplorer/browser/mssqlNodeContext'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; @@ -68,8 +67,6 @@ Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane) Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane) .registerEditorPane(EditorPaneDescriptor.create(QueryEditor, QueryEditor.ID, QueryEditor.LABEL), [new SyncDescriptor(FileQueryEditorInput), new SyncDescriptor(UntitledQueryEditorInput)]); -const actionRegistry = <IWorkbenchActionRegistry>Registry.as(ActionExtensions.WorkbenchActions); - new NewQueryTask().registerTask(); MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, { @@ -109,15 +106,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerWidgetContext, { }); // Query Actions -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - RunQueryKeyboardAction, - RunQueryKeyboardAction.ID, - RunQueryKeyboardAction.LABEL, - { primary: KeyCode.F5 } - ), - RunQueryKeyboardAction.LABEL -); +registerAction2(RunQueryKeyboardAction); // Only show Run Query if the active editor is a query editor. MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { @@ -126,120 +115,30 @@ MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { when: ContextKeyEqualsExpr.create('activeEditor', 'workbench.editor.queryEditor') }); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - RunCurrentQueryKeyboardAction, - RunCurrentQueryKeyboardAction.ID, - RunCurrentQueryKeyboardAction.LABEL, - { primary: KeyMod.CtrlCmd | KeyCode.F5 } - ), - RunCurrentQueryKeyboardAction.LABEL -); +registerAction2(RunCurrentQueryKeyboardAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - EstimatedExecutionPlanKeyboardAction, - EstimatedExecutionPlanKeyboardAction.ID, - EstimatedExecutionPlanKeyboardAction.LABEL, - { primary: KeyMod.CtrlCmd | KeyCode.KeyL } - ), - EstimatedExecutionPlanKeyboardAction.LABEL, - CATEGORIES.ExecutionPlan.value -); +registerAction2(EstimatedExecutionPlanKeyboardAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - ToggleActualPlanKeyboardAction, - ToggleActualPlanKeyboardAction.ID, - ToggleActualPlanKeyboardAction.LABEL, - { primary: KeyMod.CtrlCmd | KeyCode.KeyM } - ), - ToggleActualPlanKeyboardAction.LABEL, - CATEGORIES.ExecutionPlan.value -); +registerAction2(ToggleActualPlanKeyboardAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - CopyQueryWithResultsKeyboardAction, - CopyQueryWithResultsKeyboardAction.ID, - CopyQueryWithResultsKeyboardAction.LABEL, - { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyV) } - ), - CopyQueryWithResultsKeyboardAction.LABEL -); +registerAction2(CopyQueryWithResultsKeyboardAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - CancelQueryKeyboardAction, - CancelQueryKeyboardAction.ID, - CancelQueryKeyboardAction.LABEL, - { primary: KeyMod.Alt | KeyCode.PauseBreak } - ), - CancelQueryKeyboardAction.LABEL -); +registerAction2(CancelQueryKeyboardAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - RefreshIntellisenseKeyboardAction, - RefreshIntellisenseKeyboardAction.ID, - RefreshIntellisenseKeyboardAction.LABEL - ), - RefreshIntellisenseKeyboardAction.LABEL -); +registerAction2(RefreshIntellisenseKeyboardAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - FocusOnCurrentQueryKeyboardAction, - FocusOnCurrentQueryKeyboardAction.ID, - FocusOnCurrentQueryKeyboardAction.LABEL, - { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO } - ), - FocusOnCurrentQueryKeyboardAction.LABEL -); +registerAction2(FocusOnCurrentQueryKeyboardAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - ParseSyntaxAction, - ParseSyntaxCommandId, - ParseSyntaxAction.LABEL, - { primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KeyP } - ), - ParseSyntaxAction.LABEL -); +registerAction2(ParseSyntaxAction); // Grid actions -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - ToggleQueryResultsKeyboardAction, - ToggleQueryResultsKeyboardAction.ID, - ToggleQueryResultsKeyboardAction.LABEL, - { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KeyR }, - QueryEditorVisibleCondition - ), - ToggleQueryResultsKeyboardAction.LABEL -); +registerAction2(ToggleQueryResultsKeyboardAction); -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - ToggleFocusBetweenQueryEditorAndResultsAction, - ToggleFocusBetweenQueryEditorAndResultsAction.ID, - ToggleFocusBetweenQueryEditorAndResultsAction.LABEL, - { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KeyF }, - QueryEditorVisibleCondition - ), - ToggleFocusBetweenQueryEditorAndResultsAction.LABEL -); +registerAction2(ToggleFocusBetweenQueryEditorAndResultsAction); // Register Flavor Action -actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - ChangeFlavorAction, - ChangeFlavorAction.ID, - ChangeFlavorAction.LABEL - ), - 'Change Language Flavor' -); +registerAction2(ChangeFlavorAction); const GridKeyBindingWeight = 900; diff --git a/src/sql/workbench/contrib/query/browser/queryActions.ts b/src/sql/workbench/contrib/query/browser/queryActions.ts index 76a7b39566..6a49a34d4f 100644 --- a/src/sql/workbench/contrib/query/browser/queryActions.ts +++ b/src/sql/workbench/contrib/query/browser/queryActions.ts @@ -48,6 +48,7 @@ import { gen3Version, sqlDataWarehouse } from 'sql/platform/connection/common/co import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; /** * Action class that query-based Actions will extend. This base class automatically handles activating and @@ -200,7 +201,7 @@ export class RunQueryAction extends QueryTaskbarAction { @IQueryModelService protected readonly queryModelService: IQueryModelService, @IConnectionManagementService connectionManagementService: IConnectionManagementService, @INotificationService private readonly notificationService: INotificationService, - @ICommandService private readonly commandService?: ICommandService + @ICommandService private readonly commandService: ICommandService ) { super(connectionManagementService, editor, RunQueryAction.ID, RunQueryAction.EnabledClass); this.label = nls.localize('runQueryLabel', "Run"); @@ -899,7 +900,7 @@ export class ExportAsNotebookAction extends QueryTaskbarAction { @IConnectionManagementService connectionManagementService: IConnectionManagementService, @ICommandService private _commandService: ICommandService ) { - super(connectionManagementService, editor, ExportAsNotebookAction.ID, Codicon.notebook.classNames); + super(connectionManagementService, editor, ExportAsNotebookAction.ID, ThemeIcon.asClassName(Codicon.notebook)); this.label = nls.localize('queryEditor.exportSqlAsNotebookLabel', "To Notebook"); this.tooltip = nls.localize('queryEditor.exportSqlAsNotebookTooltip', "Export as Notebook"); } @@ -919,7 +920,7 @@ export const CATEGORIES = { export const ParseSyntaxCommandId = 'parseQueryAction'; export class ParseSyntaxTaskbarAction extends Action { constructor(@ICommandService private _commandService: ICommandService) { - super(ParseSyntaxCommandId, nls.localize('queryEditor.parse', "Parse"), Codicon.check.classNames); + super(ParseSyntaxCommandId, nls.localize('queryEditor.parse', "Parse"), ThemeIcon.asClassName(Codicon.check)); } public override async run(): Promise<void> { diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts index f336a50be9..db06a13b59 100644 --- a/src/sql/workbench/contrib/query/browser/queryEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts @@ -154,7 +154,7 @@ export class QueryEditor extends EditorPane { /** * Called to create the editor in the parent element. */ - public createEditor(parent: HTMLElement): void { + protected createEditor(parent: HTMLElement): void { parent.classList.add('query-editor'); this.splitviewContainer = DOM.$('.query-editor-view'); @@ -202,8 +202,9 @@ export class QueryEditor extends EditorPane { })); // Create Actions for the toolbar - this._runQueryAction = this.instantiationService.createInstance(actions.RunQueryAction, this); + this._cancelQueryAction = this.instantiationService.createInstance(actions.CancelQueryAction, this); + this._runQueryAction = this.instantiationService.createInstance(actions.RunQueryAction, this); this._toggleConnectDatabaseAction = this.instantiationService.createInstance(actions.ToggleConnectDatabaseAction, this, false); this._changeConnectionAction = this.instantiationService.createInstance(actions.ConnectDatabaseAction, this, true); this._listDatabasesAction = this.instantiationService.createInstance(actions.ListDatabasesAction, this); @@ -452,7 +453,7 @@ export class QueryEditor extends EditorPane { /** * Sets this editor and the 2 sub-editors to visible. */ - public override setEditorVisible(visible: boolean, group: IEditorGroup): void { + protected override setEditorVisible(visible: boolean, group: IEditorGroup): void { this.textFileEditor.setVisible(visible, group); this.textResourceEditor.setVisible(visible, group); this.resultsEditor.setVisible(visible, group); diff --git a/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts b/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts index 32d81375c1..fa93ff5796 100644 --- a/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts @@ -107,7 +107,7 @@ export class QueryResultsEditor extends EditorPane { this.styleSheet.innerHTML = content; } - createEditor(parent: HTMLElement): void { + protected createEditor(parent: HTMLElement): void { this.styleSheet.remove(); parent.appendChild(this.styleSheet); if (!this.resultsView) { diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index a0915da2d3..34504da228 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -93,7 +93,7 @@ suite('SQL QueryAction Tests', () => { test('setClass sets child CSS class correctly', () => { // If I create a RunQueryAction - let queryAction: QueryTaskbarAction = new RunQueryAction(undefined, undefined, undefined, undefined); + let queryAction: QueryTaskbarAction = new RunQueryAction(undefined, undefined, undefined, undefined, undefined); // "class should automatically get set to include the base class and the RunQueryAction class let className = RunQueryAction.EnabledClass; @@ -115,7 +115,7 @@ suite('SQL QueryAction Tests', () => { editor.setup(x => x.input).returns(() => testQueryInput.object); // If I create a QueryTaskbarAction and I pass a non-connected editor to _getConnectedQueryEditorUri - let queryAction: QueryTaskbarAction = new RunQueryAction(undefined, undefined, connectionManagementService.object, undefined); + let queryAction: QueryTaskbarAction = new RunQueryAction(undefined, undefined, connectionManagementService.object, undefined, undefined); let connected: boolean = queryAction.isConnected(editor.object); // I should get an unconnected state @@ -168,7 +168,7 @@ suite('SQL QueryAction Tests', () => { queryEditor.setup(x => x.getSelections()).returns(() => [undefined]); // If I call run on RunQueryAction when I have a non empty selection - let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined); + let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined, undefined); isSelectionEmpty = false; await queryAction.run(); @@ -245,7 +245,7 @@ suite('SQL QueryAction Tests', () => { /// End Setup Test /// ////// If I call run on RunQueryAction while disconnected and with an undefined selection - let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, undefined, connectionManagementService.object, undefined); + let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, undefined, connectionManagementService.object, undefined, undefined); isConnected = false; selectionToReturnInGetSelection = undefined; await queryAction.run(); @@ -572,7 +572,7 @@ suite('SQL QueryAction Tests', () => { queryModelService.setup(x => x.runQueryStatement(TypeMoq.It.isAny(), TypeMoq.It.isAny())); // Calling runCurrent with no open connection - let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined); + let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined, undefined); calledRunQueryStatementOnInput = false; await queryAction.runCurrent(); @@ -629,7 +629,7 @@ suite('SQL QueryAction Tests', () => { queryModelService.setup(x => x.runQuery(TypeMoq.It.isAny(), undefined, TypeMoq.It.isAny())); queryModelService.setup(x => x.runQueryStatement(TypeMoq.It.isAny(), TypeMoq.It.isAny())); - let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined); + let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object, undefined, undefined); // setting up queryEditor with only a cursor. This case should call runQueryStatement queryEditor.setup(x => x.getSelection(false)).returns(() => { return predefinedCursorSelection; }); diff --git a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts index f8cfbea8de..540f0f4d39 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts @@ -58,7 +58,7 @@ suite('SQL QueryEditor Tests', () => { return new Promise((resolve) => resolve(mockEditor)); }); instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((input) => { - return new Promise((resolve) => resolve(new RunQueryAction(undefined, undefined, undefined, undefined))); + return new Promise((resolve) => resolve(new RunQueryAction(undefined, undefined, undefined, undefined, undefined))); }); // Setup hook to capture calls to create the listDatabase action instantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((classDef, editor, action) => { @@ -68,7 +68,7 @@ suite('SQL QueryEditor Tests', () => { } } // Default - return new RunQueryAction(undefined, undefined, undefined, undefined); + return new RunQueryAction(undefined, undefined, undefined, undefined, undefined); }); // Mock EditorDescriptorService to give us a mock editor description @@ -285,7 +285,7 @@ suite('SQL QueryEditor Tests', () => { queryActionInstantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((input) => { // Default - return new RunQueryAction(undefined, undefined, undefined, undefined); + return new RunQueryAction(undefined, undefined, undefined, undefined, undefined); }); // Setup hook to capture calls to create the listDatabase action @@ -296,7 +296,7 @@ suite('SQL QueryEditor Tests', () => { return item; } // Default - return new RunQueryAction(undefined, undefined, undefined, undefined); + return new RunQueryAction(undefined, undefined, undefined, undefined, undefined); }); const workbenchinstantiationService = workbenchInstantiationService(); diff --git a/src/sql/workbench/contrib/resourceViewer/browser/resourceViewerTable.ts b/src/sql/workbench/contrib/resourceViewer/browser/resourceViewerTable.ts index d2581f7e83..4c75a47eaf 100644 --- a/src/sql/workbench/contrib/resourceViewer/browser/resourceViewerTable.ts +++ b/src/sql/workbench/contrib/resourceViewer/browser/resourceViewerTable.ts @@ -28,6 +28,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export class ResourceViewerTable extends Disposable { @@ -61,7 +62,7 @@ export class ResourceViewerTable extends Disposable { })); this._resourceViewerTable.setSelectionModel(new RowSelectionModel()); - let filterPlugin = new HeaderFilter<azdata.DataGridItem>(this._contextViewService); + let filterPlugin = new HeaderFilter<azdata.DataGridItem>({ buttonStyles: defaultButtonStyles }, this._contextViewService); this._register(attachTableFilterStyler(filterPlugin, this._themeService)); this._register(attachTableStyler(this._resourceViewerTable, this._themeService)); this._register(this._resourceViewerTable.onClick(this.onTableClick, this)); diff --git a/src/sql/workbench/contrib/scripting/test/browser/scriptingActions.test.ts b/src/sql/workbench/contrib/scripting/test/browser/scriptingActions.test.ts index 9928767c82..0e5ae61c3b 100644 --- a/src/sql/workbench/contrib/scripting/test/browser/scriptingActions.test.ts +++ b/src/sql/workbench/contrib/scripting/test/browser/scriptingActions.test.ts @@ -58,7 +58,7 @@ const treeNode = new TreeNode(NodeType.Database, '', 'db node', false, '', '', ' const oeActionArgs: ObjectExplorerActionsContext = { connectionProfile: connection, isConnectionNode: false, nodeInfo: nodeInfo }; let instantiationService: IInstantiationService; -let logServiceMock: TypeMoq.Mock<ILogService>; +let logServiceMock: TypeMoq.Mock<NullLogService>; let treeMock: TypeMoq.Mock<TestTree>; suite('Scripting Actions', () => { diff --git a/src/sql/workbench/contrib/tableDesigner/browser/actions.ts b/src/sql/workbench/contrib/tableDesigner/browser/actions.ts index 497eec7dd5..1489e5c122 100644 --- a/src/sql/workbench/contrib/tableDesigner/browser/actions.ts +++ b/src/sql/workbench/contrib/tableDesigner/browser/actions.ts @@ -7,6 +7,7 @@ import { TableDesignerComponentInput } from 'sql/workbench/services/tableDesigne import { Action } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; import { localize } from 'vs/nls'; const PublishChangesLabel = localize('tableDesigner.publishTableChanges', "Publish Changes..."); @@ -43,10 +44,10 @@ export class SaveTableChangesAction extends Action { private updateLabelAndIcon(): void { if (this._input?.tableDesignerView?.useAdvancedSaveMode) { this.label = PublishChangesLabel; - this.class = Codicon.repoPush.classNames; + this.class = ThemeIcon.asClassName(Codicon.repoPush); } else { this.label = SaveChangesLabel; - this.class = Codicon.save.classNames; + this.class = ThemeIcon.asClassName(Codicon.save); } } diff --git a/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts b/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts index cf6bbdb0b3..691d2dad7f 100644 --- a/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts +++ b/src/sql/workbench/contrib/tasks/browser/tasks.contribution.ts @@ -3,10 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; -import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import * as lifecycle from 'vs/base/common/lifecycle'; import * as ext from 'vs/workbench/common/contributions'; @@ -20,6 +17,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { TASKS_CONTAINER_ID, TASKS_VIEW_ID } from 'sql/workbench/contrib/tasks/common/tasks'; import { TaskHistoryView } from 'sql/workbench/contrib/tasks/browser/tasksView'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; export class StatusUpdater extends lifecycle.Disposable implements ext.IWorkbenchContribution { static ID = 'data.taskhistory.statusUpdater'; @@ -61,16 +59,7 @@ export class StatusUpdater extends lifecycle.Disposable implements ext.IWorkbenc } } -const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction( - SyncActionDescriptor.create( - ToggleTasksAction, - ToggleTasksAction.ID, - ToggleTasksAction.LABEL, - { primary: KeyMod.CtrlCmd | KeyCode.KeyT }), - 'View: Toggle Tasks', - localize('viewCategory', "View") -); +registerAction2(ToggleTasksAction); // markers view container const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ diff --git a/src/sql/workbench/contrib/tasks/browser/tasksActions.ts b/src/sql/workbench/contrib/tasks/browser/tasksActions.ts index ba69155216..f2ff452161 100644 --- a/src/sql/workbench/contrib/tasks/browser/tasksActions.ts +++ b/src/sql/workbench/contrib/tasks/browser/tasksActions.ts @@ -4,24 +4,22 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; + import { ToggleViewAction } from 'sql/workbench/browser/actions/layoutActions'; -import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { TASKS_VIEW_ID } from 'sql/workbench/contrib/tasks/common/tasks'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; + export class ToggleTasksAction extends ToggleViewAction { public static readonly ID = 'workbench.action.tasks.toggleTasks'; + public static readonly LABEL_ORG = 'Toggle Tasks'; public static readonly LABEL = localize('toggleTasks', "Toggle Tasks"); constructor( - id: string, label: string, - @IViewsService viewsService: IViewsService, - @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService + ) { - super(id, label, TASKS_VIEW_ID, viewsService, viewDescriptorService, contextKeyService, layoutService); + super(ToggleTasksAction.ID, ToggleTasksAction.LABEL_ORG, ToggleTasksAction.LABEL, + { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.CtrlCmd | KeyCode.KeyT }); } } diff --git a/src/sql/workbench/contrib/tasks/browser/tasksView.ts b/src/sql/workbench/contrib/tasks/browser/tasksView.ts index 66052173be..193cc8b545 100644 --- a/src/sql/workbench/contrib/tasks/browser/tasksView.ts +++ b/src/sql/workbench/contrib/tasks/browser/tasksView.ts @@ -9,7 +9,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import Severity from 'vs/base/common/severity'; import { Tree } from 'sql/base/parts/tree/browser/treeImpl'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { ITree } from 'sql/base/parts/tree/browser/tree'; import { DefaultFilter, DefaultDragAndDrop, DefaultAccessibilityProvider } from 'sql/base/parts/tree/browser/treeDefaults'; import { localize } from 'vs/nls'; @@ -59,7 +59,7 @@ export class TaskHistoryView extends ViewPane { /** * Render the view body */ - public override renderBody(container: HTMLElement): void { + protected override renderBody(container: HTMLElement): void { super.renderBody(container); let taskNode = this.taskService.getAllTasks(); @@ -171,7 +171,7 @@ export class TaskHistoryView extends ViewPane { /** * set the layout of the view */ - public override layoutBody(height: number, width: number): void { + protected override layoutBody(height: number, width: number): void { super.layoutBody(height, width); if (this._tree) { this._tree.layout(height); diff --git a/src/sql/workbench/contrib/tsgops/browser/tsgops.contribution.ts b/src/sql/workbench/contrib/tsgops/browser/tsgops.contribution.ts index 614254ee4e..734371dcb7 100644 --- a/src/sql/workbench/contrib/tsgops/browser/tsgops.contribution.ts +++ b/src/sql/workbench/contrib/tsgops/browser/tsgops.contribution.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { ICommandService } from 'vs/platform/commands/common/commands'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { HideActivityBarViewContainers, HideSettings, HidePanel } from 'sql/workbench/contrib/tsgops/browser/tsgopsActions'; import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import product from 'vs/platform/product/common/product'; import { TSGOPS_WEB_QUALITY } from 'sql/workbench/common/constants'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; + export class ADSWebLite implements IWorkbenchContribution { constructor( @ICommandService private commandService: ICommandService, @@ -27,32 +27,11 @@ export class ADSWebLite implements IWorkbenchContribution { if (product.quality === TSGOPS_WEB_QUALITY) { // Global Actions - const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions); + registerAction2(HideActivityBarViewContainers); - actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - HideActivityBarViewContainers, - HideActivityBarViewContainers.ID, - HideActivityBarViewContainers.LABEL - ), - HideActivityBarViewContainers.LABEL - ); - actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - HideSettings, - HideSettings.ID, - HideSettings.LABEL - ), - HideSettings.LABEL - ); - actionRegistry.registerWorkbenchAction( - SyncActionDescriptor.create( - HidePanel, - HidePanel.ID, - HidePanel.LABEL - ), - HidePanel.LABEL - ); + registerAction2(HideSettings); + + registerAction2(HidePanel); Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(ADSWebLite, LifecyclePhase.Restored); diff --git a/src/sql/workbench/contrib/tsgops/browser/tsgopsActions.ts b/src/sql/workbench/contrib/tsgops/browser/tsgopsActions.ts index 1d2cc33a83..977d4fb064 100644 --- a/src/sql/workbench/contrib/tsgops/browser/tsgopsActions.ts +++ b/src/sql/workbench/contrib/tsgops/browser/tsgopsActions.ts @@ -3,67 +3,70 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; - import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; -// import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -export class HidePanel extends Action { +export class HidePanel extends Action2 { static readonly ID = 'workbench.action.hidePanel'; + static readonly LABEL_ORG = 'Hide the panel'; static readonly LABEL = localize('hidePanel', "Hide the panel"); - constructor( - id: string = HidePanel.ID, - label: string = HidePanel.LABEL, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService - ) { - super(id, label); + constructor() { + super({ + id: HidePanel.ID, + title: { value: HidePanel.LABEL, original: HidePanel.LABEL_ORG }, + }); } - override async run(): Promise<void> { - this.layoutService.setPartHidden(true, Parts.PANEL_PART); + run(accessor: ServicesAccessor): void { + const layoutService = accessor.get(IWorkbenchLayoutService); + layoutService.setPartHidden(true, Parts.PANEL_PART); } } -export class HideSettings extends Action { +export class HideSettings extends Action2 { static readonly ID = 'workbench.action.hideSettings'; + static readonly LABEL_ORG = 'Hide the settings icon'; static readonly LABEL = localize('hideSettings', "Hide the settings icon"); - constructor( - id: string = HideSettings.ID, - label: string = HideSettings.LABEL, - ) { - super(id, label); + constructor() { + super({ + id: HideSettings.ID, + title: { value: HideSettings.LABEL, original: HideSettings.LABEL_ORG }, + }); } - override async run(): Promise<void> { + run(): void { let allActionItems = Array.from(document.getElementsByClassName('action-item icon')); let manageElement = allActionItems.filter((el) => el.getAttribute('aria-label') === 'Manage'); manageElement[0].parentNode.removeChild(manageElement[0]); } } -export class HideActivityBarViewContainers extends Action { +export class HideActivityBarViewContainers extends Action2 { static readonly ID = 'workbench.action.hideActivityBarViewContainers'; + static readonly LABEL_ORG = 'Hide the extension viewlet'; static readonly LABEL = localize('hideActivityBarViewContainers', "Hide the extension viewlet"); - constructor( - id: string = HideActivityBarViewContainers.ID, - label: string = HideActivityBarViewContainers.LABEL, - @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, - @IPaneCompositePartService private readonly activityBarService: IPaneCompositePartService, - ) { - super(id, label); + constructor() { + super({ + id: HideActivityBarViewContainers.ID, + title: { value: HideActivityBarViewContainers.LABEL, original: HideActivityBarViewContainers.LABEL_ORG }, + }); } - override async run(): Promise<void> { + run(accessor: ServicesAccessor): void { + const viewDescriptorService = accessor.get(IViewDescriptorService); + const activityBarService = accessor.get(IPaneCompositePartService); + let viewsToHide = ['workbench.view.search', 'workbench.view.explorer', 'workbench.view.scm', 'workbench.view.extensions']; for (let j = 0; j < viewsToHide.length; j++) { - const viewContainer = this.viewDescriptorService.getViewContainerById(viewsToHide[j]); + const viewContainer = viewDescriptorService.getViewContainerById(viewsToHide[j]); if (viewContainer) { - this.activityBarService.hideActivePaneComposite(this.viewDescriptorService.getViewContainerLocation(viewContainer)); + activityBarService.hideActivePaneComposite(viewDescriptorService.getViewContainerLocation(viewContainer)); } } } diff --git a/src/sql/workbench/contrib/views/browser/treeView.ts b/src/sql/workbench/contrib/views/browser/treeView.ts index e068d454c7..9b3bc7af2e 100644 --- a/src/sql/workbench/contrib/views/browser/treeView.ts +++ b/src/sql/workbench/contrib/views/browser/treeView.ts @@ -23,7 +23,7 @@ import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar, IActionViewItemProvider } 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 { FileThemeIcon, FolderThemeIcon, registerThemingParticipant, 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'; @@ -43,6 +43,7 @@ import { IOEShimService } from 'sql/workbench/services/objectExplorer/browser/ob import { NodeContextKey } from 'sql/workbench/contrib/views/browser/nodeContext'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { ColorScheme } from 'vs/platform/theme/common/theme'; +import { ThemeIcon } from 'vs/base/common/themables'; class Root implements ITreeItem { label = { label: 'root' }; @@ -77,6 +78,7 @@ export class TreeView extends Disposable implements ITreeView { private treeLabels: ResourceLabels | undefined; readonly badge: IViewBadge | undefined = undefined; readonly container: any | undefined = undefined; + private _manuallyManageCheckboxes: boolean = false; public readonly root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; @@ -105,6 +107,12 @@ export class TreeView extends Disposable implements ITreeView { private readonly _onDidChangeDescription: Emitter<string | undefined> = this._register(new Emitter<string | undefined>()); readonly onDidChangeDescription: Event<string | undefined> = this._onDidChangeDescription.event; + private readonly _onDidChangeCheckboxState: Emitter<readonly ITreeItem[]> = this._register(new Emitter<readonly ITreeItem[]>()); + readonly onDidChangeCheckboxState: Event<readonly ITreeItem[]> = this._onDidChangeCheckboxState.event; + + private _onDidChangeFocus: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>()); + readonly onDidChangeFocus: Event<ITreeItem> = this._onDidChangeFocus.event; + private readonly _onDidCompleteRefresh: Emitter<void> = this._register(new Emitter<void>()); private nodeContext: NodeContextKey; @@ -282,6 +290,14 @@ export class TreeView extends Disposable implements ITreeView { this.refreshContext.set(showRefreshAction); } + get manuallyManageCheckboxes(): boolean { + return this._manuallyManageCheckboxes; + } + + set manuallyManageCheckboxes(manuallyManageCheckboxes: boolean) { + this._manuallyManageCheckboxes = manuallyManageCheckboxes; + } + private registerActions() { const that = this; this._register(registerAction2(class extends Action2 { @@ -436,7 +452,7 @@ export class TreeView extends Disposable implements ITreeView { this.tree.contextKeyService.createKey<boolean>(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.onDidChangeSelection(e => this._onDidChangeSelection.fire(<any>e.elements))); this._register(this.tree.onDidChangeCollapseState(e => { if (!e.node.element) { return; @@ -560,6 +576,10 @@ export class TreeView extends Disposable implements ITreeView { return 0; } + isCollapsed(item: ITreeItem): boolean { + return !!this.tree?.isCollapsed(item); + } + async refresh(elements?: ITreeItem[]): Promise<void> { if (this.dataProvider && this.tree) { if (this.refreshing) { @@ -608,6 +628,10 @@ export class TreeView extends Disposable implements ITreeView { } } + getSelection(): ITreeItem[] { + return this.tree?.getSelection() ?? []; + } + setFocus(item: ITreeItem): void { if (this.tree) { this.focus(); @@ -945,7 +969,7 @@ class MultipleSelectionActionRunner extends ActionRunner { })); } - override async runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> { + protected override async runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> { const selection = this.getSelectedResources(); let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; let actionInSelected: boolean = false; @@ -1007,7 +1031,6 @@ class TreeMenus extends Disposable implements IDisposable { createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result, 'inline'); menu.dispose(); - contextKeyService.dispose(); return result; } diff --git a/src/sql/workbench/contrib/webview/browser/webViewDialog.ts b/src/sql/workbench/contrib/webview/browser/webViewDialog.ts index e90c993af8..26726a1d2c 100644 --- a/src/sql/workbench/contrib/webview/browser/webViewDialog.ts +++ b/src/sql/workbench/contrib/webview/browser/webViewDialog.ts @@ -19,7 +19,6 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; export class WebViewDialog extends Modal { @@ -88,7 +87,8 @@ export class WebViewDialog extends Modal { this._body = DOM.append(container, DOM.$('div.webview-dialog')); this._webview = this.webviewService.createWebviewElement({ - id: this.id, + providedViewType: this.id, + title: this.id, contentOptions: { allowScripts: true, }, @@ -112,7 +112,7 @@ export class WebViewDialog extends Modal { this._register(attachModalDialogStyler(this, this._themeService)); this._okButton = this.addFooterButton(this._okLabel, () => this.ok()); - this._register(attachButtonStyler(this._okButton, this._themeService)); + this._register(this._okButton); } protected layout(height?: number): void { @@ -121,7 +121,7 @@ export class WebViewDialog extends Modal { private updateDialogBody(): void { if (this.html) { - this._webview!.html = this.html; + this._webview!.setHtml(this.html); } } diff --git a/src/sql/workbench/contrib/welcome/gettingStarted/electron-browser/enablePreviewFeatures.ts b/src/sql/workbench/contrib/welcome/gettingStarted/electron-browser/enablePreviewFeatures.ts index 8d24e16152..7f0c07232e 100644 --- a/src/sql/workbench/contrib/welcome/gettingStarted/electron-browser/enablePreviewFeatures.ts +++ b/src/sql/workbench/contrib/welcome/gettingStarted/electron-browser/enablePreviewFeatures.ts @@ -8,7 +8,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { INativeHostService } from 'vs/platform/native/common/native'; export class NativeEnablePreviewFeatures extends AbstractEnablePreviewFeatures { diff --git a/src/sql/workbench/contrib/welcome/page/browser/gettingStartedTour.ts b/src/sql/workbench/contrib/welcome/page/browser/gettingStartedTour.ts index 0b981082bd..6ca5bc2ac6 100644 --- a/src/sql/workbench/contrib/welcome/page/browser/gettingStartedTour.ts +++ b/src/sql/workbench/contrib/welcome/page/browser/gettingStartedTour.ts @@ -7,16 +7,12 @@ import * as dom from 'vs/base/browser/dom'; import 'vs/css!./gettingStartedTour'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { localize } from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; import { Disposable } from 'vs/base/common/lifecycle'; import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { buttonBackground, buttonForeground } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Button } from 'sql/base/browser/ui/button/button'; @@ -24,6 +20,10 @@ import { extensionsViewIcon } from 'vs/workbench/contrib/extensions/browser/exte import { settingsViewBarIcon } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { NotebooksViewIcon } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet'; import { ConnectionsViewIcon } from 'sql/workbench/contrib/dataExplorer/browser/dataExplorerViewlet'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; const $ = dom.$; interface TourData { @@ -60,43 +60,47 @@ const tourData: TourData[] = [ const IS_OVERLAY_VISIBLE = new RawContextKey<boolean>('interfaceOverviewVisible', false); let guidedTour: GuidedTour; -export class GuidedTourAction extends Action { +export class GuidedTourAction extends Action2 { public static readonly ID = 'workbench.action.createGuidedTour'; + public static readonly ORG_LABEL = 'User Welcome Tour'; public static readonly LABEL = localize('guidedTour', "User Welcome Tour"); - constructor( - id: string, - label: string, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(id, label); + constructor() { + super({ + id: GuidedTourAction.ID, + title: { value: GuidedTourAction.LABEL, original: GuidedTourAction.ORG_LABEL }, + category: Categories.Help, + f1: true + }); } - public override run(): Promise<void> { + run(accessor: ServicesAccessor): void { + const instantiationService = accessor.get(IInstantiationService); if (!guidedTour) { - guidedTour = this.instantiationService.createInstance(GuidedTour); + guidedTour = instantiationService.createInstance(GuidedTour); } guidedTour.create(); - return Promise.resolve(); } } -export class HideGuidedTourAction extends Action { +export class HideGuidedTourAction extends Action2 { public static readonly ID = 'workbench.action.hideGuidedTour'; + public static readonly ORG_LABEL = 'Hide Welcome Tour'; public static readonly LABEL = localize('hideGuidedTour', "Hide Welcome Tour"); - constructor( - id: string, - label: string - ) { - super(id, label); + constructor() { + super({ + id: HideGuidedTourAction.ID, + title: { value: HideGuidedTourAction.LABEL, original: HideGuidedTourAction.ORG_LABEL }, + category: Categories.Help, + f1: true + }); } - public override run(): Promise<void> { + run(accessor: ServicesAccessor): void { if (guidedTour) { guidedTour.hide(); } - return Promise.resolve(); } } @@ -178,7 +182,7 @@ export class GuidedTour extends Disposable { headerTag.innerText = header; bodyTag.innerText = body; stepText.innerText = `${step} of ${tourData.length}`; - let button = new Button(btnContainer); + let button = new Button(btnContainer, defaultButtonStyles); button.label = btnText; btnContainer.appendChild(stepText); flexContainer.appendChild(img); @@ -319,11 +323,10 @@ export class GuidedTour extends Disposable { } } -Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions) - .registerWorkbenchAction(SyncActionDescriptor.create(GuidedTourAction, GuidedTourAction.ID, GuidedTourAction.LABEL), 'Help: Show Getting Started Guided Tour', localize('help', "Help")); +registerAction2(GuidedTourAction); + +registerAction2(HideGuidedTourAction); -Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions) - .registerWorkbenchAction(SyncActionDescriptor.create(HideGuidedTourAction, HideGuidedTourAction.ID, HideGuidedTourAction.LABEL, { primary: KeyCode.Escape }, IS_OVERLAY_VISIBLE), 'Help: Hide Getting Started Guided Tour', localize('help', "Help")); registerThemingParticipant((theme, collector) => { const bodyTag = theme.getColor(buttonForeground); diff --git a/src/sql/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts b/src/sql/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts index b5386653f7..8eaaad56dd 100644 --- a/src/sql/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts +++ b/src/sql/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts @@ -7,8 +7,7 @@ import { localize } from 'vs/nls'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { WelcomePageContribution, WelcomePageAction, WelcomeInputSerializer } from 'sql/workbench/contrib/welcome/page/browser/welcomePage'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions, CATEGORIES } from 'vs/workbench/common/actions'; -import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration'; @@ -41,8 +40,7 @@ class WelcomeContributions { Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(WelcomePageContribution, LifecyclePhase.Restored); - Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions) - .registerWorkbenchAction(SyncActionDescriptor.create(WelcomePageAction, WelcomePageAction.ID, WelcomePageAction.LABEL), 'Help: Welcome', CATEGORIES.Help.value); + registerAction2(WelcomePageAction); Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEditorSerializer(WelcomeInputSerializer.ID, WelcomeInputSerializer); } diff --git a/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts b/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts index 90ed289be2..e79f230f02 100644 --- a/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts +++ b/src/sql/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -9,22 +9,21 @@ import { URI } from 'vs/base/common/uri'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as arrays from 'vs/base/common/arrays'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { isCancellationError, onUnexpectedError } from 'vs/base/common/errors'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; -import { Action, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; +import { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Schemas } from 'vs/base/common/network'; -import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; +import { getInstalledExtensions, IExtensionStatus, isKeymapExtension, onExtensionChanged } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILifecycleService, StartupKind } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle'; -import { splitName } from 'vs/base/common/labels'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { tileBorder, gradientOne, gradientTwo, gradientBackground, extensionPackHeaderShadow, extensionPackGradientColorOneColor, extensionPackGradientColorTwoColor, tileBoxShadow, hoverShadow } from 'sql/platform/theme/common/colorRegistry'; import { registerColor, foreground, textLinkActiveForeground, descriptionForeground, activeContrastBorder, buttonForeground, menuBorder, menuForeground, editorWidgetBorder, selectBackground, buttonHoverBackground, selectBorder, iconForeground, textLinkForeground, inputBackground, focusBorder, listFocusBackground, listFocusForeground, buttonSecondaryBackground, buttonSecondaryBorder, buttonDisabledForeground, buttonDisabledBackground, buttonSecondaryForeground, buttonSecondaryHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; @@ -33,7 +32,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { TimeoutTimer } from 'vs/base/common/async'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { ILabelService, Verbosity } from 'vs/platform/label/common/label'; import { IFileService } from 'vs/platform/files/common/files'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; @@ -47,15 +46,18 @@ import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/bro import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Button } from 'sql/base/browser/ui/button/button'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { MenuItemAction } from 'vs/platform/actions/common/actions'; +import { Action2, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { AddServerAction } from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { WalkThroughInput } from 'vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughInput'; import { IWindowOpenable } from 'vs/platform/window/common/window'; import { ICommandAction } from 'vs/platform/action/common/action'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { splitRecentLabel } from 'vs/base/common/labels'; + const configurationKey = 'workbench.startupEditor'; const oldConfigurationKey = 'workbench.welcome.enabled'; const telemetryFrom = 'welcomePage'; @@ -138,22 +140,23 @@ function isGuidedTourEnabled(configurationService: IConfigurationService): boole return false; } -export class WelcomePageAction extends Action { - - +export class WelcomePageAction extends Action2 { public static readonly ID = 'workbench.action.showWelcomePage'; + public static readonly ORG_LABEL = 'Welcome'; public static readonly LABEL = localize('welcomePage', "Welcome"); - constructor( - id: string, - label: string, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - super(id, label); + constructor() { + super({ + id: WelcomePageAction.ID, + title: { value: WelcomePageAction.LABEL, original: WelcomePageAction.ORG_LABEL }, + category: Categories.Help, + f1: true + }); } - public override run(): Promise<void> { - return this.instantiationService.createInstance(WelcomePage) + run(accessor: ServicesAccessor): void { + const instantiationService = accessor.get(IInstantiationService); + instantiationService.createInstance(WelcomePage) .openEditor() .then(() => undefined); } @@ -251,8 +254,7 @@ class WelcomePage extends Disposable { @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, @ICommandService private readonly commandService: ICommandService, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IThemeService private themeService: IThemeService) { + @IContextKeyService private readonly contextKeyService: IContextKeyService) { super(); this._register(lifecycleService.onWillShutdown(() => this.dispose())); const recentlyOpened = this.workspacesService.getRecentlyOpened(); @@ -369,7 +371,7 @@ class WelcomePage extends Disposable { const newButtonContainer = document.createElement('div'); newButtonContainer.classList.add('btn', 'btn-primary'); container.appendChild(newButtonContainer); - const newButton = this._register(new Button(newButtonContainer)); + const newButton = this._register(new Button(newButtonContainer, defaultButtonStyles)); newButton.label = localize('welcomePage.new', "New"); const newButtonHtmlElement = newButton.element; newButtonHtmlElement.setAttribute('aria-haspopup', 'true'); @@ -413,7 +415,7 @@ class WelcomePage extends Disposable { const btnContainer = document.createElement('div'); btnContainer.classList.add('btn', 'btn-secondary'); container.appendChild(btnContainer); - const secondaryButton = this._register(new Button(btnContainer)); + const secondaryButton = this._register(new Button(btnContainer, { secondary: true, ...defaultButtonStyles })); secondaryButton.label = item.title; secondaryButton.element.classList.add('btn-secondary'); secondaryButton.onDidClick(async () => { @@ -431,7 +433,7 @@ class WelcomePage extends Disposable { const icon = document.createElement('div'); const containerLeft = document.createElement('div'); const containerRight = document.createElement('div'); - let startTourBtn = new Button(containerRight); + let startTourBtn = new Button(containerRight, defaultButtonStyles); startTourBtn.label = localize('welcomePage.startTour', "Start Tour"); const removeTourBtn = document.createElement('a'); removeTourBtn.setAttribute('role', 'button'); @@ -478,7 +480,7 @@ class WelcomePage extends Disposable { private async createListEntries(container: HTMLElement, fullPath: string, windowOpenable: IWindowOpenable): Promise<HTMLElement[]> { let result: HTMLElement[] = []; - const { name, parentPath } = splitName(fullPath); + const { name, parentPath } = splitRecentLabel(fullPath); const li = document.createElement('li'); const icon = document.createElement('i'); const a = document.createElement('a'); @@ -522,9 +524,9 @@ class WelcomePage extends Disposable { let windowOpenable: IWindowOpenable; if (isRecentFolder(recent)) { windowOpenable = { folderUri: recent.folderUri }; - fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri, { verbose: true }); + fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri, { verbose: Verbosity.LONG }); } else { - fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); + fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: Verbosity.LONG }); windowOpenable = { workspaceUri: recent.workspace.configPath }; } const elements = await this.createListEntries(container, fullPath, windowOpenable); @@ -575,9 +577,7 @@ class WelcomePage extends Disposable { if (btnContainer) { extensionPacks.forEach((extension) => { const installText = localize('welcomePage.install', "Install"); - let dropdownBtn = this._register(new Button(btnContainer)); - this._register(attachButtonStyler(dropdownBtn, this.themeService)); - dropdownBtn.label = installText; + this._register(new Button(btnContainer, { title: installText, supportIcons: true, ...defaultButtonStyles })); const classes = ['btn']; const getDropdownBtn = container.querySelector('.extensionPack .monaco-button:first-of-type') as HTMLAnchorElement; getDropdownBtn.id = 'dropdown-btn'; @@ -602,11 +602,12 @@ class WelcomePage extends Disposable { const header = container.querySelector('.extension-pack-header'); const installedText = localize('welcomePage.installed', "Installed"); - let installedButton = new Button(btnContainer); + let installedButton = new Button(btnContainer, { title: installedText, supportIcons: true, ...defaultButtonStyles }); + this._register(installedButton); + installedButton.label = installedText; installedButton.enabled = false; const getInstalledButton = container.querySelector('.extensionPack .monaco-button:nth-of-type(2)') as HTMLAnchorElement; - this._register(attachButtonStyler(installedButton, this.themeService)); getInstalledButton.innerText = localize('welcomePage.installed', "Installed"); getInstalledButton.title = extension.isKeymap ? localize('welcomePage.installedKeymap', "{0} keymap is already installed", extension.name) : localize('welcomePage.installedExtensionPack', "{0} support is already installed", extension.name); diff --git a/src/sql/workbench/services/accountManagement/browser/accountDialog.ts b/src/sql/workbench/services/accountManagement/browser/accountDialog.ts index abc193668b..19e89c60ec 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountDialog.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountDialog.ts @@ -9,7 +9,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget'; import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachButtonStyler, attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -48,6 +48,7 @@ import { LoadingSpinner } from 'sql/base/browser/ui/loadingSpinner/loadingSpinne import { Tenant, TenantListDelegate, TenantListRenderer } from 'sql/workbench/services/accountManagement/browser/tenantListRenderer'; import { IAccountManagementService } from 'sql/platform/accounts/common/interfaces'; import { ADAL_AUTH_LIBRARY, AuthLibrary, getAuthLibrary } from 'sql/workbench/services/accountManagement/utils'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; export const VIEWLET_ID = 'workbench.view.accountpanel'; @@ -224,7 +225,7 @@ export class AccountDialog extends Modal { // Show the add account button for the first provider const buttonSection = DOM.append(this._noaccountViewContainer, DOM.$('div.button-section')); - this._addAccountButton = new Button(buttonSection); + this._addAccountButton = new Button(buttonSection, defaultButtonStyles); this._addAccountButton.label = localize('accountDialog.addConnection', "Add an account"); this._register(this._addAccountButton.onDidClick(async () => { @@ -236,8 +237,8 @@ export class AccountDialog extends Modal { private registerListeners(): void { // Theme styler - this._register(attachButtonStyler(this._closeButton!, this._themeService)); - this._register(attachButtonStyler(this._addAccountButton!, this._themeService)); + this._register(this._closeButton!); + this._register(this._addAccountButton!); } /* Overwrite escape key behavior */ @@ -270,7 +271,6 @@ export class AccountDialog extends Modal { else { this.showNoAccountContainer(); } - } private showNoAccountContainer() { diff --git a/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts b/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts index 3a47a7e369..a0331e91db 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountPickerImpl.ts @@ -8,23 +8,23 @@ import * as DOM from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IDropdownOptions } from 'vs/base/browser/ui/dropdown/dropdown'; import { IListEvent } from 'vs/base/browser/ui/list/list'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { buttonBackground } from 'vs/platform/theme/common/colorRegistry'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import * as azdata from 'azdata'; -import { DropdownList } from 'sql/base/browser/ui/dropdownList/dropdownList'; -import { attachDropdownStyler } from 'sql/platform/theme/common/styler'; +import { DropdownList, IDropdownOptions } from 'sql/base/browser/ui/dropdownList/dropdownList'; import { AddAccountAction, RefreshAccountAction } from 'sql/platform/accounts/common/accountActions'; import { AccountPickerListRenderer, AccountListDelegate } from 'sql/workbench/services/accountManagement/browser/accountListRenderer'; import { AccountPickerViewModel } from 'sql/platform/accounts/common/accountPickerViewModel'; import { Tenant, TenantListDelegate, TenantPickerListRenderer } from 'sql/workbench/services/accountManagement/browser/tenantListRenderer'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultDropdownStyles } from 'sql/platform/theme/browser/defaultStyles'; export class AccountPicker extends Disposable { public static ACCOUNTPICKERLIST_HEIGHT = 47; @@ -136,12 +136,16 @@ export class AccountPicker extends Disposable { // Create dropdowns for account and tenant pickers const accountOptions: IDropdownOptions = { contextViewProvider: this._contextViewService, - labelRenderer: (container) => this.renderAccountLabel(container) + labelRenderer: (container) => this.renderAccountLabel(container), + buttonStyles: defaultButtonStyles, + dropdownStyles: defaultDropdownStyles }; const tenantOption: IDropdownOptions = { contextViewProvider: this._contextViewService, - labelRenderer: (container) => this.renderTenantLabel(container) + labelRenderer: (container) => this.renderTenantLabel(container), + buttonStyles: defaultButtonStyles, + dropdownStyles: defaultDropdownStyles }; // Create the add account action @@ -153,9 +157,6 @@ export class AccountPicker extends Disposable { this._dropdown = this._register(new DropdownList(this._accountContainer, accountOptions, this._accountListContainer, this._accountList, addAccountAction)); this._tenantDropdown = this._register(new DropdownList(this._tenantContainer, tenantOption, this._tenantListContainer, this._tenantList)); - this._register(attachDropdownStyler(this._dropdown, this._themeService)); - this._register(attachDropdownStyler(this._tenantDropdown, this._themeService)); - this._register(this._accountList.onDidChangeSelection((e: IListEvent<azdata.Account>) => { if (e.elements.length === 1) { this._dropdown!.renderLabel(); diff --git a/src/sql/workbench/services/accountManagement/browser/autoOAuthDialog.ts b/src/sql/workbench/services/accountManagement/browser/autoOAuthDialog.ts index 014e8f7721..e11487ce2f 100644 --- a/src/sql/workbench/services/accountManagement/browser/autoOAuthDialog.ts +++ b/src/sql/workbench/services/accountManagement/browser/autoOAuthDialog.ts @@ -6,12 +6,11 @@ import 'vs/css!./media/autoOAuthDialog'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachInputBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { localize } from 'vs/nls'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { $, append } from 'vs/base/browser/dom'; import { Button } from 'sql/base/browser/ui/button/button'; @@ -24,6 +23,7 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export class AutoOAuthDialog extends Modal { private _copyAndOpenButton?: Button; @@ -75,7 +75,7 @@ export class AutoOAuthDialog extends Modal { super.render(); attachModalDialogStyler(this, this._themeService); this.backButton!.onDidClick(() => this.cancel()); - this._register(attachButtonStyler(this.backButton!, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND })); + this._register(this.backButton); this._copyAndOpenButton = this.addFooterButton(localize('copyAndOpen', "Copy & Open"), () => this.addAccount()); this._closeButton = this.addFooterButton(localize('oauthDialog.cancel', "Cancel"), () => this.cancel(), 'right', true); @@ -103,14 +103,15 @@ export class AutoOAuthDialog extends Modal { const inputCellContainer = append(inputContainer, $('.dialog-input')); return new InputBox(inputCellContainer, this._contextViewService, { - ariaLabel: label + ariaLabel: label, + inputBoxStyles: defaultInputBoxStyles }); } private registerListeners(): void { // Theme styler - this._register(attachButtonStyler(this._copyAndOpenButton!, this._themeService)); - this._register(attachButtonStyler(this._closeButton!, this._themeService)); + this._register(this._copyAndOpenButton!); + this._register(this._closeButton!); this._register(attachInputBoxStyler(this._userCodeInputBox!, this._themeService)); this._register(attachInputBoxStyler(this._websiteInputBox!, this._themeService)); diff --git a/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts b/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts index bfcc16310e..852ca2755e 100644 --- a/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts +++ b/src/sql/workbench/services/accountManagement/test/browser/accountManagementService.test.ts @@ -535,7 +535,7 @@ function getTestState(): AccountManagementState { // Create instantiation service let mockInstantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Strict); mockInstantiationService.setup(x => x.createInstance(TypeMoq.It.isValue(AccountStore), TypeMoq.It.isAny())) - .returns(() => mockAccountStore.object); + .returns(() => <any>mockAccountStore.object); const testNotificationService = new TestNotificationService(); const testConfigurationService = new TestConfigurationService(); diff --git a/src/sql/workbench/services/backupRestoreUrlBrowser/browser/urlBrowserDialog.ts b/src/sql/workbench/services/backupRestoreUrlBrowser/browser/urlBrowserDialog.ts index 7705149d3e..dbc39e7ba5 100644 --- a/src/sql/workbench/services/backupRestoreUrlBrowser/browser/urlBrowserDialog.ts +++ b/src/sql/workbench/services/backupRestoreUrlBrowser/browser/urlBrowserDialog.ts @@ -13,8 +13,7 @@ import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { attachInputBoxStyler, attachSelectBoxStyler } from 'sql/platform/theme/common/vsstyler'; import * as DOM from 'vs/base/browser/dom'; import * as strings from 'vs/base/common/strings'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; @@ -33,6 +32,7 @@ import { Link } from 'vs/platform/opener/browser/link'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Deferred } from 'sql/base/common/promise'; +import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; /** * This function adds one year to the current date and returns it in the UTC format. @@ -110,7 +110,10 @@ export class BackupRestoreUrlBrowserDialog extends Modal { this.close(); })); - this._register(attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND })); + this._register(this.backButton); + + // {{SQL CARBON TODO}} - style + //this._register(attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND })); } let tableContainer: HTMLElement = DOM.append(DOM.append(this._body, DOM.$('.option-section')), DOM.$('table.url-table-content')); @@ -174,13 +177,16 @@ export class BackupRestoreUrlBrowserDialog extends Modal { let sharedAccessSignatureLabel = localize('backupRestoreUrlBrowserDialog.sharedAccessSignature', "Shared access signature generated"); let sasInput = DialogHelper.appendRow(tableContainer, sharedAccessSignatureLabel, 'url-input-label', 'url-input-box', null, true); - this._sasInputBox = this._register(new InputBox(sasInput, this._contextViewService, { flexibleHeight: true })); + this._sasInputBox = this._register(new InputBox(sasInput, this._contextViewService, { + flexibleHeight: true, + inputBoxStyles: defaultInputBoxStyles + })); this._sasInputBox.disable(); this._register(this._sasInputBox.onDidChange(() => this.enableOkButton())); let sasButtonContainer = DialogHelper.appendRow(tableContainer, '', 'url-input-label', 'url-input-box'); let sasButtonLabel = localize('backupRestoreUrlBrowserDialog.sharedAccessSignatureButton', "Create Credentials"); - this._sasButton = this._register(new Button(sasButtonContainer, { title: sasButtonLabel })); + this._sasButton = this._register(new Button(sasButtonContainer, { title: sasButtonLabel, ...defaultButtonStyles })); this._sasButton.label = sasButtonLabel; this._sasButton.title = sasButtonLabel; this._register(this._sasButton.onDidClick(e => this.generateSharedAccessSignature())); @@ -195,7 +201,10 @@ export class BackupRestoreUrlBrowserDialog extends Modal { this._backupFileSelectorBox.disable(); } else { let fileInput = DialogHelper.appendRow(tableContainer, backupFileLabel, 'url-input-label', 'url-input-box', null, true); - this._backupFileInputBox = this._register(new InputBox(fileInput, this._contextViewService, { flexibleHeight: true })); + this._backupFileInputBox = this._register(new InputBox(fileInput, this._contextViewService, { + flexibleHeight: true, + inputBoxStyles: defaultInputBoxStyles + })); this._backupFileInputBox.value = this._defaultBackupName; } @@ -444,8 +453,9 @@ export class BackupRestoreUrlBrowserDialog extends Modal { if (this._backupFileSelectorBox) { this._register(attachSelectBoxStyler(this._backupFileSelectorBox, this._themeService)); } - this._register(attachButtonStyler(this._sasButton, this._themeService)); - this._register(attachButtonStyler(this._okButton, this._themeService)); - this._register(attachButtonStyler(this._cancelButton, this._themeService)); + + this._register(this._sasButton); + this._register(this._okButton); + this._register(this._cancelButton); } } diff --git a/src/sql/workbench/services/bootstrap/browser/bootstrapService.ts b/src/sql/workbench/services/bootstrap/browser/bootstrapService.ts index f86d94ab24..0b65718238 100644 --- a/src/sql/workbench/services/bootstrap/browser/bootstrapService.ts +++ b/src/sql/workbench/services/bootstrap/browser/bootstrapService.ts @@ -17,7 +17,7 @@ const selectorCounter = new Map<string, number>(); export function providerIterator(service: IInstantiationService): Provider[] { return Array.from(_util.serviceIds.values()).map(v => { let factory = () => { - return (<any>service)._getOrCreateServiceInstance(v, Trace.traceCreation(v)); + return (<any>service)._getOrCreateServiceInstance(v, Trace.traceCreation(false, v)); }; factory.prototype = factory; return { diff --git a/src/sql/workbench/services/connection/browser/cmsConnectionWidget.ts b/src/sql/workbench/services/connection/browser/cmsConnectionWidget.ts index afba07b4f3..9edd7cbe9a 100644 --- a/src/sql/workbench/services/connection/browser/cmsConnectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/cmsConnectionWidget.ts @@ -27,6 +27,7 @@ import { ConnectionWidget } from 'sql/workbench/services/connection/browser/conn import { ILogService } from 'vs/platform/log/common/log'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; /** * Connection Widget clas for CMS Connections @@ -119,7 +120,11 @@ export class CmsConnectionWidget extends ConnectionWidget { if (serverDescriptionOption) { serverDescriptionOption.displayName = localize('serverDescription', "Server Description (optional)"); let serverDescriptionBuilder = DialogHelper.appendRow(this._tableContainer, serverDescriptionOption.displayName, 'connection-label', 'connection-input', 'server-description-input'); - this._serverDescriptionInputBox = new InputBox(serverDescriptionBuilder, this._contextViewService, { type: 'textarea', flexibleHeight: true }); + this._serverDescriptionInputBox = new InputBox(serverDescriptionBuilder, this._contextViewService, { + type: 'textarea', + flexibleHeight: true, + inputBoxStyles: defaultInputBoxStyles + }); this._serverDescriptionInputBox.setHeight('75px'); } } diff --git a/src/sql/workbench/services/connection/browser/connectionActions.ts b/src/sql/workbench/services/connection/browser/connectionActions.ts index b35d8c0b91..f1d46e0e3e 100644 --- a/src/sql/workbench/services/connection/browser/connectionActions.ts +++ b/src/sql/workbench/services/connection/browser/connectionActions.ts @@ -17,11 +17,15 @@ import { EditDataInput } from 'sql/workbench/browser/editData/editDataInput'; import { DashboardInput } from 'sql/workbench/browser/editor/profiler/dashboardInput'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { FileAccess } from 'vs/base/common/network'; /** * Workbench action to clear the recent connnections list */ -export class ClearRecentConnectionsAction extends Action { +// {{SQL CARBON TODO}} - remove old action that is used by ActionBar class +export class ClearRecentConnectionsAction1 extends Action { public static ID = 'clearRecentConnectionsAction'; public static LABEL = nls.localize('ClearRecentlyUsedLabel', "Clear List"); @@ -40,7 +44,7 @@ export class ClearRecentConnectionsAction extends Action { @IQuickInputService private _quickInputService: IQuickInputService, @IDialogService private _dialogService: IDialogService, ) { - super(id, label, ClearRecentConnectionsAction.ICON); + super(id, label, ClearRecentConnectionsAction1.ICON); this.enabled = true; } @@ -92,7 +96,7 @@ export class ClearRecentConnectionsAction extends Action { let confirm: IConfirmation = { message: nls.localize('clearRecentConnectionMessage', "Are you sure you want to delete all the connections from the list?"), primaryButton: nls.localize('connectionDialog.yes', "Yes"), - secondaryButton: nls.localize('connectionDialog.no', "No"), + cancelButton: nls.localize('connectionDialog.no', "No"), type: 'question' }; @@ -104,6 +108,98 @@ export class ClearRecentConnectionsAction extends Action { } } +/** + * Workbench action to clear the recent connnections list + */ +export class ClearRecentConnectionsAction extends Action2 { + + public static ID = 'clearRecentConnectionsAction'; + public static LABEL_ORG = 'Clear List'; + public static LABEL = nls.localize('ClearRecentlyUsedLabel', "Clear List"); + public static ICON = 'search-action clear-search-results'; + + private _onRecentConnectionsRemoved = new Emitter<void>(); + public onRecentConnectionsRemoved: Event<void> = this._onRecentConnectionsRemoved.event; + + private _useConfirmationMessage = false; + + constructor() { + super({ + id: ClearRecentConnectionsAction.ID, + // {{SQL CARBON TODO}} - does this work for icons? + icon: { + light: FileAccess.asBrowserUri(`sql/workbench/services/connection/browser/media/clear-search-results.svg`), + dark: FileAccess.asBrowserUri(`sql/workbench/services/connection/browser/media/clear-search-results-dark.svg`) + }, + title: { value: ClearRecentConnectionsAction.LABEL, original: ClearRecentConnectionsAction.LABEL_ORG }, + f1: true + }); + } + + public set useConfirmationMessage(value: boolean) { + this._useConfirmationMessage = value; + } + + public override run(accessor: ServicesAccessor): Promise<void> { + const connectionManagementService = accessor.get(IConnectionManagementService); + const notificationService = accessor.get(INotificationService); + const quickInputService = accessor.get(IQuickInputService); + const dialogService = accessor.get(IDialogService); + + if (this._useConfirmationMessage) { + return this.promptConfirmationMessage(dialogService).then(result => { + if (result.confirmed) { + connectionManagementService.clearRecentConnectionsList(); + this._onRecentConnectionsRemoved.fire(); + } + }); + } else { + return this.promptQuickOpenService(quickInputService).then(result => { + if (result) { + connectionManagementService.clearRecentConnectionsList(); + + const actions: INotificationActions = { primary: [] }; + notificationService.notify({ + severity: Severity.Info, + message: nls.localize('ClearedRecentConnections', "Recent connections list cleared"), + actions + }); + this._onRecentConnectionsRemoved.fire(); + } + }); + } + } + + private promptQuickOpenService(quickInputService: IQuickInputService): Promise<boolean> { + return new Promise<boolean>((resolve, reject) => { + let choices: { key, value }[] = [ + { key: nls.localize('connectionAction.yes', "Yes"), value: true }, + { key: nls.localize('connectionAction.no', "No"), value: false } + ]; + + quickInputService.pick(choices.map(x => x.key), { placeHolder: nls.localize('ClearRecentlyUsedLabel', "Clear List"), ignoreFocusLost: true }).then((choice) => { + let confirm = choices.find(x => x.key === choice); + resolve(confirm && confirm.value); + }); + }); + } + + private promptConfirmationMessage(dialogService: IDialogService): Promise<IConfirmationResult> { + let confirm: IConfirmation = { + message: nls.localize('clearRecentConnectionMessage', "Are you sure you want to delete all the connections from the list?"), + primaryButton: nls.localize('connectionDialog.yes', "Yes"), + cancelButton: nls.localize('connectionDialog.no', "No"), + type: 'question' + }; + + return new Promise<IConfirmationResult>((resolve, reject) => { + dialogService.confirm(confirm).then((confirmed) => { + resolve(confirmed); + }); + }); + } +} + /** * Action to delete one recently used connection from the MRU */ @@ -135,43 +231,46 @@ export class ClearSingleRecentConnectionAction extends Action { /** * Action to retrieve the current connection string */ -export class GetCurrentConnectionStringAction extends Action { +export class GetCurrentConnectionStringAction extends Action2 { public static ID = 'getCurrentConnectionStringAction'; + public static LABEL_ORG = 'Get Current Connection String'; public static LABEL = nls.localize('connectionAction.GetCurrentConnectionString', "Get Current Connection String"); - constructor( - id: string, - label: string, - @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, - @IEditorService private _editorService: IEditorService, - @INotificationService private readonly _notificationService: INotificationService, - @IClipboardService private _clipboardService: IClipboardService, - ) { - super(GetCurrentConnectionStringAction.ID, GetCurrentConnectionStringAction.LABEL); - this.enabled = true; + constructor() { + //super(GetCurrentConnectionStringAction.ID, GetCurrentConnectionStringAction.LABEL); + super({ + id: ClearRecentConnectionsAction.ID, + title: { value: ClearRecentConnectionsAction.LABEL, original: ClearRecentConnectionsAction.LABEL_ORG }, + f1: true + }); } - public override run(): Promise<void> { + public override run(accessor: ServicesAccessor): Promise<void> { + const connectionManagementService = accessor.get(IConnectionManagementService); + const editorService = accessor.get(IEditorService); + const notificationService = accessor.get(INotificationService); + const clipboardService = accessor.get(IClipboardService); + return new Promise<void>((resolve, reject) => { - let activeInput = this._editorService.activeEditor; + let activeInput = editorService.activeEditor; if (activeInput && (activeInput instanceof QueryEditorInput || activeInput instanceof EditDataInput || activeInput instanceof DashboardInput) - && this._connectionManagementService.isConnected(activeInput.uri)) { + && connectionManagementService.isConnected(activeInput.uri)) { let includePassword = false; - let connectionProfile = this._connectionManagementService.getConnectionProfile(activeInput.uri); - this._connectionManagementService.getConnectionString(connectionProfile.id, includePassword).then(result => { + let connectionProfile = connectionManagementService.getConnectionProfile(activeInput.uri); + connectionManagementService.getConnectionString(connectionProfile.id, includePassword).then(result => { //Copy to clipboard - this._clipboardService.writeText(result); + clipboardService.writeText(result); let message = result ? result : nls.localize('connectionAction.connectionString', "Connection string not available"); - this._notificationService.info(message); + notificationService.info(message); }); } else { let message = nls.localize('connectionAction.noConnection', "No active connection available"); - this._notificationService.info(message); + notificationService.info(message); } }); } diff --git a/src/sql/workbench/services/connection/browser/connectionBrowseTab.ts b/src/sql/workbench/services/connection/browser/connectionBrowseTab.ts index dc402a61f8..86b63ea04e 100644 --- a/src/sql/workbench/services/connection/browser/connectionBrowseTab.ts +++ b/src/sql/workbench/services/connection/browser/connectionBrowseTab.ts @@ -48,11 +48,12 @@ import { FileKind } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; -import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { ColorScheme } from 'vs/platform/theme/common/theme'; -import { FileThemeIcon, FolderThemeIcon, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { FileThemeIcon, FolderThemeIcon, IThemeService } from 'vs/platform/theme/common/themeService'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { ITreeItemLabel, ITreeViewDataProvider, TreeItemCollapsibleState, TreeViewItemHandleArg } from 'vs/workbench/common/views'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { ThemeIcon } from 'vs/base/common/themables'; @@ -109,11 +110,12 @@ export class ConnectionBrowserView extends Disposable implements IPanelView { renderFilterBox(container: HTMLElement): void { this.filterInput = new InputBox(container, this.contextViewService, { placeholder: localize('connectionDialog.FilterPlaceHolder', "Type here to filter the list"), - ariaLabel: localize('connectionDialog.FilterInputTitle', "Filter connections") + ariaLabel: localize('connectionDialog.FilterInputTitle', "Filter connections"), + inputBoxStyles: defaultInputBoxStyles }); this.filterProgressBar = new ProgressBar(this.filterInput.element); this._register(this.filterProgressBar); - this._register(attachProgressBarStyler(this.filterProgressBar, this.themeService)); + //this._register(attachProgressBarStyler(this.filterProgressBar, this.themeService)); this.filterInput.element.style.margin = '5px'; this._register(this.filterInput); this._register(attachInputBoxStyler(this.filterInput, this.themeService)); @@ -385,11 +387,11 @@ class ProviderElementRenderer extends BaseTreeItemRender<ConnectionDialogTreePro super(menus, actionViewItemProvider); } - getText(element: ConnectionDialogTreeProviderElement): string { + protected getText(element: ConnectionDialogTreeProviderElement): string { return element.name; } - getIconClass(element: ConnectionDialogTreeProviderElement): string { + protected getIconClass(element: ConnectionDialogTreeProviderElement): string { return 'codicon-folder'; } } @@ -398,11 +400,11 @@ class SavedConnectionsNodeRenderer extends BaseTreeItemRender<SavedConnectionNod public static readonly TEMPLATE_ID = 'savedConnectionNode'; public readonly templateId = SavedConnectionsNodeRenderer.TEMPLATE_ID; - getText(element: SavedConnectionNode): string { + protected getText(element: SavedConnectionNode): string { return localize('savedConnections', "Saved Connections"); } - getIconClass(element: SavedConnectionNode): string { + protected getIconClass(element: SavedConnectionNode): string { return 'codicon-folder'; } } @@ -411,11 +413,11 @@ class ConnectionProfileGroupRenderer extends BaseTreeItemRender<ConnectionProfil public static readonly TEMPLATE_ID = 'connectionProfileGroup'; public readonly templateId = ConnectionProfileGroupRenderer.TEMPLATE_ID; - getText(element: ConnectionProfileGroup): string { + protected getText(element: ConnectionProfileGroup): string { return element.name; } - getIconClass(element: ConnectionProfileGroup): string { + protected getIconClass(element: ConnectionProfileGroup): string { return 'codicon-folder'; } } @@ -613,7 +615,6 @@ class ConnectionBrowseTreeMenuProvider { createAndFillInContextMenuActions(menu, { shouldForwardArgs: true }, result); menu.dispose(); - contextKeyService.dispose(); return result.primary; } diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index 0d16c4df85..5fd3d09d15 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -19,7 +19,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { localize } from 'vs/nls'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import * as styler from 'vs/platform/theme/common/styler'; +import * as styler from 'sql/platform/theme/common/vsstyler'; import * as DOM from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -37,7 +37,7 @@ import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/t import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ICancelableEvent } from 'sql/base/parts/tree/browser/treeDefaults'; import { RecentConnectionActionsProvider, RecentConnectionTreeController } from 'sql/workbench/services/connection/browser/recentConnectionTreeController'; -import { ClearRecentConnectionsAction } from 'sql/workbench/services/connection/browser/connectionActions'; +import { ClearRecentConnectionsAction1 } from 'sql/workbench/services/connection/browser/connectionActions'; import { ITree } from 'sql/base/parts/tree/browser/tree'; import { AsyncServerTree } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -66,7 +66,6 @@ export class ConnectionDialogWidget extends Modal { private _connectionTypeContainer: HTMLElement; private _connectionDetailTitle: HTMLElement; private _connectButton: Button; - private _closeButton: Button; private _providerTypeSelectBox: SelectBox; private _newConnectionParams: INewConnectionParams; private _recentConnectionTree: AsyncServerTree | ITree; @@ -185,10 +184,10 @@ export class ConnectionDialogWidget extends Modal { // Remove duplicate listings (CMS uses the same display name) let uniqueProvidersMap = this.connectionManagementService.getUniqueConnectionProvidersByNameMap(filteredProviderMap); - this._providerTypeSelectBox.setOptions(Object.keys(uniqueProvidersMap).map(k => uniqueProvidersMap[k])); + this._providerTypeSelectBox?.setOptions(Object.keys(uniqueProvidersMap).map(k => uniqueProvidersMap[k])); } - protected renderBody(container: HTMLElement): void { + protected override renderBody(container: HTMLElement): void { this._body = DOM.append(container, DOM.$('.connection-dialog')); const connectTypeLabel = localize('connectType', "Connection type"); @@ -261,7 +260,7 @@ export class ConnectionDialogWidget extends Modal { const cancelLabel = localize('connectionDialog.cancel', "Cancel"); this._connectButton = this.addFooterButton(connectLabel, () => this.connect()); this._connectButton.enabled = false; - this._closeButton = this.addFooterButton(cancelLabel, () => this.cancel(), 'right', true); + this.addFooterButton(cancelLabel, () => this.cancel(), 'right', true); this.registerListeners(); this.onProviderTypeSelected(this._providerTypeSelectBox.value); } @@ -282,9 +281,6 @@ export class ConnectionDialogWidget extends Modal { private registerListeners(): void { // Theme styler this._register(styler.attachSelectBoxStyler(this._providerTypeSelectBox, this._themeService)); - this._register(styler.attachButtonStyler(this._connectButton, this._themeService)); - this._register(styler.attachButtonStyler(this._closeButton, this._themeService)); - this._register(this._providerTypeSelectBox.onDidSelect(selectedProviderType => { this.onProviderTypeSelected(selectedProviderType.selected); })); @@ -342,7 +338,7 @@ export class ConnectionDialogWidget extends Modal { this._recentConnectionActionBarContainer = DOM.append(recentConnectionContainer, DOM.$('.recent-titles-container')); const actionsContainer = DOM.append(this._recentConnectionActionBarContainer, DOM.$('.connection-history-actions')); this._actionbar = this._register(new ActionBar(actionsContainer, { animated: false })); - const clearAction = this.instantiationService.createInstance(ClearRecentConnectionsAction, ClearRecentConnectionsAction.ID, ClearRecentConnectionsAction.LABEL); + const clearAction = this.instantiationService.createInstance(ClearRecentConnectionsAction1, ClearRecentConnectionsAction1.ID, ClearRecentConnectionsAction1.LABEL); clearAction.useConfirmationMessage = true; clearAction.onRecentConnectionsRemoved(() => this.open(false)); this._actionbar.push(clearAction, { icon: true, label: true }); @@ -496,9 +492,10 @@ export class ConnectionDialogWidget extends Modal { } public updateProvider(providerDisplayName: string) { - this._providerTypeSelectBox.selectWithOptionName(providerDisplayName); - - this.onProviderTypeSelected(providerDisplayName); + if (this._providerTypeSelectBox) { + this._providerTypeSelectBox.selectWithOptionName(providerDisplayName); + this.onProviderTypeSelected(providerDisplayName); + } } public set databaseDropdownExpanded(val: boolean) { diff --git a/src/sql/workbench/services/connection/browser/connectionWidget.ts b/src/sql/workbench/services/connection/browser/connectionWidget.ts index dff1752740..3a489f2d9b 100644 --- a/src/sql/workbench/services/connection/browser/connectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionWidget.ts @@ -29,7 +29,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { ILogService } from 'vs/platform/log/common/log'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown'; import { RadioButton } from 'sql/base/browser/ui/radioButton/radioButton'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; @@ -46,6 +45,8 @@ import { adjustForMssqlAppName } from 'sql/platform/connection/common/utils'; import { isMssqlAuthProviderEnabled } from 'sql/workbench/services/connection/browser/utils'; import { RequiredIndicatorClassName } from 'sql/base/browser/ui/label/label'; import { FieldSet } from 'sql/base/browser/ui/fieldset/fieldset'; +import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultCheckboxStyles } from 'sql/platform/theme/browser/defaultStyles'; const ConnectionStringText = localize('connectionWidget.connectionString', "Connection string"); @@ -241,7 +242,8 @@ export class ConnectionWidget extends lifecycle.Disposable { }, ariaLabel: ConnectionStringText, flexibleHeight: true, - flexibleMaxHeight: 100 + flexibleMaxHeight: 100, + inputBoxStyles: defaultInputBoxStyles }); this._register(this._connectionStringInputBox); this._register(this._connectionStringInputBox.onDidChange(() => { @@ -304,7 +306,8 @@ export class ConnectionWidget extends lifecycle.Disposable { default: this._customOptionWidgets[i] = new InputBox(customOptionsContainer, this._contextViewService, { ariaLabel: option.displayName, - placeholder: option.placeholder + placeholder: option.placeholder, + inputBoxStyles: defaultInputBoxStyles }); this._register(styler.attachInputBoxStyler(this._customOptionWidgets[i] as InputBox, this._themeService)); break; @@ -401,7 +404,8 @@ export class ConnectionWidget extends lifecycle.Disposable { } }, ariaLabel: serverNameOption.displayName, - placeholder: serverNameOption.placeholder + placeholder: serverNameOption.placeholder, + inputBoxStyles: defaultInputBoxStyles }); this._register(this._serverNameInputBox); } @@ -416,7 +420,8 @@ export class ConnectionWidget extends lifecycle.Disposable { validation: (value: string) => self.validateUsername(value, userNameOption.isRequired) ? ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', "{0} is required.", userNameOption.displayName) }) : null }, ariaLabel: userNameOption.displayName, - placeholder: userNameOption.placeholder + placeholder: userNameOption.placeholder, + inputBoxStyles: defaultInputBoxStyles }); this._register(this._userNameInputBox); // Password @@ -424,7 +429,8 @@ export class ConnectionWidget extends lifecycle.Disposable { let password = DialogHelper.appendRow(this._tableContainer, passwordOption.displayName, 'connection-label', 'connection-input', 'password-row'); this._passwordInputBox = new InputBox(password, this._contextViewService, { ariaLabel: passwordOption.displayName, - placeholder: passwordOption.placeholder + placeholder: passwordOption.placeholder, + inputBoxStyles: defaultInputBoxStyles }); this._passwordInputBox.inputElement.type = 'password'; this._register(this._passwordInputBox); @@ -483,7 +489,8 @@ export class ConnectionWidget extends lifecycle.Disposable { let connectionNameBuilder = DialogHelper.appendRow(this._tableContainer, connectionNameOption.displayName, 'connection-label', 'connection-input'); this._connectionNameInputBox = new InputBox(connectionNameBuilder, this._contextViewService, { ariaLabel: connectionNameOption.displayName, - placeholder: connectionNameOption.placeholder + placeholder: connectionNameOption.placeholder, + inputBoxStyles: defaultInputBoxStyles }); this._register(this._connectionNameInputBox); } @@ -494,7 +501,7 @@ export class ConnectionWidget extends lifecycle.Disposable { const buttonContainer = DOM.append(rowContainer, DOM.$('td')); buttonContainer.setAttribute('align', 'right'); const divContainer = DOM.append(buttonContainer, DOM.$('div.advanced-button')); - this._advancedButton = new Button(divContainer, { secondary: true }); + this._advancedButton = new Button(divContainer, { secondary: true, ...defaultButtonStyles }); this._register(this._advancedButton); this._advancedButton.label = localize('advanced', "Advanced..."); this._register(this._advancedButton.onDidClick(() => { @@ -529,7 +536,7 @@ export class ConnectionWidget extends lifecycle.Disposable { let rowContainer = DOM.append(container, DOM.$(`tr.${rowContainerClass}`)); DOM.append(rowContainer, DOM.$('td')); let checkboxContainer = DOM.append(rowContainer, DOM.$(`td.${cellContainerClass}`)); - return new Checkbox(checkboxContainer, { label, checked: isChecked, ariaLabel: label }); + return new Checkbox(checkboxContainer, { ...defaultCheckboxStyles, label, checked: isChecked, ariaLabel: label }); } protected registerListeners(): void { @@ -538,8 +545,6 @@ export class ConnectionWidget extends lifecycle.Disposable { this._register(styler.attachInputBoxStyler(this._connectionNameInputBox, this._themeService)); this._register(styler.attachInputBoxStyler(this._userNameInputBox, this._themeService)); this._register(styler.attachInputBoxStyler(this._passwordInputBox, this._themeService)); - this._register(attachButtonStyler(this._advancedButton, this._themeService)); - this._register(styler.attachCheckboxStyler(this._rememberPasswordCheckBox, this._themeService)); this._register(styler.attachSelectBoxStyler(this._azureAccountDropdown, this._themeService)); if (this._serverGroupSelectBox) { this._register(styler.attachSelectBoxStyler(this._serverGroupSelectBox, this._themeService)); diff --git a/src/sql/workbench/services/connection/browser/passwordChangeDialog.ts b/src/sql/workbench/services/connection/browser/passwordChangeDialog.ts index 598855bc47..fb0d9c5735 100644 --- a/src/sql/workbench/services/connection/browser/passwordChangeDialog.ts +++ b/src/sql/workbench/services/connection/browser/passwordChangeDialog.ts @@ -13,7 +13,6 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { localize } from 'vs/nls'; import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import * as DOM from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; @@ -24,6 +23,7 @@ import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMess import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; const dialogWidth: string = '500px'; // Width is set manually here as there is no default width for normal dialogs. const okText: string = localize('passwordChangeDialog.ok', "OK"); @@ -86,8 +86,6 @@ export class PasswordChangeDialog extends Modal { this._register(attachModalDialogStyler(this, this._themeService)); this._okButton = this.addFooterButton(okText, async () => { await this.handleOkButtonClick(); }); this._cancelButton = this.addFooterButton(cancelText, () => { this.handleCancelButtonClick(); }, 'right', true); - this._register(attachButtonStyler(this._okButton, this._themeService)); - this._register(attachButtonStyler(this._cancelButton, this._themeService)); } protected renderBody(container: HTMLElement) { @@ -100,12 +98,18 @@ export class PasswordChangeDialog extends Modal { const contentElement = body.appendChild(DOM.$('.properties-content.components-grid')); contentElement.appendChild(DOM.$('')).appendChild(DOM.$('span.component-label')).innerText = newPasswordText; const passwordInputContainer = contentElement.appendChild(DOM.$('')); - this._passwordValueText = new InputBox(passwordInputContainer, this.contextViewService, { type: 'password' }); + this._passwordValueText = new InputBox(passwordInputContainer, this.contextViewService, { + type: 'password', + inputBoxStyles: defaultInputBoxStyles + }); this._register(attachInputBoxStyler(this._passwordValueText, this._themeService)); contentElement.appendChild(DOM.$('')).appendChild(DOM.$('span.component-label')).innerText = confirmPasswordText; const confirmInputContainer = contentElement.appendChild(DOM.$('')); - this._confirmValueText = new InputBox(confirmInputContainer, this.contextViewService, { type: 'password' }); + this._confirmValueText = new InputBox(confirmInputContainer, this.contextViewService, { + type: 'password', + inputBoxStyles: defaultInputBoxStyles + }); this._register(attachInputBoxStyler(this._confirmValueText, this._themeService)); } diff --git a/src/sql/workbench/services/connection/common/connectionTreeService.ts b/src/sql/workbench/services/connection/common/connectionTreeService.ts index 54cac43e4f..0d19c269de 100644 --- a/src/sql/workbench/services/connection/common/connectionTreeService.ts +++ b/src/sql/workbench/services/connection/common/connectionTreeService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITreeViewDataProvider } from 'vs/workbench/common/views'; import { Emitter, Event } from 'vs/base/common/event'; @@ -79,4 +79,4 @@ export class ConnectionTreeService implements IConnectionTreeService { } } -registerSingleton(IConnectionTreeService, ConnectionTreeService, false); +registerSingleton(IConnectionTreeService, ConnectionTreeService, InstantiationType.Delayed); diff --git a/src/sql/workbench/services/connection/test/browser/connectionDialogService.test.ts b/src/sql/workbench/services/connection/test/browser/connectionDialogService.test.ts index b6f61a4c70..421bf93c88 100644 --- a/src/sql/workbench/services/connection/test/browser/connectionDialogService.test.ts +++ b/src/sql/workbench/services/connection/test/browser/connectionDialogService.test.ts @@ -30,12 +30,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ConnectionWidget } from 'sql/workbench/services/connection/browser/connectionWidget'; import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService'; -import { NullCommandService } from 'vs/platform/commands/common/commands'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { ClearRecentConnectionsAction } from 'sql/workbench/services/connection/browser/connectionActions'; +import { ClearRecentConnectionsAction1 } from 'sql/workbench/services/connection/browser/connectionActions'; import { RecentConnectionActionsProvider } from 'sql/workbench/services/connection/browser/recentConnectionTreeController'; import { RecentConnectionDataSource } from 'sql/workbench/services/objectExplorer/browser/recentConnectionDataSource'; import { ServerTreeRenderer } from 'sql/workbench/services/objectExplorer/browser/serverTreeRenderer'; @@ -54,6 +53,7 @@ import { ConnectionTreeService, IConnectionTreeService } from 'sql/workbench/ser import { ConnectionBrowserView } from 'sql/workbench/services/connection/browser/connectionBrowseTab'; import { ConnectionProviderProperties, ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { Emitter } from 'vs/base/common/event'; +import { NullCommandService } from 'vs/platform/commands/test/common/nullCommandService'; suite('ConnectionDialogService tests', () => { const testTreeViewId = 'testTreeView'; @@ -98,7 +98,9 @@ suite('ConnectionDialogService tests', () => { undefined, // telemetry service new TestConfigurationService(), // configuration service new TestCapabilitiesService()); + testInstantiationService.stub(IConnectionManagementService, mockConnectionManagementService.object); + testInstantiationService.stub(IContextKeyService, new MockContextKeyService()); testInstantiationService.stub(IThemeService, new TestThemeService()); @@ -219,8 +221,8 @@ suite('ConnectionDialogService tests', () => { mockWidget.setup(x => x.databaseDropdownExpanded).returns(() => false); mockWidget.setup(x => x.databaseDropdownExpanded = false); - mockInstantationService.setup(x => x.createInstance(TypeMoq.It.isValue(ClearRecentConnectionsAction), TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => { - return testInstantiationService.createInstance(ClearRecentConnectionsAction, ClearRecentConnectionsAction.ID, ClearRecentConnectionsAction.LABEL); + mockInstantationService.setup(x => x.createInstance(TypeMoq.It.isValue(ClearRecentConnectionsAction1), TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => { + return testInstantiationService.createInstance(ClearRecentConnectionsAction1, ClearRecentConnectionsAction1.ID, ClearRecentConnectionsAction1.LABEL); }); mockInstantationService.setup(x => x.createInstance(TypeMoq.It.isValue(RecentConnectionActionsProvider))).returns(() => { return testInstantiationService.createInstance(RecentConnectionActionsProvider); diff --git a/src/sql/workbench/services/connection/test/browser/testTreeView.ts b/src/sql/workbench/services/connection/test/browser/testTreeView.ts index e45c261e25..d145abaf4f 100644 --- a/src/sql/workbench/services/connection/test/browser/testTreeView.ts +++ b/src/sql/workbench/services/connection/test/browser/testTreeView.ts @@ -24,7 +24,7 @@ import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar, IActionViewItemProvider } 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 { FileThemeIcon, FolderThemeIcon, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; @@ -43,6 +43,7 @@ import { IHoverService, IHoverOptions, IHoverTarget } from 'vs/workbench/service import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { isMacintosh } from 'vs/base/common/platform'; +import { ThemeIcon } from 'vs/base/common/themables'; class Root implements ITreeItem { label = { label: 'root' }; @@ -61,6 +62,7 @@ export class TreeView extends Disposable implements ITreeView { private isVisible: boolean = false; private _hasIconForParentNode = false; private _hasIconForLeafNode = false; + manuallyManageCheckboxes: boolean = false; private readonly collapseAllContextKey: RawContextKey<boolean>; private readonly collapseAllContext: IContextKey<boolean>; @@ -78,9 +80,16 @@ export class TreeView extends Disposable implements ITreeView { badge: IViewBadge | undefined; readonly container: any | undefined; - public root: ITreeItem; // {{SQL CARBON EDIT}} + public root: ITreeItem; private elementsToRefresh: ITreeItem[] = []; + + private readonly _onDidChangeCheckboxState: Emitter<readonly ITreeItem[]> = this._register(new Emitter<readonly ITreeItem[]>()); + readonly onDidChangeCheckboxState: Event<readonly ITreeItem[]> = this._onDidChangeCheckboxState.event; + + private readonly _onDidChangeFocus: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>()); + readonly onDidChangeFocus: Event<ITreeItem> = this._onDidChangeFocus.event; + private readonly _onDidExpandItem: Emitter<ITreeItem> = this._register(new Emitter<ITreeItem>()); readonly onDidExpandItem: Event<ITreeItem> = this._onDidExpandItem.event; @@ -422,7 +431,7 @@ export class TreeView extends Disposable implements ITreeView { this.tree.contextKeyService.createKey<boolean>(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.onDidChangeSelection(e => this._onDidChangeSelection.fire(<any>e.elements))); this._register(this.tree.onDidChangeCollapseState(e => { if (!e.node.element) { return; @@ -591,6 +600,14 @@ export class TreeView extends Disposable implements ITreeView { } } + isCollapsed(item: ITreeItem): boolean { + return !!this.tree?.isCollapsed(item); + } + + getSelection(): ITreeItem[] { + return this.tree?.getSelection() ?? []; + } + setFocus(item: ITreeItem): void { if (this.tree) { this.focus(); @@ -965,7 +982,7 @@ class MultipleSelectionActionRunner extends ActionRunner { })); } - override async runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> { + protected override async runAction(action: IAction, context: TreeViewItemHandleArg): Promise<void> { const selection = this.getSelectedResources(); let selectionHandleArgs: TreeViewItemHandleArg[] | undefined = undefined; let actionInSelected: boolean = false; diff --git a/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts b/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts index 2fde674d2e..b48ceeec8c 100644 --- a/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts +++ b/src/sql/workbench/services/dashboard/browser/newDashboardTabDialogImpl.ts @@ -10,7 +10,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget'; import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachButtonStyler, attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -147,7 +147,6 @@ export class NewDashboardTabDialog extends Modal { this._addNewTabButton = this.addFooterButton(localize('newDashboardTab.ok', "OK"), () => this.addNewTabs()); this._cancelButton = this.addFooterButton(localize('newDashboardTab.cancel', "Cancel"), () => this.cancel(), 'right', true); - this.registerListeners(); } protected renderBody(container: HTMLElement) { @@ -185,12 +184,6 @@ export class NewDashboardTabDialog extends Modal { this._register(attachListStyler(this._extensionList, this._themeService)); } - private registerListeners(): void { - // Theme styler - this._register(attachButtonStyler(this._cancelButton!, this._themeService)); - this._register(attachButtonStyler(this._addNewTabButton!, this._themeService)); - } - /* Overwrite escape key behavior */ protected override onClose() { this.cancel(); diff --git a/src/sql/workbench/services/dialog/browser/dialogModal.ts b/src/sql/workbench/services/dialog/browser/dialogModal.ts index 74db4d562b..062a0308c3 100644 --- a/src/sql/workbench/services/dialog/browser/dialogModal.ts +++ b/src/sql/workbench/services/dialog/browser/dialogModal.ts @@ -10,9 +10,7 @@ import { DialogPane } from 'sql/workbench/services/dialog/browser/dialogPane'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { Button } from 'vs/base/browser/ui/button/button'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { Emitter } from 'vs/base/common/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -50,7 +48,7 @@ export class DialogModal extends Modal { super(_dialog.title, name, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, options); } - public layout(): void { + protected layout(): void { this._dialogPane.layout(); } @@ -64,7 +62,6 @@ export class DialogModal extends Modal { if (this._modalOptions.renderFooter && this.backButton) { this.backButton.onDidClick(() => this.cancel()); - attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }); } if (this._modalOptions.renderFooter && this._dialog.customButtons) { @@ -115,7 +112,6 @@ export class DialogModal extends Modal { button.onUpdate(() => { this.updateButtonElement(buttonElement, button, requireDialogValid); }); - attachButtonStyler(buttonElement, this._themeService); this.updateButtonElement(buttonElement, button, requireDialogValid); return buttonElement; } diff --git a/src/sql/workbench/services/dialog/browser/dialogPane.ts b/src/sql/workbench/services/dialog/browser/dialogPane.ts index 1e6c067618..ea0a7ad0f3 100644 --- a/src/sql/workbench/services/dialog/browser/dialogPane.ts +++ b/src/sql/workbench/services/dialog/browser/dialogPane.ts @@ -19,12 +19,11 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IThemable } from 'vs/base/common/styler'; import { attachTabbedPanelStyler } from 'sql/workbench/common/styler'; import { localize } from 'vs/nls'; import { getFocusableElements } from 'sql/base/browser/dom'; -export class DialogPane extends Disposable implements IThemable { +export class DialogPane extends Disposable { private _tabbedPanel: TabbedPanel | undefined; private _moduleRefs: NgModuleRef<{}>[] = []; diff --git a/src/sql/workbench/services/dialog/browser/wizardModal.ts b/src/sql/workbench/services/dialog/browser/wizardModal.ts index f3b0d251a8..6673a89a9f 100644 --- a/src/sql/workbench/services/dialog/browser/wizardModal.ts +++ b/src/sql/workbench/services/dialog/browser/wizardModal.ts @@ -11,9 +11,7 @@ import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/boots import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes'; import { DialogModule } from 'sql/workbench/services/dialog/browser/dialog.module'; import { Button } from 'vs/base/browser/ui/button/button'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -61,7 +59,7 @@ export class WizardModal extends Modal { this._useDefaultMessageBoxLocation = false; } - public layout(): void { + protected layout(): void { } @@ -71,7 +69,6 @@ export class WizardModal extends Modal { if (this.backButton) { this.backButton.onDidClick(() => this.cancel()); - attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND }); } this._wizard.customButtons.forEach(button => { @@ -122,7 +119,6 @@ export class WizardModal extends Modal { button.onUpdate(() => { this.updateButtonElement(buttonElement, button, requirePageValid); }); - attachButtonStyler(buttonElement, this._themeService); this.updateButtonElement(buttonElement, button, requirePageValid); return buttonElement; } diff --git a/src/sql/workbench/services/dialog/test/electron-browser/dialogPane.test.ts b/src/sql/workbench/services/dialog/test/electron-browser/dialogPane.test.ts index e1c821dc5a..75717c652e 100644 --- a/src/sql/workbench/services/dialog/test/electron-browser/dialogPane.test.ts +++ b/src/sql/workbench/services/dialog/test/electron-browser/dialogPane.test.ts @@ -9,8 +9,7 @@ import { DialogPane } from 'sql/workbench/services/dialog/browser/dialogPane'; import { DialogComponentParams } from 'sql/workbench/services/dialog/browser/dialogContainer.component'; import { bootstrapAngular } from 'sql/workbench/services/bootstrap/browser/bootstrapService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; -import { workbenchInstantiationService } from 'vs/workbench/test/electron-browser/workbenchTestServices'; - +import { workbenchInstantiationService } from 'vs/workbench/test/electron-sandbox/workbenchTestServices'; interface BootstrapAngular { (collection, moduleType, container, selectorString, params: DialogComponentParams, input, callbackSetModule): void; diff --git a/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts b/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts index 0b1cbe1715..1a1c89348a 100644 --- a/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts +++ b/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts @@ -10,7 +10,6 @@ import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import Severity from 'vs/base/common/severity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -22,7 +21,6 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { onUnexpectedError } from 'vs/base/common/errors'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { Link } from 'vs/platform/opener/browser/link'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -51,6 +49,8 @@ export class ErrorMessageDialog extends Modal { private _onOk = new Emitter<void>(); public onOk: Event<void> = this._onOk.event; + protected _telemetryView: TelemetryKeys.TelemetryView | string = TelemetryKeys.TelemetryView.ErrorMessageDialog; + constructor( @IThemeService themeService: IThemeService, @IClipboardService clipboardService: IClipboardService, @@ -60,12 +60,12 @@ export class ErrorMessageDialog extends Modal { @ILogService logService: ILogService, @ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService, @IOpenerService private readonly _openerService: IOpenerService, - protected _telemetryView: TelemetryKeys.TelemetryView | string = TelemetryKeys.TelemetryView.ErrorMessageDialog, ) { super('', TelemetryKeys.ModalDialogName.ErrorMessage, telemetryService, layoutService, clipboardService, themeService, logService, textResourcePropertiesService, contextKeyService, { dialogStyle: 'normal', hasTitleIcon: true, height: 340 }); this._okLabel = localize('errorMessageDialog.ok', "OK"); this._closeLabel = localize('errorMessageDialog.close', "Close"); this._readMoreLabel = localize('errorMessageDialog.readMore', "Read More"); + } protected renderBody(container: HTMLElement) { @@ -81,7 +81,6 @@ export class ErrorMessageDialog extends Modal { this._actionButtons.unshift(this.createStandardButton(localize('errorMessageDialog.action', "Action"), () => this.onActionSelected(i))); } this._okButton = this.addFooterButton(this._okLabel, () => this.ok()); - this._register(attachButtonStyler(this._okButton, this._themeService)); } private createCopyButton() { @@ -91,16 +90,12 @@ export class ErrorMessageDialog extends Modal { this._clipboardService.writeText(this._messageDetails!).catch(err => onUnexpectedError(err)); } }, 'left', true); - this._copyButton!.icon = { - id: 'codicon scriptToClipboard' - }; + this._copyButton!.icon = 'codicon scriptToClipboard'; this._copyButton!.element.title = copyButtonLabel; - this._register(attachButtonStyler(this._copyButton!, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND, buttonForeground: SIDE_BAR_FOREGROUND })); } private createStandardButton(label: string, onSelect: () => void): Button { let button = this.addFooterButton(label, onSelect, 'right', false); - this._register(attachButtonStyler(button, this._themeService)); return button; } diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts index 4db1db9310..a1bd786f17 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts @@ -20,8 +20,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { attachInputBoxStyler, attachSelectBoxStyler } from 'sql/platform/theme/common/vsstyler'; import * as DOM from 'vs/base/browser/dom'; import * as strings from 'vs/base/common/strings'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; @@ -32,6 +31,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; export class FileBrowserDialog extends Modal { private _viewModel: FileBrowserViewModel; @@ -39,7 +39,6 @@ export class FileBrowserDialog extends Modal { private _filePathInputBox: InputBox; private _fileFilterSelectBox: SelectBox; private _okButton: Button; - private _cancelButton: Button; private _onOk = new Emitter<string>(); public onOk: Event<string> = this._onOk.event; @@ -81,8 +80,6 @@ export class FileBrowserDialog extends Modal { this.backButton.onDidClick(() => { this.close(); }); - - this._register(attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND })); } this._treeContainer = DOM.append(this._body, DOM.$('.tree-view')); @@ -93,7 +90,8 @@ export class FileBrowserDialog extends Modal { let pathLabel = localize('filebrowser.filepath', "Selected path"); let pathBuilder = DialogHelper.appendRow(tableContainer, pathLabel, 'file-input-label', 'file-input-box'); this._filePathInputBox = new InputBox(pathBuilder, this._contextViewService, { - ariaLabel: pathLabel + ariaLabel: pathLabel, + inputBoxStyles: defaultInputBoxStyles, }); let filterLabel = localize('fileFilter', "Files of type"); @@ -104,7 +102,7 @@ export class FileBrowserDialog extends Modal { this._okButton = this.addFooterButton(localize('fileBrowser.ok', "OK"), () => this.ok()); this._okButton.enabled = false; - this._cancelButton = this.addFooterButton(localize('fileBrowser.discard', "Discard"), () => this.close(), 'right', true); + this.addFooterButton(localize('fileBrowser.discard', "Discard"), () => this.close(), 'right', true); this.registerListeners(); this.updateTheme(); @@ -231,8 +229,6 @@ export class FileBrowserDialog extends Modal { // Theme styler this._register(attachInputBoxStyler(this._filePathInputBox, this._themeService)); this._register(attachSelectBoxStyler(this._fileFilterSelectBox, this._themeService)); - this._register(attachButtonStyler(this._okButton, this._themeService)); - this._register(attachButtonStyler(this._cancelButton, this._themeService)); this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme())); } diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserRenderer.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserRenderer.ts index 233c0a5e92..b9c1f53349 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserRenderer.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserRenderer.ts @@ -8,13 +8,10 @@ import { ITree, IRenderer } from 'sql/base/parts/tree/browser/tree'; import { FileKind } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { ResourceLabels, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; -// eslint-disable-next-line code-import-patterns import { IFileTemplateData } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; -const EmptyDisposable = toDisposable(() => null); - /** * Renders the tree items. * Uses the dom template to render file browser. @@ -48,9 +45,10 @@ export class FileBrowserRenderer implements IRenderer { * Render template in a dom element based on template id */ public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): IFileTemplateData { + const templateDisposables = new DisposableStore(); const label = this.resourceLabels.create(container); - const elementDisposable = EmptyDisposable; - return { elementDisposable, label, container }; + const templateData: IFileTemplateData = { templateDisposables, elementDisposables: templateDisposables.add(new DisposableStore()), label, container }; + return templateData; } /** diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserTreeView.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserTreeView.ts index da27ae9f50..7afd38fb83 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserTreeView.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserTreeView.ts @@ -13,7 +13,7 @@ import nls = require('vs/nls'); import { DefaultFilter, DefaultAccessibilityProvider, DefaultDragAndDrop } from 'sql/base/parts/tree/browser/treeDefaults'; import { Tree } from 'sql/base/parts/tree/browser/treeImpl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITree } from 'sql/base/parts/tree/browser/tree'; import { IExpandableTree } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils'; diff --git a/src/sql/workbench/services/insights/browser/insightsDialogView.ts b/src/sql/workbench/services/insights/browser/insightsDialogView.ts index 7addc9780e..28d40a3ae7 100644 --- a/src/sql/workbench/services/insights/browser/insightsDialogView.ts +++ b/src/sql/workbench/services/insights/browser/insightsDialogView.ts @@ -48,7 +48,6 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInsightsConfigDetails } from 'sql/platform/extensions/common/extensions'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { IDisposableDataProvider } from 'sql/base/common/dataProvider'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -346,7 +345,6 @@ export class InsightsDialogView extends Modal { public override render() { super.render(); this._closeButton = this.addFooterButton('Close', () => this.close()); - this._register(attachButtonStyler(this._closeButton, this._themeService)); this._register(attachModalDialogStyler(this, this._themeService)); } @@ -401,7 +399,6 @@ export class InsightsDialogView extends Modal { }, 'left', true); button.enabled = false; this._taskButtonDisposables.push(button); - this._taskButtonDisposables.push(attachButtonStyler(button, this._themeService)); } } } diff --git a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts index 48296fe175..7e84686e56 100644 --- a/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts +++ b/src/sql/workbench/services/insights/test/electron-browser/insightsUtils.test.ts @@ -21,18 +21,8 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati import { IFileService } from 'vs/platform/files/common/files'; import * as pfs from 'vs/base/node/pfs'; import { getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { IProcessEnvironment } from 'vs/base/common/platform'; -import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { isEqual } from 'vs/base/common/resources'; import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/baseConfigurationResolverService'; -import { TestNativeWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; - -class MockWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { - - constructor(public userEnv: IProcessEnvironment) { - super({ ...TestNativeWindowConfiguration, userEnv }, undefined); - } -} class TestConfigurationResolverService extends BaseConfigurationResolverService { @@ -193,10 +183,8 @@ suite('Insights Utils tests', function () { new Workspace('TestWorkspace', undefined, undefined, undefined, undefined)); - const environmentService = new MockWorkbenchEnvironmentService({ TEST_PATH: queryFileDir }); - // Create mock window service with env variable containing test folder for resolution - const configurationResolverService = new TestConfigurationResolverService({ getAppRoot: () => undefined, getExecPath: () => undefined }, Promise.resolve(environmentService.userEnv), + const configurationResolverService = new TestConfigurationResolverService({ getAppRoot: () => undefined, getExecPath: () => undefined }, undefined, undefined, undefined, undefined, @@ -221,14 +209,12 @@ suite('Insights Utils tests', function () { ok(isEqual(resolvedPath, URI.file(queryFilePath))); }); - test('resolveQueryFilePath resolves path correctly with env var and non-empty workspace', async () => { + test.skip('resolveQueryFilePath resolves path correctly with env var and non-empty workspace', async () => { const contextService = new TestContextService( new Workspace('TestWorkspace', [toWorkspaceFolder(URI.file(os.tmpdir()))], undefined, undefined, undefined)); - const environmentService = new MockWorkbenchEnvironmentService({ TEST_PATH: queryFileDir }); - // Create mock window service with env variable containing test folder for resolution - const configurationResolverService = new TestConfigurationResolverService({ getAppRoot: () => undefined, getExecPath: () => undefined }, Promise.resolve(environmentService.userEnv), + const configurationResolverService = new TestConfigurationResolverService({ getAppRoot: () => undefined, getExecPath: () => undefined }, undefined, undefined, undefined, undefined, diff --git a/src/sql/workbench/services/notebook/browser/notebookServiceImpl.ts b/src/sql/workbench/services/notebook/browser/notebookServiceImpl.ts index 6e1f343a2b..974e783606 100644 --- a/src/sql/workbench/services/notebook/browser/notebookServiceImpl.ts +++ b/src/sql/workbench/services/notebook/browser/notebookServiceImpl.ts @@ -921,7 +921,7 @@ export class NotebookService extends Disposable implements INotebookService { protected async removeContributedProvidersFromCache(identifier: IExtensionIdentifier, extensionService: IExtensionService): Promise<void> { try { - const extensionDescriptions = await extensionService.getExtensions(); + const extensionDescriptions = extensionService.extensions; let extensionDescription = extensionDescriptions.find(c => c.identifier.value.toLowerCase() === identifier.id.toLowerCase()); if (extensionDescription && extensionDescription.contributes && extensionDescription.contributes[Extensions.NotebookProviderDescriptionContribution] diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts index f396f939cb..45787e0944 100644 --- a/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts +++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts @@ -37,7 +37,7 @@ export class AsyncServerTree extends WorkbenchAsyncDataTree<ConnectionProfileGro user, container, delegate, renderers, dataSource, options, instantiationService, contextKeyService, listService, - themeService, configurationService); + configurationService); // Adding support for expand/collapse on enter/space this.onKeyDown(e => { @@ -61,7 +61,7 @@ export class AsyncServerTree extends WorkbenchAsyncDataTree<ConnectionProfileGro * This method overrides the original implementation to find the node by comparing the ids of the elements. * If the node is not found in the original implementation, we search for the node in the nodes map by ids. */ - public override getDataNode(element: ServerTreeElement, throwError: boolean = true): IAsyncDataTreeNode<ConnectionProfileGroup, ServerTreeElement> | undefined { + protected override getDataNode(element: ServerTreeElement, throwError: boolean = true): IAsyncDataTreeNode<ConnectionProfileGroup, ServerTreeElement> | undefined { try { const node = super.getDataNode(element); return node; diff --git a/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts b/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts index 8f70b875e1..c661081965 100644 --- a/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts +++ b/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts @@ -25,6 +25,10 @@ import { Codicon } from 'vs/base/common/codicons'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { status } from 'vs/base/browser/ui/aria/aria'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { Action2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { FileAccess } from 'vs/base/common/network'; export interface IServerView { showFilteredTree(filter: string): void; @@ -46,7 +50,7 @@ export class RefreshAction extends Action { @IErrorMessageService private _errorMessageService: IErrorMessageService, @ILogService private _logService: ILogService ) { - super(id, label, Codicon.refresh.classNames); + super(id, label, ThemeIcon.asClassName(Codicon.refresh)); } public override async run(): Promise<void> { let treeNode: TreeNode | undefined = undefined; @@ -105,7 +109,7 @@ export class EditConnectionAction extends Action { private _connectionProfile: ConnectionProfile, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService ) { - super(id, label, Codicon.edit.classNames); + super(id, label, ThemeIcon.asClassName(Codicon.edit)); } public override async run(): Promise<void> { @@ -125,7 +129,7 @@ export class DisconnectConnectionAction extends Action { private _connectionProfile: ConnectionProfile, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService ) { - super(id, label, Codicon.debugDisconnect.classNames); + super(id, label, ThemeIcon.asClassName(Codicon.debugDisconnect)); } override async run(actionContext: ObjectExplorerActionsContext): Promise<any> { @@ -148,10 +152,12 @@ export class DisconnectConnectionAction extends Action { } } + /** * Actions to add a server to the group */ -export class AddServerAction extends Action { +// {{SQL CARBON TODO}} - remove old action used in IAction array +export class AddServerAction1 extends Action { public static ID = 'registeredServers.addConnection'; public static LABEL = localize('connectionTree.addConnection', "New Connection"); @@ -191,23 +197,80 @@ export class AddServerAction extends Action { } } + +/** + * Actions to add a server to the group + */ +export class AddServerAction extends Action2 { + public static ID = 'registeredServers.addConnection'; + public static LABEL_ORG = 'New Connection'; + public static LABEL = localize('connectionTree.addConnection', "New Connection"); + + constructor() { + super({ + id: AddServerAction.ID, + icon: { + light: FileAccess.asBrowserUri(`sql/workbench/services/connection/browser/media/add_server.svg`), + dark: FileAccess.asBrowserUri(`sql/workbench/services/connection/browser/media/add_server_inverse.svg`) + }, + title: { value: AddServerAction.LABEL, original: AddServerAction.LABEL_ORG }, + f1: true + }); + } + + public override async run(accessor: ServicesAccessor, element: ConnectionProfileGroup): Promise<void> { + const connectionManagementService = accessor.get(IConnectionManagementService); + // {{SQL CARBON TODO}} - how to get action context for profile group? + // Not sure how to fix this.... + let connection: Partial<IConnectionProfile> | undefined = element === undefined ? undefined : { + connectionName: undefined, + serverName: undefined, + databaseName: undefined, + userName: undefined, + password: undefined, + authenticationType: undefined, + groupId: undefined, + groupFullName: element.fullName, + savePassword: undefined, + getOptionsKey: undefined, + matches: undefined, + providerName: '', + options: {}, + saveProfile: true, + id: element.id! + } as Partial<IConnectionProfile>; + await connectionManagementService.showConnectionDialog(undefined, { + showDashboard: true, + saveTheConnection: true, + showConnectionDialogOnError: true, + showFirewallRuleOnError: true + }, connection); + } +} + /** * Action to open up the dialog to create a new server group */ -export class AddServerGroupAction extends Action { +export class AddServerGroupAction extends Action2 { public static ID = 'registeredServers.addServerGroup'; + public static LABEL_ORG = 'New Server Group'; public static LABEL = localize('connectionTree.addServerGroup', "New Server Group"); - constructor( - id: string, - label: string, - @IServerGroupController private readonly serverGroupController: IServerGroupController - ) { - super(id, label, SqlIconId.addServerGroupAction); + constructor() { + super({ + id: AddServerGroupAction.ID, + icon: { + light: FileAccess.asBrowserUri(`sql/workbench/contrib/objectExplorer/browser/media/new_servergroup.svg`), + dark: FileAccess.asBrowserUri(`sql/workbench/contrib/objectExplorer/browser/media/new_servergroup_inverse.svg`) + }, + title: { value: AddServerGroupAction.LABEL, original: AddServerGroupAction.LABEL_ORG }, + f1: true + }); } - public override async run(): Promise<void> { - return this.serverGroupController.showCreateGroupDialog(); + override async run(accessor: ServicesAccessor): Promise<void> { + const serverGroupController = accessor.get(IServerGroupController); + return serverGroupController.showCreateGroupDialog(); } } @@ -224,7 +287,7 @@ export class EditServerGroupAction extends Action { private _group: ConnectionProfileGroup, @IServerGroupController private readonly serverGroupController: IServerGroupController ) { - super(id, label, Codicon.edit.classNames); + super(id, label, ThemeIcon.asClassName(Codicon.edit)); } public override run(): Promise<void> { @@ -236,22 +299,29 @@ export class EditServerGroupAction extends Action { * Action to toggle filtering the server connections tree to only show * active connections or not. */ -export class ActiveConnectionsFilterAction extends Action { +export class ActiveConnectionsFilterAction extends Action2 { public static ID = 'registeredServers.recentConnections'; + public static SHOW_ACTIVE_CONNECTIONS_LABEL_ORG = 'Show Active Connections'; public static SHOW_ACTIVE_CONNECTIONS_LABEL = localize('activeConnections', "Show Active Connections"); + public static SHOW_ALL_CONNECTIONS_LABEL_ORG = 'Show All Connections'; public static SHOW_ALL_CONNECTIONS_LABEL = localize('showAllConnections', "Show All Connections"); public static readonly ACTIVE = 'active'; - constructor( - id: string, - label: string, - @IObjectExplorerService private _objectExplorerService: IObjectExplorerService - ) { - super(id, label, SqlIconId.activeConnectionsAction); + constructor() { + super({ + id: ActiveConnectionsFilterAction.ID, + icon: { + light: FileAccess.asBrowserUri(`sql/workbench/contrib/objectExplorer/browser/media/connected_active_server.svg`), + dark: FileAccess.asBrowserUri(`sql/workbench/contrib/objectExplorer/browser/media/connected_active_server_inverse.svg`) + }, + title: { value: ActiveConnectionsFilterAction.SHOW_ACTIVE_CONNECTIONS_LABEL_ORG, original: ActiveConnectionsFilterAction.SHOW_ACTIVE_CONNECTIONS_LABEL_ORG }, + f1: true + }); } - public override async run(): Promise<void> { - const serverTreeView = this._objectExplorerService.getServerTreeView(); + override async run(accessor: ServicesAccessor): Promise<void> { + const objectExplorerService = accessor.get(IObjectExplorerService); + const serverTreeView = objectExplorerService.getServerTreeView(); if (serverTreeView.view !== ServerTreeViewView.active) { // show active connections in the tree serverTreeView.showFilteredTree(ServerTreeViewView.active); @@ -277,7 +347,7 @@ export class DeleteConnectionAction extends Action { @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IDialogService private _dialogService: IDialogService ) { - super(id, label, Codicon.trash.classNames); + super(id, label, ThemeIcon.asClassName(Codicon.trash)); if (element instanceof ConnectionProfileGroup && element.id === UNSAVED_GROUP_ID) { this.enabled = false; } @@ -291,22 +361,26 @@ export class DeleteConnectionAction extends Action { } public override async run(): Promise<void> { - - const deleteConnectionConfirmationYes = localize('deleteConnectionConfirmationYes', "Yes"); - const deleteConnectionConfirmationNo = localize('deleteConnectionConfirmationNo', "No"); - if (this.element instanceof ConnectionProfile) { const name = this.element.connectionName || this.element.serverName; - const modalResult = await this._dialogService.show(Severity.Warning, localize('deleteConnectionConfirmation', "Are you sure you want to delete connection '{0}'?", name), - [deleteConnectionConfirmationYes, deleteConnectionConfirmationNo]); - if (modalResult.choice === 0) { + + // {{SQL CARBON TODO}} - check that the confirm dialog is same as before + const result = await this._dialogService.confirm({ + type: Severity.Warning, + message: localize('deleteConnectionConfirmation', "Are you sure you want to delete connection '{0}'?", name) + }); + + if (result.confirmed) { await this._connectionManagementService.deleteConnection(this.element); status(localize('connectionDeleted', "Connection {0} deleted", name)); } } else if (this.element instanceof ConnectionProfileGroup) { - const modalResult = await this._dialogService.show(Severity.Warning, localize('deleteConnectionGroupConfirmation', "Are you sure you want to delete connection group '{0}'?", this.element.name), - [deleteConnectionConfirmationYes, deleteConnectionConfirmationNo]); - if (modalResult.choice === 0) { + const result = await this._dialogService.confirm({ + type: Severity.Warning, + message: localize('deleteConnectionGroupConfirmation', "Are you sure you want to delete connection group '{0}'?", this.element.name) + }); + + if (result.confirmed) { await this._connectionManagementService.deleteConnectionGroup(this.element); status(localize('connectionGroupDeleted', "Connection group {0} deleted", this.element.name)); } @@ -333,7 +407,7 @@ export class FilterChildrenAction extends Action { } function getFilterActionIconClass(node: TreeNode): string { - return node.filters.length > 0 ? Codicon.filterFilled.classNames : Codicon.filter.classNames; + return node.filters.length > 0 ? ThemeIcon.asClassName(Codicon.filterFilled) : ThemeIcon.asClassName(Codicon.filter); } export class RemoveFilterAction extends Action { @@ -390,7 +464,7 @@ export class DeleteRecentConnectionsAction extends Action { private _connectionProfile: ConnectionProfile, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService ) { - super(id, label, Codicon.trash.classNames); + super(id, label, ThemeIcon.asClassName(Codicon.trash)); } public override async run(): Promise<void> { diff --git a/src/sql/workbench/services/objectExplorer/browser/filterDialog/filterDialog.ts b/src/sql/workbench/services/objectExplorer/browser/filterDialog/filterDialog.ts index daadbeec9e..8508d29f8d 100644 --- a/src/sql/workbench/services/objectExplorer/browser/filterDialog/filterDialog.ts +++ b/src/sql/workbench/services/objectExplorer/browser/filterDialog/filterDialog.ts @@ -15,7 +15,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { localize } from 'vs/nls'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; -import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +//import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import * as DOM from 'vs/base/browser/dom'; import * as azdata from 'azdata'; import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; @@ -40,7 +40,6 @@ import { status } from 'vs/base/browser/ui/aria/aria'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; - // strings for filter dialog const OkButtonText = localize('objectExplorer.okButtonText', "OK"); const CancelButtonText = localize('objectExplorer.cancelButtonText', "Cancel"); @@ -83,8 +82,6 @@ const CLEAR_COLUMN_ID = 'clear'; export class FilterDialog extends Modal { private _okButton?: Button; - private _cancelButton?: Button; - private _clearAllButton?: Button; private filterTable: Table<Slick.SlickData>; private _tableCellEditorFactory: TableCellEditorFactory; @@ -143,11 +140,8 @@ export class FilterDialog extends Modal { this.titleIconClassName = TitleIconClass; this._register(attachModalDialogStyler(this, this._themeService)); this._okButton = this.addFooterButton(OkButtonText, async () => { await this.onApply() }); - this._cancelButton = this.addFooterButton(CancelButtonText, () => { this.onClose() }); - this._clearAllButton = this.addFooterButton(ClearAllButtonText, () => { this.onClearAll() }, 'left', true); - this._register(attachButtonStyler(this._okButton, this._themeService)); - this._register(attachButtonStyler(this._cancelButton, this._themeService)); - this._register(attachButtonStyler(this._clearAllButton, this._themeService)); + this.addFooterButton(CancelButtonText, () => { this.onClose() }); + this.addFooterButton(ClearAllButtonText, () => { this.onClearAll() }, 'left', true); } protected renderBody(container: HTMLElement): void { @@ -684,13 +678,13 @@ export class FilterDialog extends Modal { } private styleComponent(component: TabbedPanel | InputBox | Checkbox | Table<Slick.SlickData> | SelectBox | Button | Dropdown): void { - if (component instanceof InputBox) { - this._register(attachInputBoxStyler(component, this._themeService)); - } else if (component instanceof SelectBox) { - this._register(attachSelectBoxStyler(component, this._themeService)); - } else if (component instanceof Table) { - this._register(attachTableStyler(component, this._themeService)); - } + // if (component instanceof InputBox) { + // this._register(attachInputBoxStyler(component, this._themeService)); + // } else if (component instanceof SelectBox) { + // this._register(attachSelectBoxStyler(component, this._themeService)); + // } else if (component instanceof Table) { + // this._register(attachTableStyler(component, this._themeService)); + // } } diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts index 5c93d723a7..c156bcb013 100644 --- a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts +++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts @@ -10,7 +10,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { DisconnectConnectionAction, EditConnectionAction, - DeleteConnectionAction, RefreshAction, EditServerGroupAction, AddServerAction, FilterChildrenAction, RemoveFilterAction, DeleteRecentConnectionsAction + DeleteConnectionAction, RefreshAction, EditServerGroupAction, FilterChildrenAction, RemoveFilterAction, DeleteRecentConnectionsAction, AddServerAction1 } from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction'; import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode'; import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType'; @@ -225,7 +225,7 @@ export class ServerTreeActionProvider { private getConnectionProfileGroupActions(element: ConnectionProfileGroup): IAction[] { // TODO: Should look into using the MenuRegistry for this return [ - this._instantiationService.createInstance(AddServerAction, AddServerAction.ID, AddServerAction.LABEL), + this._instantiationService.createInstance(AddServerAction1, AddServerAction1.ID, AddServerAction1.LABEL), this._instantiationService.createInstance(EditServerGroupAction, EditServerGroupAction.ID, EditServerGroupAction.LABEL, element), this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_GROUP_LABEL, element) ]; diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts index 8edb179488..2ef580b369 100644 --- a/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts +++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts @@ -231,11 +231,13 @@ export class ServerTreeRenderer implements IRenderer { let iconPath = this.getIconPath(connection); this.renderServerIcon(templateData.icon, iconPath, isConnected); - const treeNode = this._objectExplorerService.getObjectExplorerNode(connection); - let label = treeNode?.filters?.length > 0 ? getLabelWithFilteredSuffix(connection.title) : connection.title; - templateData.label.textContent = label; - templateData.root.title = treeNode?.filters?.length > 0 ? getLabelWithFilteredSuffix(connection.title) : connection.serverInfo; - templateData.connectionProfile = connection; + if (this._objectExplorerService) { + const treeNode = this._objectExplorerService.getObjectExplorerNode(connection); + let label = treeNode?.filters?.length > 0 ? getLabelWithFilteredSuffix(connection.title) : connection.title; + templateData.label.textContent = label; + templateData.root.title = treeNode?.filters?.length > 0 ? getLabelWithFilteredSuffix(connection.title) : connection.serverInfo; + templateData.connectionProfile = connection; + } } private renderConnectionProfileGroup(connectionProfileGroup: ConnectionProfileGroup, templateData: IConnectionProfileGroupTemplateData): void { diff --git a/src/sql/workbench/services/profiler/browser/profilerColumnEditorDialog.ts b/src/sql/workbench/services/profiler/browser/profilerColumnEditorDialog.ts index 066731405b..aa72ce6bcd 100644 --- a/src/sql/workbench/services/profiler/browser/profilerColumnEditorDialog.ts +++ b/src/sql/workbench/services/profiler/browser/profilerColumnEditorDialog.ts @@ -18,7 +18,7 @@ import { Tree } from 'sql/base/parts/tree/browser/treeImpl'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as DOM from 'vs/base/browser/dom'; import { IDataSource, ITree, IRenderer } from 'sql/base/parts/tree/browser/tree'; -import { attachListStyler } from 'vs/platform/theme/common/styler'; +import { attachListStyler } from 'sql/platform/theme/common/vsstyler'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -27,6 +27,7 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; class EventItem { @@ -319,7 +320,7 @@ export class ProfilerColumnEditorDialog extends Modal { protected renderBody(container: HTMLElement): void { const body = DOM.append(container, DOM.$('')); - this._selectBox = new SelectBox(this._options, 0, this._contextViewService); + this._selectBox = new SelectBox(this._options, 0, this._contextViewService, defaultSelectBoxStyles); this._selectBox.render(body); this._register(this._selectBox.onDidSelect(e => { this._element!.changeSort(e.index === 0 ? 'event' : 'column'); diff --git a/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts b/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts index 9b3e562652..f7a29a1359 100644 --- a/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts +++ b/src/sql/workbench/services/profiler/browser/profilerFilterDialog.ts @@ -16,7 +16,7 @@ import { localize } from 'vs/nls'; import { ProfilerInput } from 'sql/workbench/browser/editor/profiler/profilerInput'; import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox'; -import { attachButtonStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachSelectBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { generateUuid } from 'vs/base/common/uuid'; import * as DOM from 'vs/base/browser/dom'; @@ -29,6 +29,7 @@ import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import * as aria from 'vs/base/browser/ui/aria/aria'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; const ClearText: string = localize('profilerFilterDialog.clear', "Clear all"); const ApplyText: string = localize('profilerFilterDialog.apply', "Apply"); @@ -109,11 +110,11 @@ export class ProfilerFilterDialog extends Modal { this._applyButton = this.addFooterButton(ApplyText, () => this.filterSession(), 'right', true); this._okButton = this.addFooterButton(OkText, () => this.handleOkButtonClick()); this._cancelButton = this.addFooterButton(CancelText, () => this.hide('cancel'), 'right', true); - this._register(attachButtonStyler(this._okButton, this._themeService)); - this._register(attachButtonStyler(this._cancelButton, this._themeService)); - this._register(attachButtonStyler(this._applyButton, this._themeService)); - this._register(attachButtonStyler(this._saveFilterButton, this._themeService)); - this._register(attachButtonStyler(this._loadFilterButton, this._themeService)); + this._register(this._okButton); + this._register(this._cancelButton); + this._register(this._applyButton); + this._register(this._saveFilterButton); + this._register(this._loadFilterButton); } protected renderBody(container: HTMLElement) { @@ -246,7 +247,10 @@ export class ProfilerFilterDialog extends Modal { const operatorDropDown = this.createSelectBox(DOM.append(row, DOM.$('td')), Operators, Operators[0], OperatorText); - const valueText = new InputBox(DOM.append(row, DOM.$('td')), this.contextViewService, { ariaLabel: ValueText }); + const valueText = new InputBox(DOM.append(row, DOM.$('td')), this.contextViewService, { + ariaLabel: ValueText, + inputBoxStyles: defaultInputBoxStyles + }); this._register(attachInputBoxStyler(valueText, this._themeService)); const removeCell = DOM.append(row, DOM.$('td')); diff --git a/src/sql/workbench/services/resourceProvider/browser/firewallRuleDialog.ts b/src/sql/workbench/services/resourceProvider/browser/firewallRuleDialog.ts index 4a6d0b5ed5..92e44e0b01 100644 --- a/src/sql/workbench/services/resourceProvider/browser/firewallRuleDialog.ts +++ b/src/sql/workbench/services/resourceProvider/browser/firewallRuleDialog.ts @@ -10,9 +10,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { localize } from 'vs/nls'; import { buttonBackground } from 'vs/platform/theme/common/colorRegistry'; -import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { attachInputBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; @@ -31,6 +30,7 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; // TODO: Make the help link 1) extensible (01/08/2018, https://github.com/Microsoft/azuredatastudio/issues/450) // in case that other non-Azure sign in is to be used @@ -116,7 +116,7 @@ export class FirewallRuleDialog extends Modal { super.render(); attachModalDialogStyler(this, this._themeService); this.backButton!.onDidClick(() => this.cancel()); - this._register(attachButtonStyler(this.backButton!, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND })); + this._register(this.backButton!); this._createButton = this.addFooterButton(LocalizedStrings.OK, () => this.createFirewallRule()); this._closeButton = this.addFooterButton(LocalizedStrings.Cancel, () => this.cancel(), 'right', true); this.registerListeners(); @@ -139,7 +139,8 @@ export class FirewallRuleDialog extends Modal { const descInputContainer = DOM.append(descriptionDiv, DOM.$('.dialog-input-section')); DOM.append(descInputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.RuleName; this._ruleNameInpuBox = new InputBox(DOM.append(descInputContainer, DOM.$('.dialog-input')), this._contextViewService, { - ariaLabel: LocalizedStrings.RuleName + ariaLabel: LocalizedStrings.RuleName, + inputBoxStyles: defaultInputBoxStyles }); // Single IP Address radio button @@ -169,12 +170,14 @@ export class FirewallRuleDialog extends Modal { DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.FROM; this._fromRangeinputBox = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, { - ariaLabel: LocalizedStrings.FROM + ariaLabel: LocalizedStrings.FROM, + inputBoxStyles: defaultInputBoxStyles }); DOM.append(inputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.TO; this._toRangeinputBox = new InputBox(DOM.append(inputContainer, DOM.$('.dialog-input')), this._contextViewService, { - ariaLabel: LocalizedStrings.TO + ariaLabel: LocalizedStrings.TO, + inputBoxStyles: defaultInputBoxStyles }); // Register events @@ -261,8 +264,8 @@ export class FirewallRuleDialog extends Modal { private registerListeners(): void { // Theme styler - this._register(attachButtonStyler(this._createButton!, this._themeService)); - this._register(attachButtonStyler(this._closeButton!, this._themeService)); + this._register(this._createButton!); + this._register(this._closeButton!); this._register(attachInputBoxStyler(this._ruleNameInpuBox!, this._themeService)); this._register(attachInputBoxStyler(this._fromRangeinputBox!, this._themeService)); this._register(attachInputBoxStyler(this._toRangeinputBox!, this._themeService)); diff --git a/src/sql/workbench/services/restore/browser/restoreDialog.ts b/src/sql/workbench/services/restore/browser/restoreDialog.ts index b778c74ba6..efaf0efafb 100644 --- a/src/sql/workbench/services/restore/browser/restoreDialog.ts +++ b/src/sql/workbench/services/restore/browser/restoreDialog.ts @@ -30,7 +30,7 @@ import { Table } from 'sql/base/browser/ui/table/table'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper'; import { HideReason, Modal } from 'sql/workbench/browser/modal/modal'; -import { attachTableStyler, attachInputBoxStyler, attachSelectBoxStyler, attachEditableDropdownStyler, attachCheckboxStyler } from 'sql/platform/theme/common/styler'; +import { attachTableStyler, attachInputBoxStyler, attachSelectBoxStyler, attachEditableDropdownStyler } from 'sql/platform/theme/common/styler'; import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { RestoreViewModel, RestoreOptionParam, SouceDatabaseNamesParam } from 'sql/workbench/services/restore/browser/restoreViewModel'; import * as FileValidationConstants from 'sql/workbench/services/fileBrowser/common/fileValidationServiceConstants'; @@ -43,7 +43,6 @@ import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler, attachTabbedPanelStyler } from 'sql/workbench/common/styler'; import { fileFiltersSet } from 'sql/workbench/services/restore/common/constants'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { Dropdown } from 'sql/base/browser/ui/editableDropdown/browser/dropdown'; import { IBackupRestoreUrlBrowserDialogService } from 'sql/workbench/services/backupRestoreUrlBrowser/common/urlBrowserDialogService'; import { MediaDeviceType } from 'sql/workbench/contrib/backup/common/constants'; @@ -51,6 +50,8 @@ import { ITextResourcePropertiesService } from 'vs/editor/common/services/textRe import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; +import { defaultButtonStyles, defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultCheckboxStyles } from 'sql/platform/theme/browser/defaultStyles'; interface FileListElement { logicalFileName: string; @@ -202,7 +203,8 @@ export class RestoreDialog extends Modal { validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: urlErrorMessage }) : null }, placeholder: localize('enterBackupUrl', "Please enter URL"), - ariaLabel: LocalizedStrings.BACKURL + ariaLabel: LocalizedStrings.BACKURL, + inputBoxStyles: defaultInputBoxStyles }; const urlInputContainer = DOM.append(this._restoreFromUrlElement, DOM.$('.dialog-input-section')); DOM.append(urlInputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.BACKURL; @@ -213,7 +215,7 @@ export class RestoreDialog extends Modal { DOM.append(urlBrowseContainer, DOM.$('.dialog-label')).innerText = ''; let browseLabel = localize('restoreDialog.browse', "Browse"); - this._browseUrlButton = this._register(new Button(DOM.append(urlBrowseContainer, DOM.$('.file-browser')), { secondary: true })); + this._browseUrlButton = this._register(new Button(DOM.append(urlBrowseContainer, DOM.$('.file-browser')), { secondary: true, ...defaultButtonStyles })); this._browseUrlButton.label = browseLabel; this._browseUrlButton.setWidth('50px'); @@ -226,14 +228,15 @@ export class RestoreDialog extends Modal { validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: errorMessage }) : null }, placeholder: localize('multipleBackupFilePath', "Please enter one or more file paths separated by commas"), - ariaLabel: LocalizedStrings.BACKFILEPATH + ariaLabel: LocalizedStrings.BACKFILEPATH, + inputBoxStyles: defaultInputBoxStyles }; const filePathInputContainer = DOM.append(this._restoreFromBackupFileElement, DOM.$('.dialog-input-section')); DOM.append(filePathInputContainer, DOM.$('.dialog-label')).innerText = LocalizedStrings.BACKFILEPATH; this._filePathInputBox = this._register(new InputBox(DOM.append(filePathInputContainer, DOM.$('.dialog-input')), this._contextViewService, validationOptions)); - this._browseFileButton = this._register(new Button(DOM.append(filePathInputContainer, DOM.$('.file-browser')), { secondary: true })); + this._browseFileButton = this._register(new Button(DOM.append(filePathInputContainer, DOM.$('.file-browser')), { secondary: true, ...defaultButtonStyles })); this._browseFileButton.label = '...'; this._sourceDatabasesElement = DOM.$('.source-database-list'); @@ -295,6 +298,7 @@ export class RestoreDialog extends Modal { validationOptions: { validation: (value: string) => this.viewModel.databases?.includes(value) ? ({ type: MessageType.ERROR, content: localize('restoreDialog.targetDatabaseAlreadyExists', "Target database already exists") }) : null }, + inputBoxStyles: defaultInputBoxStyles })); const restoreToLabel = localize('restoreTo', "Restore to"); @@ -550,12 +554,12 @@ export class RestoreDialog extends Modal { private createCheckBoxHelper(container: HTMLElement, label: string, isChecked: boolean, onCheck: (viaKeyboard: boolean) => void): Checkbox { const checkbox = this._register(new Checkbox(DOM.append(container, DOM.$('.dialog-input-section')), { + ...defaultCheckboxStyles, label: label, checked: isChecked, onChange: onCheck, ariaLabel: label })); - this._register(attachCheckboxStyler(checkbox, this._themeService)); return checkbox; } @@ -665,11 +669,11 @@ export class RestoreDialog extends Modal { this._register(attachInputBoxStyler(this._destinationRestoreToInputBox!, this._themeService)); this._register(attachSelectBoxStyler(this._restoreFromSelectBox!, this._themeService)); this._register(attachSelectBoxStyler(this._sourceDatabaseSelectBox!, this._themeService)); - this._register(attachButtonStyler(this._browseFileButton!, this._themeService)); - this._register(attachButtonStyler(this._browseUrlButton!, this._themeService)); - this._register(attachButtonStyler(this._scriptButton!, this._themeService)); - this._register(attachButtonStyler(this._restoreButton!, this._themeService)); - this._register(attachButtonStyler(this._closeButton!, this._themeService)); + this._register(this._browseFileButton!); + this._register(this._browseUrlButton!); + this._register(this._scriptButton!); + this._register(this._restoreButton!); + this._register(this._closeButton!); this._register(attachTableStyler(this._fileListTable!, this._themeService)); this._register(attachTableStyler(this._restorePlanTable!, this._themeService)); diff --git a/src/sql/workbench/services/serverGroup/browser/serverGroupDialog.ts b/src/sql/workbench/services/serverGroup/browser/serverGroupDialog.ts index aa53d8f21e..1b56c3b749 100644 --- a/src/sql/workbench/services/serverGroup/browser/serverGroupDialog.ts +++ b/src/sql/workbench/services/serverGroup/browser/serverGroupDialog.ts @@ -11,7 +11,7 @@ import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { attachInputBoxStyler, attachToggleStyler, attachButtonStyler } from 'vs/platform/theme/common/styler'; +import { attachInputBoxStyler } from 'sql/platform/theme/common/vsstyler'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { localize } from 'vs/nls'; @@ -24,13 +24,13 @@ import { ServerGroupViewModel } from 'sql/workbench/services/serverGroup/common/ import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { ILogService } from 'vs/platform/log/common/log'; -import { Color } from 'vs/base/common/color'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; import { attachModalDialogStyler } from 'sql/workbench/common/styler'; import { assertIsDefined, isUndefinedOrNull } from 'vs/base/common/types'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { RequiredIndicatorClassName } from 'sql/base/browser/ui/label/label'; +import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; interface IRenderedServerGroupDialog { groupNameInputBox: InputBox; @@ -104,7 +104,8 @@ export class ServerGroupDialog extends Modal { validation: (value: string) => !value && !this._skipGroupNameValidation ? ({ type: MessageType.ERROR, content: localize('MissingGroupNameError', "Group name is required.") }) : null }, ariaLabel: serverGroupNameLabel, - required: true + required: true, + inputBoxStyles: defaultInputBoxStyles }); // Connection Group Description @@ -112,7 +113,8 @@ export class ServerGroupDialog extends Modal { DOM.append(body, DOM.$('.dialog-label')).innerText = groupDescriptionLabel; this._groupDescriptionInputBox = new InputBox(DOM.append(body, DOM.$('.input-divider')), this._contextViewService, { - ariaLabel: groupDescriptionLabel + ariaLabel: groupDescriptionLabel, + inputBoxStyles: defaultInputBoxStyles }); // Connection Group Color @@ -176,8 +178,8 @@ export class ServerGroupDialog extends Modal { // Theme styler this._register(attachInputBoxStyler(renderedDialog.groupNameInputBox, this._themeService)); this._register(attachInputBoxStyler(renderedDialog.groupDescriptionInputBox, this._themeService)); - this._register(attachButtonStyler(renderedDialog.addServerButton, this._themeService)); - this._register(attachButtonStyler(renderedDialog.closeButton, this._themeService)); + this._register(renderedDialog.addServerButton); + this._register(renderedDialog.closeButton); // handler for name change events this._register(renderedDialog.groupNameInputBox.onDidChange(groupName => { @@ -196,18 +198,12 @@ export class ServerGroupDialog extends Modal { const colorBox = new Colorbox(container, { name: 'server-group-color', - label: color + color: color }); this._register(colorBox.onSelect((viaKeyboard) => { this.onSelectGroupColor(color); })); - colorBox.style({ - backgroundColor: Color.fromHex(color) - }); - - // Theme styler - this._register(attachToggleStyler(colorBox, this._themeService)); // add the new colorbox to the color map this._colorColorBoxesMap[i] = { color, colorbox: colorBox }; diff --git a/src/sql/workbench/services/tableDesigner/browser/tableDesignerPublishDialog.ts b/src/sql/workbench/services/tableDesigner/browser/tableDesignerPublishDialog.ts index 5e5a3679ac..0819e308f1 100644 --- a/src/sql/workbench/services/tableDesigner/browser/tableDesignerPublishDialog.ts +++ b/src/sql/workbench/services/tableDesigner/browser/tableDesignerPublishDialog.ts @@ -11,7 +11,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { localize } from 'vs/nls'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import * as DOM from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; @@ -21,9 +20,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { Mimes } from 'vs/base/common/mime'; import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox'; import * as azdata from 'azdata'; -import { attachCheckboxStyler } from 'sql/platform/theme/common/styler'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; +import { defaultCheckboxStyles } from 'sql/platform/theme/browser/defaultStyles'; const OkText: string = localize('tableDesigner.UpdateDatabase', "Update Database"); const CancelText: string = localize('tableDesigner.cancel', "Cancel"); @@ -78,9 +77,9 @@ export class TableDesignerPublishDialog extends Modal { const requireConfirmation = this._report.requireConfirmation === true; this._okButton.enabled = !requireConfirmation; this._generateScriptButton.enabled = !requireConfirmation; - this._register(attachButtonStyler(this._okButton, this._themeService)); - this._register(attachButtonStyler(this._generateScriptButton, this._themeService)); - this._register(attachButtonStyler(this._cancelButton, this._themeService)); + this._register(this._okButton); + this._register(this._generateScriptButton); + this._register(this._cancelButton); } protected renderBody(container: HTMLElement) { @@ -96,6 +95,7 @@ export class TableDesignerPublishDialog extends Modal { if (this._report.requireConfirmation && this._report.confirmationText) { const checkboxContainer = DOM.append(body, DOM.$('div')); const checkbox = new Checkbox(checkboxContainer, { + ...defaultCheckboxStyles, label: this._report.confirmationText, checked: false }); @@ -103,7 +103,6 @@ export class TableDesignerPublishDialog extends Modal { this._okButton.enabled = checked; this._generateScriptButton.enabled = checked; })); - this._register(attachCheckboxStyler(checkbox, this._themeService)); } } diff --git a/src/sql/workbench/services/tasks/browser/tasksRegistry.ts b/src/sql/workbench/services/tasks/browser/tasksRegistry.ts index 3cae17435c..3dd7256e57 100644 --- a/src/sql/workbench/services/tasks/browser/tasksRegistry.ts +++ b/src/sql/workbench/services/tasks/browser/tasksRegistry.ts @@ -14,8 +14,8 @@ import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { IdGenerator } from 'vs/base/common/idGenerator'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ICommandAction } from 'vs/platform/action/common/action'; +import { ThemeIcon } from 'vs/base/common/themables'; const ids = new IdGenerator('task-icon-'); @@ -56,14 +56,15 @@ export const TaskRegistry: ITaskRegistry = new class implements ITaskRegistry { getOrCreateTaskIconClassName(item: ICommandAction): string | undefined { let iconClass: string | undefined; + let icon: any = item.icon; if (this.taskIdToIconClassNameMap.has(item.id)) { iconClass = this.taskIdToIconClassNameMap.get(item.id); } else if (ThemeIcon.isThemeIcon(item.icon)) { // TODO - } else if (item.icon?.dark) { // at the very least we need a dark icon + } else if (icon?.dark) { // at the very least we need a dark icon iconClass = ids.nextId(); - createCSSRule(`.codicon.${iconClass}, .hc-light .codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`); - createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`); + createCSSRule(`.codicon.${iconClass}, .hc-light .codicon.${iconClass}`, `background-image: ${asCSSUrl(icon.light || icon.dark)}`); + createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(icon.dark)}`); this.taskIdToIconClassNameMap.set(item.id, iconClass); } return iconClass; diff --git a/src/sql/workbench/services/tasks/common/tasksService.ts b/src/sql/workbench/services/tasks/common/tasksService.ts index 9437a5b697..84d1ced365 100644 --- a/src/sql/workbench/services/tasks/common/tasksService.ts +++ b/src/sql/workbench/services/tasks/common/tasksService.ts @@ -151,37 +151,35 @@ export class TaskService implements ITaskService { public beforeShutdown(): Promise<boolean> { const message = localize('InProgressWarning', "1 or more tasks are in progress. Are you sure you want to quit?"); - const options = [ - localize('taskService.yes', "Yes"), - localize('taskService.no', "No") - ]; - return new Promise<boolean>((resolve, reject) => { let numOfInprogressTasks = this.getNumberOfInProgressTasks(); if (numOfInprogressTasks > 0) { - this.dialogService.show(Severity.Warning, message, options).then(choice => { - switch (choice.choice) { - case 0: - let timeout: any; - let isTimeout = false; - this.cancelAllTasks().then(() => { - clearTimeout(timeout); - if (!isTimeout) { - resolve(false); - } - }, error => { - clearTimeout(timeout); - if (!isTimeout) { - resolve(false); - } - }); - timeout = setTimeout(function () { - isTimeout = true; + this.dialogService.confirm({ + type: Severity.Warning, + message: message, + primaryButton: localize('taskService.yes', "Yes"), + cancelButton: localize('taskService.no', "No") + }).then(result => { + if (result.confirmed) { + let timeout: any; + let isTimeout = false; + this.cancelAllTasks().then(() => { + clearTimeout(timeout); + if (!isTimeout) { resolve(false); - }, 2000); - break; - case 1: - resolve(true); + } + }, error => { + clearTimeout(timeout); + if (!isTimeout) { + resolve(false); + } + }); + timeout = setTimeout(function () { + isTimeout = true; + resolve(false); + }, 2000); + } else { + resolve(true); } }); } else { diff --git a/src/sql/workbench/test/browser/modal/optionsDialogHelper.test.ts b/src/sql/workbench/test/browser/modal/optionsDialogHelper.test.ts index 4e88528153..762850c564 100644 --- a/src/sql/workbench/test/browser/modal/optionsDialogHelper.test.ts +++ b/src/sql/workbench/test/browser/modal/optionsDialogHelper.test.ts @@ -98,7 +98,7 @@ suite('Advanced options helper tests', () => { isArray: undefined }; - inputBox = TypeMoq.Mock.ofType(InputBox, TypeMoq.MockBehavior.Loose, $('div'), null, null); + inputBox = TypeMoq.Mock.ofType(InputBox, TypeMoq.MockBehavior.Loose, $('div'), null, {}); inputBox.callBase = true; inputBox.setup(x => x.validate()).returns(() => isValid ? undefined : MessageType.ERROR); inputBox.setup(x => x.value).returns(() => inputValue); diff --git a/src/sql/workbench/update/electron-browser/gettingStarted.contribution.ts b/src/sql/workbench/update/electron-browser/gettingStarted.contribution.ts index a678bfa0fc..5c399ec02e 100644 --- a/src/sql/workbench/update/electron-browser/gettingStarted.contribution.ts +++ b/src/sql/workbench/update/electron-browser/gettingStarted.contribution.ts @@ -3,11 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { ShowGettingStartedAction } from 'sql/workbench/update/electron-browser/gettingStarted'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { Registry } from 'vs/platform/registry/common/platform'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; -// add getting started contributions -Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions) - .registerWorkbenchAction(SyncActionDescriptor.create(ShowGettingStartedAction, ShowGettingStartedAction.ID, ShowGettingStartedAction.LABEL), 'Show Getting Started'); +registerAction2(ShowGettingStartedAction); diff --git a/src/sql/workbench/update/electron-browser/gettingStarted.ts b/src/sql/workbench/update/electron-browser/gettingStarted.ts index cda0b3874c..547739c956 100644 --- a/src/sql/workbench/update/electron-browser/gettingStarted.ts +++ b/src/sql/workbench/update/electron-browser/gettingStarted.ts @@ -4,27 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import nls = require('vs/nls'); -import { Action } from 'vs/base/common/actions'; import product from 'vs/platform/product/common/product'; import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { MenuRegistry, MenuId, Action2 } from 'vs/platform/actions/common/actions'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -export class ShowGettingStartedAction extends Action { +export class ShowGettingStartedAction extends Action2 { static ID = 'update.showGettingStarted'; + static LABEL_ORG = 'Show Getting Started'; static LABEL = nls.localize('showReleaseNotes', "Show Getting Started"); - constructor( - id = ShowGettingStartedAction.ID, - label = ShowGettingStartedAction.LABEL, - @IOpenerService private openerService: IOpenerService - ) { - super(id, label, undefined, true); + constructor() { + super({ + id: ShowGettingStartedAction.ID, + title: { + value: ShowGettingStartedAction.LABEL, + original: ShowGettingStartedAction.LABEL_ORG + } + }); } - override run(): Promise<any> { + override run(accessor: ServicesAccessor): Promise<any> { + const openerService = accessor.get(IOpenerService); const uri = URI.parse(product.gettingStartedUrl); - return this.openerService.open(uri); + return openerService.open(uri); } } diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index be1f42c090..ff94053352 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -44,6 +44,9 @@ "ES2020.String", "ES2020.Symbol.WellKnown", "ES2020.Intl", + "ES2021.Promise", + "ES2021.String", + "ES2021.WeakRef", "DOM", "DOM.Iterable", "WebWorker.ImportScripts" diff --git a/src/tsconfig.json b/src/tsconfig.json index 54366c878b..5b0c19c811 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -4,8 +4,10 @@ "removeComments": false, "preserveConstEnums": true, "sourceMap": false, + "allowJs": true, + "resolveJsonModule": true, "outDir": "../out/vs", - "target": "es2020", + "target": "es2021", "types": [ "keytar", "mocha", @@ -22,8 +24,11 @@ ] }, "include": [ + "./bootstrap.js", + "./bootstrap-amd.js", + "./server-main.js", "./typings", - "./vs", + "./vs/**/*.ts", "./sql", "vscode-dts/vscode.proposed.*.d.ts", "vscode-dts/vscode.d.ts" diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 057aa8748a..256f406fb6 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -18,6 +18,7 @@ "include": [ "typings/require.d.ts", "typings/thenable.d.ts", + "typings/vscode-globals-product.d.ts", "vs/loader.d.ts", "vs/monaco.d.ts", "vs/editor/*", diff --git a/src/tsec.exemptions.json b/src/tsec.exemptions.json index c2479eb2c7..412844a60d 100644 --- a/src/tsec.exemptions.json +++ b/src/tsec.exemptions.json @@ -10,27 +10,13 @@ "vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils.ts" ], "ban-trustedtypes-createpolicy": [ - "vs/base/browser/dom.ts", - "vs/base/browser/markdownRenderer.ts", - "vs/base/browser/defaultWorkerFactory.ts", + "vs/base/browser/trustedTypes.ts", "vs/base/worker/workerMain.ts", - "vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts", - "vs/editor/contrib/stickyScroll/browser/stickyScroll.ts", - "vs/editor/browser/view/domLineBreaksComputer.ts", - "vs/editor/browser/view/viewLayer.ts", - "vs/editor/browser/widget/diffEditorWidget.ts", - "vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.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/cellParts/cellDragRenderer.ts", - "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts", - "vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts" + "vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts" ], "ban-worker-calls": [ "vs/base/browser/defaultWorkerFactory.ts", - "vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts", - "vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService.ts" + "vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts" ], "ban-element-innerhtml-assignments": [ "sql/workbench/browser/modal/modal.ts", diff --git a/src/typings/require.d.ts b/src/typings/require.d.ts index 7c7db4b411..cf69dc92f7 100644 --- a/src/typings/require.d.ts +++ b/src/typings/require.d.ts @@ -42,12 +42,19 @@ declare const define: { interface NodeRequire { toUrl(path: string): string; + + /** + * @deprecated MUST not be used anymore + * + * With the move from AMD to ESM we cannot use this anymore. There will be NO MORE node require like this. + */ + __$__nodeRequire<T>(moduleName: string): T; + (dependencies: string[], callback: (...args: any[]) => any, errorback?: (err: any) => void): any; config(data: any): any; onError: Function; - __$__nodeRequire<T>(moduleName: string): T; - getStats(): ReadonlyArray<LoaderEvent>; - hasDependencyCycle(): boolean; + getStats?(): ReadonlyArray<LoaderEvent>; + hasDependencyCycle?(): boolean; define(amdModuleId: string, dependencies: string[], callback: (...args: any[]) => any): any; } diff --git a/src/typings/vscode-globals-modules.d.ts b/src/typings/vscode-globals-modules.d.ts new file mode 100644 index 0000000000..0d3037ba8e --- /dev/null +++ b/src/typings/vscode-globals-modules.d.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// AMD2ESM mirgation relevant + +declare global { + + /** + * @deprecated node modules that are in used in a context that + * shouldn't have access to node_modules (node-free renderer or + * shared process) + */ + var _VSCODE_NODE_MODULES: { + crypto: typeof import('crypto'); + zlib: typeof import('zlib'); + net: typeof import('net'); + os: typeof import('os'); + module: typeof import('module'); + ['native-watchdog']: typeof import('native-watchdog') + perf_hooks: typeof import('perf_hooks'); + + ['vsda']: any + ['vscode-encrypt']: any + } +} + +// fake export to make global work +export { } diff --git a/src/typings/vscode-globals-product.d.ts b/src/typings/vscode-globals-product.d.ts new file mode 100644 index 0000000000..c625ae6822 --- /dev/null +++ b/src/typings/vscode-globals-product.d.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// AMD2ESM mirgation relevant + +declare global { + + /** + * @deprecated You MUST use `IProductService` whenever possible. + */ + var _VSCODE_PRODUCT_JSON: Record<string, any>; + /** + * @deprecated You MUST use `IProductService` whenever possible. + */ + var _VSCODE_PACKAGE_JSON: Record<string, any>; + +} + +// fake export to make global work +export { } diff --git a/src/typings/windows-mutex.d.ts b/src/typings/windows-mutex.d.ts new file mode 100644 index 0000000000..0a6a13cc5f --- /dev/null +++ b/src/typings/windows-mutex.d.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Copied from the `@types/windows-mutex` package. +// The dependency is an optional dependency that is only used on Windows, +// but we need the typings to compile on all platforms. +// The types package exported from DefinitelyTyped also maps to `windows-mutex`, +// whereas we are now using `@vscode/windows-mutex`. +declare module '@vscode/windows-mutex' { + export class Mutex { + constructor(name: string); + isActive(): boolean; + release(): void; + } + + export function isActive(name: string): boolean; +} diff --git a/src/typings/windows-process-tree.d.ts b/src/typings/windows-process-tree.d.ts new file mode 100644 index 0000000000..658e68b1f5 --- /dev/null +++ b/src/typings/windows-process-tree.d.ts @@ -0,0 +1,75 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Copied from the `@vscode/windows-process-tree` package. +// The dependency is an optional dependency that is only used on Windows, +// but we need the typings to compile on all platforms. +// The `@types/windows-process-tree` package has also been deprecated. +declare module '@vscode/windows-process-tree' { + export enum ProcessDataFlag { } + + export interface IProcessInfo { + pid: number; + ppid: number; + name: string; + + /** + * The working set size of the process, in bytes. + */ + memory?: number; + + /** + * The string returned is at most 512 chars, strings exceeding this length are truncated. + */ + commandLine?: string; + } + + export interface IProcessCpuInfo extends IProcessInfo { + cpu?: number; + } + + export interface IProcessTreeNode { + pid: number; + name: string; + memory?: number; + commandLine?: string; + children: IProcessTreeNode[]; + } + + /** + * Returns a tree of processes with the rootPid process as the root. + * @param rootPid - The pid of the process that will be the root of the tree. + * @param callback - The callback to use with the returned list of processes. + * @param flags - The flags for what process data should be included. + */ + export function getProcessTree(rootPid: number, callback: (tree: IProcessTreeNode | undefined) => void, flags?: ProcessDataFlag): void; + + namespace getProcessTree { + function __promisify__(rootPid: number, flags?: ProcessDataFlag): Promise<IProcessTreeNode>; + } + + /** + * Returns a list of processes containing the rootPid process and all of its descendants. + * @param rootPid - The pid of the process of interest. + * @param callback - The callback to use with the returned set of processes. + * @param flags - The flags for what process data should be included. + */ + export function getProcessList(rootPid: number, callback: (processList: IProcessInfo[] | undefined) => void, flags?: ProcessDataFlag): void; + + namespace getProcessList { + function __promisify__(rootPid: number, flags?: ProcessDataFlag): Promise<IProcessInfo[]>; + } + + /** + * Returns the list of processes annotated with cpu usage information. + * @param processList - The list of processes. + * @param callback - The callback to use with the returned list of processes. + */ + export function getProcessCpuUsage(processList: IProcessInfo[], callback: (processListWithCpu: IProcessCpuInfo[]) => void): void; + + namespace getProcessCpuUsage { + function __promisify__(processList: IProcessInfo[]): Promise<IProcessCpuInfo[]>; + } +} diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 23ff961ef5..d064ebc5f3 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -73,7 +73,7 @@ class DevicePixelRatioMonitor extends Disposable { private _handleChange(fireEvent: boolean): void { this._mediaQueryList?.removeEventListener('change', this._listener); - this._mediaQueryList = matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); + this._mediaQueryList = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); this._mediaQueryList.addEventListener('change', this._listener); if (fireEvent) { @@ -194,12 +194,26 @@ export const isAndroid = (userAgent.indexOf('Android') >= 0); let standalone = false; if (window.matchMedia) { - const matchMedia = window.matchMedia('(display-mode: standalone)'); - standalone = matchMedia.matches; - addMatchMediaChangeListener(matchMedia, ({ matches }) => { + const standaloneMatchMedia = window.matchMedia('(display-mode: standalone) or (display-mode: window-controls-overlay)'); + const fullScreenMatchMedia = window.matchMedia('(display-mode: fullscreen)'); + standalone = standaloneMatchMedia.matches; + addMatchMediaChangeListener(standaloneMatchMedia, ({ matches }) => { + // entering fullscreen would change standaloneMatchMedia.matches to false + // if standalone is true (running as PWA) and entering fullscreen, skip this change + if (standalone && fullScreenMatchMedia.matches) { + return; + } + // otherwise update standalone (browser to PWA or PWA to browser) standalone = matches; }); } export function isStandalone(): boolean { return standalone; } + +// Visible means that the feature is enabled, not necessarily being rendered +// e.g. visible is true even in fullscreen mode where the controls are hidden +// See docs at https://developer.mozilla.org/en-US/docs/Web/API/WindowControlsOverlay/visible +export function isWCOEnabled(): boolean { + return (navigator as any)?.windowControlsOverlay?.visible; +} diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 96d077df0d..dcc40bb056 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IAction, IActionRunner } from 'vs/base/common/actions'; @@ -19,12 +20,13 @@ export interface IContextMenuDelegate { getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number }; getActions(): readonly IAction[]; getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox'; - getActionViewItem?(action: IAction): IActionViewItem | undefined; + getActionViewItem?(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined; getActionsContext?(event?: IContextMenuEvent): unknown; getKeyBinding?(action: IAction): ResolvedKeybinding | undefined; getMenuClassName?(): string; onHide?(didCancel: boolean): void; actionRunner?: IActionRunner; + skipTelemetry?: boolean; autoSelectFirstItem?: boolean; anchorAlignment?: AnchorAlignment; anchorAxisAlignment?: AnchorAxisAlignment; diff --git a/src/vs/base/browser/defaultWorkerFactory.ts b/src/vs/base/browser/defaultWorkerFactory.ts index a7e79f1790..b3cf56da40 100644 --- a/src/vs/base/browser/defaultWorkerFactory.ts +++ b/src/vs/base/browser/defaultWorkerFactory.ts @@ -3,19 +3,32 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { globals } from 'vs/base/common/platform'; +import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes'; +import { COI } from 'vs/base/common/network'; import { IWorker, IWorkerCallback, IWorkerFactory, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker'; -const ttPolicy = window.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value }); +const ttPolicy = createTrustedTypesPolicy('defaultWorkerFactory', { createScriptURL: value => value }); + +export function createBlobWorker(blobUrl: string, options?: WorkerOptions): Worker { + if (!blobUrl.startsWith('blob:')) { + throw new URIError('Not a blob-url: ' + blobUrl); + } + return new Worker(ttPolicy ? ttPolicy.createScriptURL(blobUrl) as unknown as string : blobUrl, options); +} function getWorker(label: string): Worker | Promise<Worker> { // Option for hosts to overwrite the worker script (used in the standalone editor) - if (globals.MonacoEnvironment) { - if (typeof globals.MonacoEnvironment.getWorker === 'function') { - return globals.MonacoEnvironment.getWorker('workerMain.js', label); + interface IMonacoEnvironment { + getWorker?(moduleId: string, label: string): Worker | Promise<Worker>; + getWorkerUrl?(moduleId: string, label: string): string; + } + const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; + if (monacoEnvironment) { + if (typeof monacoEnvironment.getWorker === 'function') { + return monacoEnvironment.getWorker('workerMain.js', label); } - if (typeof globals.MonacoEnvironment.getWorkerUrl === 'function') { - const workerUrl = <string>globals.MonacoEnvironment.getWorkerUrl('workerMain.js', label); + if (typeof monacoEnvironment.getWorkerUrl === 'function') { + const workerUrl = monacoEnvironment.getWorkerUrl('workerMain.js', label); return new Worker(ttPolicy ? ttPolicy.createScriptURL(workerUrl) as unknown as string : workerUrl, { name: label }); } } @@ -32,16 +45,30 @@ function getWorker(label: string): Worker | Promise<Worker> { // ESM-comment-begin export function getWorkerBootstrapUrl(scriptPath: string, label: string): string { - if (/^((http:)|(https:)|(file:))/.test(scriptPath) && scriptPath.substring(0, self.origin.length) !== self.origin) { + if (/^((http:)|(https:)|(file:))/.test(scriptPath) && scriptPath.substring(0, globalThis.origin.length) !== globalThis.origin) { // this is the cross-origin case // i.e. the webpage is running at a different origin than where the scripts are loaded from const myPath = 'vs/base/worker/defaultWorkerFactory.js'; const workerBaseUrl = require.toUrl(myPath).slice(0, -myPath.length); // explicitly using require.toUrl(), see https://github.com/microsoft/vscode/issues/107440#issuecomment-698982321 - const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};const ttPolicy = self.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });importScripts(ttPolicy?.createScriptURL('${scriptPath}') ?? '${scriptPath}');/*${label}*/`; + const js = `/*${label}*/globalThis.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};const ttPolicy = globalThis.trustedTypes?.createPolicy('defaultWorkerFactory', { createScriptURL: value => value });importScripts(ttPolicy?.createScriptURL('${scriptPath}') ?? '${scriptPath}');/*${label}*/`; const blob = new Blob([js], { type: 'application/javascript' }); return URL.createObjectURL(blob); } - return scriptPath + '#' + label; + + const start = scriptPath.lastIndexOf('?'); + const end = scriptPath.lastIndexOf('#', start); + const params = start > 0 + ? new URLSearchParams(scriptPath.substring(start + 1, ~end ? end : undefined)) + : new URLSearchParams(); + + COI.addSearchParam(params, true, true); + const search = params.toString(); + + if (!search) { + return `${scriptPath}#${label}`; + } else { + return `${scriptPath}?${params.toString()}#${label}`; + } } // ESM-comment-end diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index cb20e4cad7..a7cbb24fda 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -71,14 +71,29 @@ export const DataTransfers = { /** * Typically transfer type for copy/paste transfers. */ - TEXT: Mimes.text + TEXT: Mimes.text, + + /** + * Internal type used to pass around text/uri-list data. + * + * This is needed to work around https://bugs.chromium.org/p/chromium/issues/detail?id=239745. + */ + INTERNAL_URI_LIST: 'application/vnd.code.uri-list', }; -export function applyDragImage(event: DragEvent, label: string | null, clazz: string): void { +export function applyDragImage(event: DragEvent, label: string | null, clazz: string, backgroundColor?: string | null, foregroundColor?: string | null): void { const dragImage = document.createElement('div'); dragImage.className = clazz; dragImage.textContent = label; + if (foregroundColor) { + dragImage.style.color = foregroundColor; + } + + if (backgroundColor) { + dragImage.style.background = backgroundColor; + } + if (event.dataTransfer) { document.body.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, -10, -10); @@ -92,24 +107,3 @@ export interface IDragAndDropData { update(dataTransfer: DataTransfer): void; getData(): unknown; } - -export class DragAndDropData<T> implements IDragAndDropData { - - constructor(private data: T) { } - - update(): void { - // noop - } - - getData(): T { - return this.data; - } -} - -export interface IStaticDND { - CurrentDragAndDropData: IDragAndDropData | undefined; -} - -export const StaticDND: IStaticDND = { - CurrentDragAndDropData: undefined -}; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index c17d37d6f7..e3029a7ef7 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -123,45 +123,6 @@ export function addDisposableGenericMouseUpListener(node: EventTarget, handler: return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture); } -export function createEventEmitter<K extends keyof HTMLElementEventMap>(target: HTMLElement, type: K, options?: boolean | AddEventListenerOptions): event.Emitter<HTMLElementEventMap[K]> { - let domListener: DomListener | null = null; - const handler = (e: HTMLElementEventMap[K]) => result.fire(e); - const onFirstListenerAdd = () => { - if (!domListener) { - domListener = new DomListener(target, type, handler, options); - } - }; - const onLastListenerRemove = () => { - if (domListener) { - domListener.dispose(); - domListener = null; - } - }; - const result = new event.Emitter<HTMLElementEventMap[K]>({ onFirstListenerAdd, onLastListenerRemove }); - return result; -} - -interface IRequestAnimationFrame { - (callback: (time: number) => void): number; -} -let _animationFrame: IRequestAnimationFrame | null = null; -function doRequestAnimationFrame(callback: (time: number) => void): number { - if (!_animationFrame) { - const emulatedRequestAnimationFrame = (callback: (time: number) => void): any => { - return setTimeout(() => callback(new Date().getTime()), 0); - }; - _animationFrame = ( - self.requestAnimationFrame - || (<any>self).msRequestAnimationFrame - || (<any>self).webkitRequestAnimationFrame - || (<any>self).mozRequestAnimationFrame - || (<any>self).oRequestAnimationFrame - || emulatedRequestAnimationFrame - ); - } - return _animationFrame.call(self, callback); -} - /** * Schedule a callback to be run at the next animation frame. * This allows multiple parties to register callbacks that should run at the next animation frame. @@ -250,7 +211,7 @@ class AnimationFrameQueueItem implements IDisposable { if (!animFrameRequested) { animFrameRequested = true; - doRequestAnimationFrame(animationFrameRunner); + requestAnimationFrame(animationFrameRunner); } return item; @@ -363,16 +324,8 @@ class SizeUtils { } private static getDimension(element: HTMLElement, cssPropertyName: string, jsPropertyName: string): number { - const computedStyle: CSSStyleDeclaration = getComputedStyle(element); - let value = '0'; - if (computedStyle) { - if (computedStyle.getPropertyValue) { - value = computedStyle.getPropertyValue(cssPropertyName); - } else { - // IE8 - value = (<any>computedStyle).getAttribute(jsPropertyName); - } - } + const computedStyle = getComputedStyle(element); + const value = computedStyle ? computedStyle.getPropertyValue(cssPropertyName) : '0'; return SizeUtils.convertToPixels(element, value); } @@ -464,7 +417,12 @@ export class Dimension implements IDimension { } } -export function getTopLeftOffset(element: HTMLElement): { left: number; top: number } { +export interface IDomPosition { + readonly left: number; + readonly top: number; +} + +export function getTopLeftOffset(element: HTMLElement): IDomPosition { // Adapted from WinJS.Utilities.getPosition // and added borders to the mix @@ -541,8 +499,8 @@ export function position(element: HTMLElement, top: number, right?: number, bott export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePosition { const bb = domNode.getBoundingClientRect(); return { - left: bb.left + StandardWindow.scrollX, - top: bb.top + StandardWindow.scrollY, + left: bb.left + window.scrollX, + top: bb.top + window.scrollY, width: bb.width, height: bb.height }; @@ -566,30 +524,6 @@ export function getDomNodeZoomLevel(domNode: HTMLElement): number { return zoom; } -export interface IStandardWindow { - readonly scrollX: number; - readonly scrollY: number; -} - -export const StandardWindow: IStandardWindow = new class implements IStandardWindow { - get scrollX(): number { - if (typeof window.scrollX === 'number') { - // modern browsers - return window.scrollX; - } else { - return document.body.scrollLeft + document.documentElement!.scrollLeft; - } - } - - get scrollY(): number { - if (typeof window.scrollY === 'number') { - // modern browsers - return window.scrollY; - } else { - return document.body.scrollTop + document.documentElement!.scrollTop; - } - } -}; // Adapted from WinJS // Gets the width of the element, including margins. @@ -757,10 +691,11 @@ export function getActiveElement(): Element | null { return result; } -export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement { +export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0], beforeAppend?: (style: HTMLStyleElement) => void): HTMLStyleElement { const style = document.createElement('style'); style.type = 'text/css'; style.media = 'screen'; + beforeAppend?.(style); container.appendChild(style); return style; } @@ -891,23 +826,19 @@ export interface EventLike { stopPropagation(): void; } -export const EventHelper = { - stop: function (e: EventLike, cancelBubble?: boolean) { - if (e.preventDefault) { - e.preventDefault(); - } else { - // IE8 - (<any>e).returnValue = false; - } +export function isEventLike(obj: unknown): obj is EventLike { + const candidate = obj as EventLike | undefined; + return !!(candidate && typeof candidate.preventDefault === 'function' && typeof candidate.stopPropagation === 'function'); +} + +export const EventHelper = { + stop: <T extends EventLike>(e: T, cancelBubble?: boolean): T => { + e.preventDefault(); if (cancelBubble) { - if (e.stopPropagation) { - e.stopPropagation(); - } else { - // IE8 - (<any>e).cancelBubble = true; - } + e.stopPropagation(); } + return e; } }; @@ -999,6 +930,12 @@ class FocusTracker extends Disposable implements IFocusTracker { } } +/** + * Creates a new `IFocusTracker` instance that tracks focus changes on the given `element` and its descendants. + * + * @param element The `HTMLElement` or `Window` to track focus changes on. + * @returns An `IFocusTracker` instance. + */ export function trackFocus(element: HTMLElement | Window): IFocusTracker { return new FocusTracker(element); } @@ -1044,8 +981,6 @@ function _$<T extends Element>(namespace: Namespace, description: string, attrs? throw new Error('Bad use of emmet'); } - attrs = { ...(attrs || {}) }; - const tagName = match[1] || 'div'; let result: T; @@ -1062,24 +997,24 @@ function _$<T extends Element>(namespace: Namespace, description: string, attrs? result.className = match[4].replace(/\./g, ' ').trim(); } - Object.keys(attrs).forEach(name => { - const value = attrs![name]; - - if (typeof value === 'undefined') { - return; - } - - if (/^on\w+$/.test(name)) { - (<any>result)[name] = value; - } else if (name === 'selected') { - if (value) { - result.setAttribute(name, 'true'); + if (attrs) { + Object.entries(attrs).forEach(([name, value]) => { + if (typeof value === 'undefined') { + return; } - } else { - result.setAttribute(name, value); - } - }); + if (/^on\w+$/.test(name)) { + (<any>result)[name] = value; + } else if (name === 'selected') { + if (value) { + result.setAttribute(name, 'true'); + } + + } else { + result.setAttribute(name, value); + } + }); + } result.append(...children); @@ -1112,6 +1047,14 @@ export function join(nodes: Node[], separator: Node | string): Node[] { return result; } +export function setVisibility(visible: boolean, ...elements: HTMLElement[]): void { + if (visible) { + show(...elements); + } else { + hide(...elements); + } +} + export function show(...elements: HTMLElement[]): void { for (const element of elements) { element.style.display = ''; @@ -1149,18 +1092,12 @@ export function removeTabIndexAndUpdateFocus(node: HTMLElement): void { // in the hierarchy of the parent DOM nodes. if (document.activeElement === node) { const parentFocusable = findParentWithAttribute(node.parentElement, 'tabIndex'); - if (parentFocusable) { - parentFocusable.focus(); - } + parentFocusable?.focus(); } node.removeAttribute('tabindex'); } -export function getElementsByTagName(tag: string): HTMLElement[] { - return Array.prototype.slice.call(document.getElementsByTagName(tag), 0); -} - export function finalHandler<T extends Event>(fn: (event: T) => any): (event: T) => any { return e => { e.preventDefault(); @@ -1283,13 +1220,28 @@ export function asCSSUrl(uri: URI | null | undefined): string { if (!uri) { return `url('')`; } - return `url('${FileAccess.asBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`; + return `url('${FileAccess.uriToBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`; } export function asCSSPropertyValue(value: string) { return `'${value.replace(/'/g, '%27')}'`; } +export function asCssValueWithDefault(cssPropertyValue: string | undefined, dflt: string): string { + if (cssPropertyValue !== undefined) { + const variableMatch = cssPropertyValue.match(/^\s*var\((.+)\)$/); + if (variableMatch) { + const varArguments = variableMatch[1].split(',', 2); + if (varArguments.length === 2) { + dflt = asCssValueWithDefault(varArguments[1].trim(), dflt); + } + return `var(${varArguments[0]}, ${dflt})`; + } + return cssPropertyValue; + } + return dflt; +} + export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void { // If the data is provided as Buffer, we create a @@ -1451,21 +1403,104 @@ const defaultSafeProtocols = [ Schemas.vscodeFileResource // {{SQL CARBON EDIT}} Add allowed schema for welcome page support ]; +/** + * List of safe, non-input html tags. + */ +export const basicMarkupHtmlTags = Object.freeze([ + 'a', + 'abbr', + 'b', + 'bdo', + 'blockquote', + 'br', + 'caption', + 'cite', + 'code', + 'col', + 'colgroup', + 'dd', + 'del', + 'details', + 'dfn', + 'div', + 'dl', + 'dt', + 'em', + 'figcaption', + 'figure', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'hr', + 'i', + 'img', + 'ins', + 'kbd', + 'label', + 'li', + 'mark', + 'ol', + 'p', + 'pre', + 'q', + 'rp', + 'rt', + 'ruby', + 'samp', + 'small', + 'small', + 'source', + 'span', + 'strike', + 'strong', + 'sub', + 'summary', + 'sup', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'time', + 'tr', + 'tt', + 'u', + 'ul', + 'var', + 'video', + 'wbr', +]); + +// {{SQL CARBON EDIT}} - not needed since local copy made in safeInnerHTML function +// const defaultDomPurifyConfig = Object.freeze<dompurify.Config & { RETURN_TRUSTED_TYPE: true }>({ +// ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'], // {{SQL CARBON EDIT}} - add i and img tags for dashboard +// ALLOWED_ATTR: ['href', 'data-href', 'data-command', 'target', 'title', 'name', 'src', 'alt', 'class', 'id', 'role', 'tabindex', 'style', 'data-code', 'width', 'height', 'align', 'x-dispatch', 'required', 'checked', 'placeholder', 'type', 'start'], +// RETURN_DOM: false, +// RETURN_DOM_FRAGMENT: false, +// RETURN_TRUSTED_TYPE: true +// }); + /** * Sanitizes the given `value` and reset the given `node` with it. */ export function safeInnerHtml(node: HTMLElement, value: string, allowUnknownProtocols: boolean = false): void { // {{SQL CARBON EDIT}} - add allow unknown schemas parameter - const options: dompurify.Config = { - ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'], // {{SQL CARBON EDIT}} Add i & img tags for welcome page support - ALLOWED_ATTR: ['href', 'data-href', 'data-command', 'target', 'title', 'name', 'src', 'alt', 'class', 'id', 'role', 'tabindex', 'style', 'data-code', 'width', 'height', 'align', 'x-dispatch', 'required', 'checked', 'placeholder', 'type', 'aria-label'], // {{SQL CARBON EDIT}} Add aria-label - RETURN_DOM: false, - RETURN_DOM_FRAGMENT: false, - ALLOW_UNKNOWN_PROTOCOLS: allowUnknownProtocols - }; - const hook = hookDomPurifyHrefAndSrcSanitizer(defaultSafeProtocols); try { - const html = dompurify.sanitize(value, { ...options, RETURN_TRUSTED_TYPE: true }); + // {{SQL CARBON EDIT}} - set ALLOW_UNKNOWN_PROTOCOLS + const options = Object.freeze<dompurify.Config & { RETURN_TRUSTED_TYPE: true }>({ + ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'], // {{SQL CARBON EDIT}} - add i and img tags for dashboard + ALLOWED_ATTR: ['href', 'data-href', 'data-command', 'target', 'title', 'name', 'src', 'alt', 'class', 'id', 'role', 'tabindex', 'style', 'data-code', 'width', 'height', 'align', 'x-dispatch', 'required', 'checked', 'placeholder', 'type', 'start'], + RETURN_DOM: false, + RETURN_DOM_FRAGMENT: false, + RETURN_TRUSTED_TYPE: true, + ALLOW_UNKNOWN_PROTOCOLS: allowUnknownProtocols + }); + + const html = dompurify.sanitize(value, options); node.innerHTML = html as unknown as string; } finally { hook.dispose(); @@ -1724,18 +1759,6 @@ export class DragAndDropObserver extends Disposable { } } -export function computeClippingRect(elementOrRect: HTMLElement | DOMRectReadOnly, clipper: HTMLElement) { - const frameRect = (elementOrRect instanceof HTMLElement ? elementOrRect.getBoundingClientRect() : elementOrRect); - const rootRect = clipper.getBoundingClientRect(); - - const top = Math.max(rootRect.top - frameRect.top, 0); - const right = Math.max(frameRect.width - (frameRect.right - rootRect.right), 0); - const bottom = Math.max(frameRect.height - (frameRect.bottom - rootRect.bottom), 0); - const left = Math.max(rootRect.left - frameRect.left, 0); - - return { top, right, bottom, left }; -} - type HTMLElementAttributeKeys<T> = Partial<{ [K in keyof T]: T[K] extends Function ? never : T[K] extends object ? HTMLElementAttributeKeys<T[K]> : T[K] }>; type ElementAttributes<T> = HTMLElementAttributeKeys<T> & Record<string, any>; type RemoveHTMLElement<T> = T extends HTMLElement ? never : T; @@ -1764,23 +1787,6 @@ type TagToRecord<TTag> = TagToElementAndId<TTag> extends { element: infer TEleme : never; type Child = HTMLElement | string | Record<string, HTMLElement>; -type Children = [] - | [Child] - | [Child, Child] - | [Child, Child, Child] - | [Child, Child, Child, Child] - | [Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child] - | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]; const H_REGEX = /(?<tag>[\w\-]+)?(?:#(?<id>[\w\-]+))?(?<class>(?:\.(?:[\w\-]+))*)(?:@(?<name>(?:[\w\_])+))?/; @@ -1803,16 +1809,16 @@ export function h<TTag extends string> (tag: TTag): TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; -export function h<TTag extends string, T extends Children> - (tag: TTag, children: T): +export function h<TTag extends string, T extends Child[]> + (tag: TTag, children: [...T]): (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; export function h<TTag extends string> (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>): TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; -export function h<TTag extends string, T extends Children> - (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>, children: T): +export function h<TTag extends string, T extends Child[]> + (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>, children: [...T]): (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never; export function h(tag: string, ...args: [] | [attributes: { $: string } & Partial<ElementAttributes<HTMLElement>> | Record<string, any>, children?: any[]] | [children: any[]]): Record<string, HTMLElement> { @@ -1840,8 +1846,23 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia el.id = match.groups['id']; } + const classNames = []; if (match.groups['class']) { - el.className = match.groups['class'].replace(/\./g, ' ').trim(); + for (const className of match.groups['class'].split('.')) { + if (className !== '') { + classNames.push(className); + } + } + } + if (attributes.className !== undefined) { + for (const className of attributes.className.split('.')) { + if (className !== '') { + classNames.push(className); + } + } + } + if (classNames.length > 0) { + el.className = classNames.join(' '); } const result: Record<string, HTMLElement> = {}; @@ -1856,7 +1877,7 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia el.appendChild(c); } else if (typeof c === 'string') { el.append(c); - } else { + } else if ('root' in c) { Object.assign(result, c); el.appendChild(c.root); } @@ -1864,7 +1885,9 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia } for (const [key, value] of Object.entries(attributes)) { - if (key === 'style') { + if (key === 'className') { + continue; + } else if (key === 'style') { for (const [cssKey, cssValue] of Object.entries(value)) { el.style.setProperty( camelCaseToHyphenCase(cssKey), diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts index 97cbbbc8a5..2ac0cd4fd2 100644 --- a/src/vs/base/browser/event.ts +++ b/src/vs/base/browser/event.ts @@ -20,6 +20,9 @@ export interface DOMEventMap extends HTMLElementEventMap, DocumentEventMap, Wind '-monaco-gesturestart': GestureEvent; '-monaco-gesturesend': GestureEvent; '-monaco-gesturecontextmenu': GestureEvent; + 'compositionstart': CompositionEvent; + 'compositionupdate': CompositionEvent; + 'compositionend': CompositionEvent; } export class DomEmitter<K extends keyof DOMEventMap> implements IDisposable { @@ -36,8 +39,8 @@ export class DomEmitter<K extends keyof DOMEventMap> implements IDisposable { constructor(element: EventHandler, type: K, useCapture?: boolean) { const fn = (e: Event) => this.emitter.fire(e as DOMEventMap[K]); this.emitter = new Emitter({ - onFirstListenerAdd: () => element.addEventListener(type, fn, useCapture), - onLastListenerRemove: () => element.removeEventListener(type, fn, useCapture) + onWillAddFirstListener: () => element.addEventListener(type, fn, useCapture), + onDidRemoveLastListener: () => element.removeEventListener(type, fn, useCapture) }); } @@ -45,18 +48,3 @@ export class DomEmitter<K extends keyof DOMEventMap> implements IDisposable { this.emitter.dispose(); } } - -export interface CancellableEvent { - preventDefault(): void; - stopPropagation(): void; -} - -export function stopEvent<T extends CancellableEvent>(event: T): T { - event.preventDefault(); - event.stopPropagation(); - return event; -} - -export function stop<T extends CancellableEvent>(event: BaseEvent<T>): BaseEvent<T> { - return BaseEvent.map(event, stopEvent); -} diff --git a/src/vs/base/browser/fastDomNode.ts b/src/vs/base/browser/fastDomNode.ts index 70e1afa573..7cef02d553 100644 --- a/src/vs/base/browser/fastDomNode.ts +++ b/src/vs/base/browser/fastDomNode.ts @@ -12,11 +12,16 @@ export class FastDomNode<T extends HTMLElement> { private _left: string = ''; private _bottom: string = ''; private _right: string = ''; + private _paddingTop: string = ''; + private _paddingLeft: string = ''; + private _paddingBottom: string = ''; + private _paddingRight: string = ''; private _fontFamily: string = ''; private _fontWeight: string = ''; private _fontSize: string = ''; private _fontStyle: string = ''; private _fontFeatureSettings: string = ''; + private _fontVariationSettings: string = ''; private _textDecoration: string = ''; private _lineHeight: string = ''; private _letterSpacing: string = ''; @@ -97,6 +102,42 @@ export class FastDomNode<T extends HTMLElement> { this.domNode.style.right = this._right; } + public setPaddingTop(_paddingTop: number | string): void { + const paddingTop = numberAsPixels(_paddingTop); + if (this._paddingTop === paddingTop) { + return; + } + this._paddingTop = paddingTop; + this.domNode.style.paddingTop = this._paddingTop; + } + + public setPaddingLeft(_paddingLeft: number | string): void { + const paddingLeft = numberAsPixels(_paddingLeft); + if (this._paddingLeft === paddingLeft) { + return; + } + this._paddingLeft = paddingLeft; + this.domNode.style.paddingLeft = this._paddingLeft; + } + + public setPaddingBottom(_paddingBottom: number | string): void { + const paddingBottom = numberAsPixels(_paddingBottom); + if (this._paddingBottom === paddingBottom) { + return; + } + this._paddingBottom = paddingBottom; + this.domNode.style.paddingBottom = this._paddingBottom; + } + + public setPaddingRight(_paddingRight: number | string): void { + const paddingRight = numberAsPixels(_paddingRight); + if (this._paddingRight === paddingRight) { + return; + } + this._paddingRight = paddingRight; + this.domNode.style.paddingRight = this._paddingRight; + } + public setFontFamily(fontFamily: string): void { if (this._fontFamily === fontFamily) { return; @@ -138,6 +179,14 @@ export class FastDomNode<T extends HTMLElement> { this.domNode.style.fontFeatureSettings = this._fontFeatureSettings; } + public setFontVariationSettings(fontVariationSettings: string): void { + if (this._fontVariationSettings === fontVariationSettings) { + return; + } + this._fontVariationSettings = fontVariationSettings; + this.domNode.style.fontVariationSettings = this._fontVariationSettings; + } + public setTextDecoration(textDecoration: string): void { if (this._textDecoration === textDecoration) { return; diff --git a/src/vs/base/browser/formattedTextRenderer.ts b/src/vs/base/browser/formattedTextRenderer.ts index 7a37d820c4..efa941333a 100644 --- a/src/vs/base/browser/formattedTextRenderer.ts +++ b/src/vs/base/browser/formattedTextRenderer.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { DisposableStore } from 'vs/base/common/lifecycle'; export interface IContentActionHandler { - callback: (content: string, event: IMouseEvent) => void; + callback: (content: string, event: IMouseEvent | IKeyboardEvent) => void; readonly disposables: DisposableStore; } diff --git a/src/vs/base/browser/globalPointerMoveMonitor.ts b/src/vs/base/browser/globalPointerMoveMonitor.ts index 9acd7ece52..422cc07d5d 100644 --- a/src/vs/base/browser/globalPointerMoveMonitor.ts +++ b/src/vs/base/browser/globalPointerMoveMonitor.ts @@ -64,7 +64,17 @@ export class GlobalPointerMoveMonitor implements IDisposable { try { initialElement.setPointerCapture(pointerId); this._hooks.add(toDisposable(() => { - initialElement.releasePointerCapture(pointerId); + try { + initialElement.releasePointerCapture(pointerId); + } catch (err) { + // See https://github.com/microsoft/vscode/issues/161731 + // + // `releasePointerCapture` sometimes fails when being invoked with the exception: + // DOMException: Failed to execute 'releasePointerCapture' on 'Element': + // No active pointer with the given id is found. + // + // There's no need to do anything in case of failure + } })); } catch (err) { // See https://github.com/microsoft/vscode/issues/144584 diff --git a/src/vs/base/browser/indexedDB.ts b/src/vs/base/browser/indexedDB.ts index 7081ff88af..5a8ba749bf 100644 --- a/src/vs/base/browser/indexedDB.ts +++ b/src/vs/base/browser/indexedDB.ts @@ -6,7 +6,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { getErrorMessage } from 'vs/base/common/errors'; import { mark } from 'vs/base/common/performance'; -import { isArray } from 'vs/base/common/types'; class MissingStoresError extends Error { constructor(readonly db: IDBDatabase) { @@ -106,9 +105,7 @@ export class IndexedDB { if (this.pendingTransactions.length) { this.pendingTransactions.splice(0, this.pendingTransactions.length).forEach(transaction => transaction.abort()); } - if (this.database) { - this.database.close(); - } + this.database?.close(); this.database = null; } @@ -122,13 +119,14 @@ export class IndexedDB { this.pendingTransactions.push(transaction); return new Promise<T | T[]>((c, e) => { transaction.oncomplete = () => { - if (isArray(request)) { + if (Array.isArray(request)) { c(request.map(r => r.result)); } else { c(request.result); } }; transaction.onerror = () => e(transaction.error); + transaction.onabort = () => e(transaction.error); const request = dbRequestFn(transaction.objectStore(store)); }).finally(() => this.pendingTransactions.splice(this.pendingTransactions.indexOf(transaction), 1)); } diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index dd6441db7e..1eaf2e6ecd 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -5,7 +5,7 @@ import * as browser from 'vs/base/browser/browser'; import { EVENT_KEY_CODE_MAP, KeyCode, KeyCodeUtils, KeyMod } from 'vs/base/common/keyCodes'; -import { SimpleKeybinding } from 'vs/base/common/keybindings'; +import { KeyCodeChord } from 'vs/base/common/keybindings'; import * as platform from 'vs/base/common/platform'; @@ -23,19 +23,22 @@ function extractKeyCode(e: KeyboardEvent): KeyCode { if (keyCode === 3) { return KeyCode.PauseBreak; } else if (browser.isFirefox) { - if (keyCode === 59) { - return KeyCode.Semicolon; - } else if (keyCode === 107) { - return KeyCode.Equal; - } else if (keyCode === 109) { - return KeyCode.Minus; - } else if (platform.isMacintosh && keyCode === 224) { - return KeyCode.Meta; + switch (keyCode) { + case 59: return KeyCode.Semicolon; + case 60: + if (platform.isLinux) { return KeyCode.IntlBackslash; } + break; + case 61: return KeyCode.Equal; + // based on: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#numpad_keys + case 107: return KeyCode.NumpadAdd; + case 109: return KeyCode.NumpadSubtract; + case 173: return KeyCode.Minus; + case 224: + if (platform.isMacintosh) { return KeyCode.Meta; } + break; } } else if (browser.isWebKit) { - if (keyCode === 91) { - return KeyCode.Meta; - } else if (platform.isMacintosh && keyCode === 93) { + if (platform.isMacintosh && keyCode === 93) { // the two meta keys in the Mac have different key codes (91 and 93) return KeyCode.Meta; } else if (!platform.isMacintosh && keyCode === 92) { @@ -58,13 +61,14 @@ export interface IKeyboardEvent { readonly shiftKey: boolean; readonly altKey: boolean; readonly metaKey: boolean; + readonly altGraphKey: boolean; readonly keyCode: KeyCode; readonly code: string; /** * @internal */ - toKeybinding(): SimpleKeybinding; + toKeyCodeChord(): KeyCodeChord; equals(keybinding: number): boolean; preventDefault(): void; @@ -121,11 +125,12 @@ export class StandardKeyboardEvent implements IKeyboardEvent { public readonly shiftKey: boolean; public readonly altKey: boolean; public readonly metaKey: boolean; + public readonly altGraphKey: boolean; public readonly keyCode: KeyCode; public readonly code: string; private _asKeybinding: number; - private _asRuntimeKeybinding: SimpleKeybinding; + private _asKeyCodeChord: KeyCodeChord; constructor(source: KeyboardEvent) { const e = source; @@ -137,6 +142,7 @@ export class StandardKeyboardEvent implements IKeyboardEvent { this.shiftKey = e.shiftKey; this.altKey = e.altKey; this.metaKey = e.metaKey; + this.altGraphKey = e.getModifierState('AltGraph'); this.keyCode = extractKeyCode(e); this.code = e.code; @@ -148,7 +154,7 @@ export class StandardKeyboardEvent implements IKeyboardEvent { this.metaKey = this.metaKey || this.keyCode === KeyCode.Meta; this._asKeybinding = this._computeKeybinding(); - this._asRuntimeKeybinding = this._computeRuntimeKeybinding(); + this._asKeyCodeChord = this._computeKeyCodeChord(); // console.log(`code: ${e.code}, keyCode: ${e.keyCode}, key: ${e.key}`); } @@ -165,8 +171,8 @@ export class StandardKeyboardEvent implements IKeyboardEvent { } } - public toKeybinding(): SimpleKeybinding { - return this._asRuntimeKeybinding; + public toKeyCodeChord(): KeyCodeChord { + return this._asKeyCodeChord; } public equals(other: number): boolean { @@ -197,11 +203,11 @@ export class StandardKeyboardEvent implements IKeyboardEvent { return result; } - private _computeRuntimeKeybinding(): SimpleKeybinding { + private _computeKeyCodeChord(): KeyCodeChord { let key = KeyCode.Unknown; if (this.keyCode !== KeyCode.Ctrl && this.keyCode !== KeyCode.Shift && this.keyCode !== KeyCode.Alt && this.keyCode !== KeyCode.Meta) { key = this.keyCode; } - return new SimpleKeybinding(this.ctrlKey, this.shiftKey, this.altKey, this.metaKey, key); + return new KeyCodeChord(this.ctrlKey, this.shiftKey, this.altKey, this.metaKey, key); } } diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index 86cac17c99..78c24a8cd6 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -7,13 +7,16 @@ import * as DOM from 'vs/base/browser/dom'; import * as dompurify from 'vs/base/browser/dompurify/dompurify'; import { DomEmitter } from 'vs/base/browser/event'; import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; -import { IMarkdownString, escapeDoubleQuotes, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent'; +import { IMarkdownString, escapeDoubleQuotes, parseHrefAndDimensions, removeMarkdownEscapes, MarkdownStringTrustedOptions } from 'vs/base/common/htmlContent'; import { markdownEscapeEscapedIcons } from 'vs/base/common/iconLabels'; import { defaultGenerator } from 'vs/base/common/idGenerator'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { Lazy } from 'vs/base/common/lazy'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { marked } from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; @@ -29,9 +32,58 @@ export interface MarkedOptions extends marked.MarkedOptions { export interface MarkdownRenderOptions extends FormattedTextRenderOptions { readonly codeBlockRenderer?: (languageId: string, value: string) => Promise<HTMLElement>; + readonly codeBlockRendererSync?: (languageId: string, value: string) => HTMLElement; readonly asyncRenderCallback?: () => void; + readonly fillInIncompleteTokens?: boolean; } +const defaultMarkedRenderers = Object.freeze({ + image: (href: string | null, title: string | null, text: string): string => { + let dimensions: string[] = []; + let attributes: string[] = []; + if (href) { + ({ href, dimensions } = parseHrefAndDimensions(href)); + attributes.push(`src="${escapeDoubleQuotes(href)}"`); + } + if (text) { + attributes.push(`alt="${escapeDoubleQuotes(text)}"`); + } + if (title) { + attributes.push(`title="${escapeDoubleQuotes(title)}"`); + } + if (dimensions.length) { + attributes = attributes.concat(dimensions); + } + return '<img ' + attributes.join(' ') + '>'; + }, + + paragraph: (text: string): string => { + return `<p>${text}</p>`; + }, + + link: (href: string | null, title: string | null, text: string): string => { + if (typeof href !== 'string') { + return ''; + } + + // Remove markdown escapes. Workaround for https://github.com/chjj/marked/issues/829 + if (href === text) { // raw link case + text = removeMarkdownEscapes(text); + } + + title = typeof title === 'string' ? escapeDoubleQuotes(removeMarkdownEscapes(title)) : ''; + href = removeMarkdownEscapes(href); + + // HTML Encode href + href = href.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + return `<a href="${href}" title="${title || href}">${text}</a>`; + }, +}); + /** * Low-level way create a html element from a markdown string. * @@ -78,7 +130,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende // and because of that special rewriting needs to be done // so that the URI uses a protocol that's understood by // browsers (like http or https) - return FileAccess.asBrowserUri(uri).toString(true); + return FileAccess.uriToBrowserUri(uri).toString(true); } if (!uri) { return href; @@ -93,57 +145,25 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende }; const renderer = new marked.Renderer(); - - renderer.image = (href: string, title: string, text: string) => { - let dimensions: string[] = []; - let attributes: string[] = []; - if (href) { - ({ href, dimensions } = parseHrefAndDimensions(href)); - attributes.push(`src="${escapeDoubleQuotes(href)}"`); - } - if (text) { - attributes.push(`alt="${escapeDoubleQuotes(text)}"`); - } - if (title) { - attributes.push(`title="${escapeDoubleQuotes(title)}"`); - } - if (dimensions.length) { - attributes = attributes.concat(dimensions); - } - return '<img ' + attributes.join(' ') + '>'; - }; - renderer.link = (href, title, text): string => { - if (typeof href !== 'string') { - return ''; - } - - // Remove markdown escapes. Workaround for https://github.com/chjj/marked/issues/829 - if (href === text) { // raw link case - text = removeMarkdownEscapes(text); - } - - title = typeof title === 'string' ? escapeDoubleQuotes(removeMarkdownEscapes(title)) : ''; - href = removeMarkdownEscapes(href); - - // HTML Encode href - href = href.replace(/&/g, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); - return `<a href="${href}" title="${title || href}">${text}</a>`; - }; - renderer.paragraph = (text): string => { - return `<p>${text}</p>`; - }; + renderer.image = defaultMarkedRenderers.image; + renderer.link = defaultMarkedRenderers.link; + renderer.paragraph = defaultMarkedRenderers.paragraph; // Will collect [id, renderedElement] tuples const codeBlocks: Promise<[string, HTMLElement]>[] = []; + const syncCodeBlocks: [string, HTMLElement][] = []; - if (options.codeBlockRenderer) { + if (options.codeBlockRendererSync) { renderer.code = (code, lang) => { const id = defaultGenerator.nextId(); - const value = options.codeBlockRenderer!(lang ?? '', code); + const value = options.codeBlockRendererSync!(postProcessCodeBlockLanguageId(lang), code); + syncCodeBlocks.push([id, value]); + return `<div class="code" data-code="${id}">${escape(code)}</div>`; + }; + } else if (options.codeBlockRenderer) { + renderer.code = (code, lang) => { + const id = defaultGenerator.nextId(); + const value = options.codeBlockRenderer!(postProcessCodeBlockLanguageId(lang), code); codeBlocks.push(value.then(element => [id, element])); return `<div class="code" data-code="${id}">${escape(code)}</div>`; }; @@ -151,15 +171,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende if (options.actionHandler) { - const onClick = options.actionHandler.disposables.add(new DomEmitter(element, 'click')); - const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(element, 'auxclick')); - options.actionHandler.disposables.add(Event.any(onClick.event, onAuxClick.event)(e => { - const mouseEvent = new StandardMouseEvent(e); - if (!mouseEvent.leftButton && !mouseEvent.middleButton) { - return; - } - - let target: HTMLElement | null = mouseEvent.target; + const _activateLink = function (event: StandardMouseEvent | StandardKeyboardEvent): void { + let target: HTMLElement | null = event.target; if (target.tagName !== 'A') { target = target.parentElement; if (!target || target.tagName !== 'A') { @@ -172,13 +185,29 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende if (markdown.baseUri) { href = resolveWithBaseUri(URI.from(markdown.baseUri), href); } - options.actionHandler!.callback(href, mouseEvent); + options.actionHandler!.callback(href, event); } } catch (err) { onUnexpectedError(err); } finally { - mouseEvent.preventDefault(); + event.preventDefault(); } + }; + const onClick = options.actionHandler.disposables.add(new DomEmitter(element, 'click')); + const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(element, 'auxclick')); + options.actionHandler.disposables.add(Event.any(onClick.event, onAuxClick.event)(e => { + const mouseEvent = new StandardMouseEvent(e); + if (!mouseEvent.leftButton && !mouseEvent.middleButton) { + return; + } + _activateLink(mouseEvent); + })); + options.actionHandler.disposables.add(DOM.addDisposableListener(element, 'keydown', (e) => { + const keyboardEvent = new StandardKeyboardEvent(e); + if (!keyboardEvent.equals(KeyCode.Space) && !keyboardEvent.equals(KeyCode.Enter)) { + return; + } + _activateLink(keyboardEvent); })); } @@ -210,7 +239,19 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende value = markdownEscapeEscapedIcons(value); } - let renderedMarkdown = marked.parse(value, markedOptions); + let renderedMarkdown: string; + if (options.fillInIncompleteTokens) { + // The defaults are applied by parse but not lexer()/parser(), and they need to be present + const opts = { + ...marked.defaults, + ...markedOptions + }; + const tokens = marked.lexer(value, opts); + const newTokens = fillInIncompleteTokens(tokens); + renderedMarkdown = marked.parser(newTokens, opts); + } else { + renderedMarkdown = marked.parse(value, markedOptions); + } // Rewrite theme icons if (markdown.supportThemeIcons) { @@ -274,6 +315,15 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende } options.asyncRenderCallback?.(); }); + } else if (syncCodeBlocks.length > 0) { + const renderedElements = new Map(syncCodeBlocks); + const placeholderElements = element.querySelectorAll<HTMLDivElement>(`div[data-code]`); + for (const placeholderElement of placeholderElements) { + const renderedElement = renderedElements.get(placeholderElement.dataset['code'] ?? ''); + if (renderedElement) { + DOM.reset(placeholderElement, renderedElement); + } + } } // signal size changes for image tags @@ -295,6 +345,18 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende }; } +function postProcessCodeBlockLanguageId(lang: string | undefined): string { + if (!lang) { + return ''; + } + + const parts = lang.split(/[\s+|:|,|\{|\?]/, 1); + if (parts.length) { + return parts[0]; + } + return lang; +} + function resolveWithBaseUri(baseUri: URI, href: string): string { const hasScheme = /^\w[\w\d+.-]*:/.test(href); if (hasScheme) { @@ -309,7 +371,7 @@ function resolveWithBaseUri(baseUri: URI, href: string): string { } function sanitizeRenderedMarkdown( - options: { isTrusted?: boolean }, + options: { isTrusted?: boolean | MarkdownStringTrustedOptions }, renderedMarkdown: string, ): TrustedHTML { const { config, allowedSchemes } = getSanitizerOptions(options); @@ -317,7 +379,7 @@ function sanitizeRenderedMarkdown( if (e.attrName === 'style' || e.attrName === 'class') { if (element.tagName === 'SPAN') { if (e.attrName === 'style') { - e.keepAttr = /^(color\:#[0-9a-fA-F]+;)?(background-color\:#[0-9a-fA-F]+;)?$/.test(e.attrValue); + e.keepAttr = /^(color\:(#[0-9a-fA-F]+|var\(--vscode(-[a-zA-Z]+)+\));)?(background-color\:(#[0-9a-fA-F]+|var\(--vscode(-[a-zA-Z]+)+\));)?$/.test(e.attrValue); return; } else if (e.attrName === 'class') { e.keepAttr = /^codicon codicon-[a-z\-]+( codicon-modifier-[a-z\-]+)?$/.test(e.attrValue); @@ -339,7 +401,29 @@ function sanitizeRenderedMarkdown( } } -function getSanitizerOptions(options: { readonly isTrusted?: boolean }): { config: dompurify.Config; allowedSchemes: string[] } { +export const allowedMarkdownAttr = [ + 'align', + 'autoplay', + 'alt', + 'class', + 'controls', + 'data-code', + 'data-href', + 'height', + 'href', + 'loop', + 'muted', + 'playsinline', + 'poster', + 'src', + 'style', + 'target', + 'title', + 'width', + 'start', +]; + +function getSanitizerOptions(options: { readonly isTrusted?: boolean | MarkdownStringTrustedOptions }): { config: dompurify.Config; allowedSchemes: string[] } { const allowedSchemes = [ Schemas.http, Schemas.https, @@ -361,8 +445,8 @@ function getSanitizerOptions(options: { readonly isTrusted?: boolean }): { confi // Since we have our own sanitize function for marked, it's possible we missed some tag so let dompurify make sure. // HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/ // HTML table tags that can result from markdown are from https://github.github.com/gfm/#tables-extension- - ALLOWED_TAGS: ['ul', 'li', 'p', 'b', 'i', 'code', 'blockquote', 'ol', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'em', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'del', 'a', 'strong', 'br', 'img', 'span'], - ALLOWED_ATTR: ['href', 'data-href', 'target', 'title', 'src', 'alt', 'class', 'style', 'data-code', 'width', 'height', 'align'], + ALLOWED_TAGS: [...DOM.basicMarkupHtmlTags], + ALLOWED_ATTR: allowedMarkdownAttr, ALLOW_UNKNOWN_PROTOCOLS: true, }, allowedSchemes @@ -381,6 +465,27 @@ export function renderStringAsPlaintext(string: IMarkdownString | string) { * Strips all markdown from `markdown`. For example `# Header` would be output as `Header`. */ export function renderMarkdownAsPlaintext(markdown: IMarkdownString) { + // values that are too long will freeze the UI + let value = markdown.value ?? ''; + if (value.length > 100_000) { + value = `${value.substr(0, 100_000)}…`; + } + + const html = marked.parse(value, { renderer: plainTextRenderer.value }).replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m); + + return sanitizeRenderedMarkdown({ isTrusted: false }, html).toString(); +} + +const unescapeInfo = new Map<string, string>([ + ['"', '"'], + [' ', ' '], + ['&', '&'], + [''', '\''], + ['<', '<'], + ['>', '>'], +]); + +const plainTextRenderer = new Lazy<marked.Renderer>(() => { const renderer = new marked.Renderer(); renderer.code = (code: string): string => { @@ -443,22 +548,186 @@ export function renderMarkdownAsPlaintext(markdown: IMarkdownString) { 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 renderer; +}); + +function mergeRawTokenText(tokens: marked.Token[]): string { + let mergedTokenText = ''; + tokens.forEach(token => { + mergedTokenText += token.raw; + }); + return mergedTokenText; +} + +function completeSingleLinePattern(token: marked.Tokens.ListItem | marked.Tokens.Paragraph): marked.Token | undefined { + for (const subtoken of token.tokens) { + if (subtoken.type === 'text') { + const lines = subtoken.raw.split('\n'); + const lastLine = lines[lines.length - 1]; + if (lastLine.includes('`')) { + return completeCodespan(token); + } else if (lastLine.includes('**')) { + return completeDoublestar(token); + } else if (lastLine.match(/\*\w/)) { + return completeStar(token); + } else if (lastLine.match(/(^|\s)__\w/)) { + return completeDoubleUnderscore(token); + } else if (lastLine.match(/(^|\s)_\w/)) { + return completeUnderscore(token); + } else if (lastLine.match(/(^|\s)\[.*\]\(\w*/)) { + return completeLinkTarget(token); + } else if (lastLine.match(/(^|\s)\[\w/)) { + return completeLinkText(token); + } + } } - const unescapeInfo = new Map<string, string>([ - ['"', '"'], - [' ', ' '], - ['&', '&'], - [''', '\''], - ['<', '<'], - ['>', '>'], - ]); - - const html = marked.parse(value, { renderer }).replace(/&(#\d+|[a-zA-Z]+);/g, m => unescapeInfo.get(m) ?? m); - - return sanitizeRenderedMarkdown({ isTrusted: false }, html).toString(); + return undefined; +} + +// function completeListItemPattern(token: marked.Tokens.List): marked.Tokens.List | undefined { +// // Patch up this one list item +// const lastItem = token.items[token.items.length - 1]; + +// const newList = completeSingleLinePattern(lastItem); +// if (!newList || newList.type !== 'list') { +// // Nothing to fix, or not a pattern we were expecting +// return; +// } + +// // Re-parse the whole list with the last item replaced +// const completeList = marked.lexer(mergeRawTokenText(token.items.slice(0, token.items.length - 1)) + newList.items[0].raw); +// if (completeList.length === 1 && completeList[0].type === 'list') { +// return completeList[0]; +// } + +// // Not a pattern we were expecting +// return undefined; +// } + +export function fillInIncompleteTokens(tokens: marked.TokensList): marked.TokensList { + let i: number; + let newTokens: marked.Token[] | undefined; + for (i = 0; i < tokens.length; i++) { + const token = tokens[i]; + if (token.type === 'paragraph' && token.raw.match(/(\n|^)```/)) { + // If the code block was complete, it would be in a type='code' + newTokens = completeCodeBlock(tokens.slice(i)); + break; + } + + if (token.type === 'paragraph' && token.raw.match(/(\n|^)\|/)) { + newTokens = completeTable(tokens.slice(i)); + break; + } + + // if (i === tokens.length - 1 && token.type === 'list') { + // const newListToken = completeListItemPattern(token); + // if (newListToken) { + // newTokens = [newListToken]; + // break; + // } + // } + + if (i === tokens.length - 1 && token.type === 'paragraph') { + // Only operates on a single token, because any newline that follows this should break these patterns + const newToken = completeSingleLinePattern(token); + if (newToken) { + newTokens = [newToken]; + break; + } + } + } + + if (newTokens) { + const newTokensList = [ + ...tokens.slice(0, i), + ...newTokens + ]; + (newTokensList as marked.TokensList).links = tokens.links; + return newTokensList as marked.TokensList; + } + + return tokens; +} + +function completeCodeBlock(tokens: marked.Token[]): marked.Token[] { + const mergedRawText = mergeRawTokenText(tokens); + return marked.lexer(mergedRawText + '\n```'); +} + +function completeCodespan(token: marked.Token): marked.Token { + return completeWithString(token, '`'); +} + +function completeStar(tokens: marked.Token): marked.Token { + return completeWithString(tokens, '*'); +} + +function completeUnderscore(tokens: marked.Token): marked.Token { + return completeWithString(tokens, '_'); +} + +function completeLinkTarget(tokens: marked.Token): marked.Token { + return completeWithString(tokens, ')'); +} + +function completeLinkText(tokens: marked.Token): marked.Token { + return completeWithString(tokens, '](about:blank)'); +} + +function completeDoublestar(tokens: marked.Token): marked.Token { + return completeWithString(tokens, '**'); +} + +function completeDoubleUnderscore(tokens: marked.Token): marked.Token { + return completeWithString(tokens, '__'); +} + +function completeWithString(tokens: marked.Token[] | marked.Token, closingString: string): marked.Token { + const mergedRawText = mergeRawTokenText(Array.isArray(tokens) ? tokens : [tokens]); + + // If it was completed correctly, this should be a single token. + // Expecting either a Paragraph or a List + return marked.lexer(mergedRawText + closingString)[0] as marked.Token; +} + +function completeTable(tokens: marked.Token[]): marked.Token[] | undefined { + const mergedRawText = mergeRawTokenText(tokens); + const lines = mergedRawText.split('\n'); + + let numCols: number | undefined; // The number of line1 col headers + let hasSeparatorRow = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (typeof numCols === 'undefined' && line.match(/^\s*\|/)) { + const line1Matches = line.match(/(\|[^\|]+)(?=\||$)/g); + if (line1Matches) { + numCols = line1Matches.length; + } + } else if (typeof numCols === 'number') { + if (line.match(/^\s*\|/)) { + if (i !== lines.length - 1) { + // We got the line1 header row, and the line2 separator row, but there are more lines, and it wasn't parsed as a table! + // That's strange and means that the table is probably malformed in the source, so I won't try to patch it up. + return undefined; + } + + // Got a line2 separator row- partial or complete, doesn't matter, we'll replace it with a correct one + hasSeparatorRow = true; + } else { + // The line after the header row isn't a valid separator row, so the table is malformed, don't fix it up + return undefined; + } + } + } + + if (typeof numCols === 'number' && numCols > 0) { + const prefixText = hasSeparatorRow ? lines.slice(0, -1).join('\n') : mergedRawText; + const line1EndsInPipe = !!prefixText.match(/\|\s*$/); + const newRawText = prefixText + (line1EndsInPipe ? '' : '|') + `\n|${' --- |'.repeat(numCols)}`; + return marked.lexer(newRawText); + } + + return undefined; } diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index c18f6fbe5f..f2b42c0adb 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -74,7 +74,7 @@ export class StandardMouseEvent implements IMouseEvent { } // Find the position of the iframe this code is executing in relative to the iframe where the event was captured. - const iframeOffsets = IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(self, e.view); + const iframeOffsets = IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(window, e.view); this.posx -= iframeOffsets.left; this.posy -= iframeOffsets.top; } @@ -88,28 +88,14 @@ export class StandardMouseEvent implements IMouseEvent { } } -export interface IDataTransfer { - dropEffect: string; - effectAllowed: string; - types: any[]; - files: any[]; - - setData(type: string, data: string): void; - setDragImage(image: any, x: number, y: number): void; - - getData(type: string): string; - clearData(types?: string[]): void; -} - export class DragMouseEvent extends StandardMouseEvent { - public readonly dataTransfer: IDataTransfer; + public readonly dataTransfer: DataTransfer; constructor(e: MouseEvent) { super(e); this.dataTransfer = (<any>e).dataTransfer; } - } export interface IMouseWheelEvent extends MouseEvent { @@ -211,14 +197,10 @@ export class StandardWheelEvent { } public preventDefault(): void { - if (this.browserEvent) { - this.browserEvent.preventDefault(); - } + this.browserEvent?.preventDefault(); } public stopPropagation(): void { - if (this.browserEvent) { - this.browserEvent.stopPropagation(); - } + this.browserEvent?.stopPropagation(); } } diff --git a/src/vs/base/browser/performance.ts b/src/vs/base/browser/performance.ts new file mode 100644 index 0000000000..8449d710dd --- /dev/null +++ b/src/vs/base/browser/performance.ts @@ -0,0 +1,272 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export namespace inputLatency { + + // Measurements are recorded as totals, the average is calculated when the final measurements + // are created. + interface ICumulativeMeasurement { + total: number; + min: number; + max: number; + } + const totalKeydownTime: ICumulativeMeasurement = { total: 0, min: Number.MAX_VALUE, max: 0 }; + const totalInputTime: ICumulativeMeasurement = { ...totalKeydownTime }; + const totalRenderTime: ICumulativeMeasurement = { ...totalKeydownTime }; + const totalInputLatencyTime: ICumulativeMeasurement = { ...totalKeydownTime }; + let measurementsCount = 0; + + + + // The state of each event, this helps ensure the integrity of the measurement and that + // something unexpected didn't happen that could skew the measurement. + const enum EventPhase { + Before = 0, + InProgress = 1, + Finished = 2 + } + const state = { + keydown: EventPhase.Before, + input: EventPhase.Before, + render: EventPhase.Before, + }; + + /** + * Record the start of the keydown event. + */ + export function onKeyDown() { + /** Direct Check C. See explanation in {@link recordIfFinished} */ + recordIfFinished(); + performance.mark('inputlatency/start'); + performance.mark('keydown/start'); + state.keydown = EventPhase.InProgress; + queueMicrotask(markKeyDownEnd); + } + + /** + * Mark the end of the keydown event. + */ + function markKeyDownEnd() { + if (state.keydown === EventPhase.InProgress) { + performance.mark('keydown/end'); + state.keydown = EventPhase.Finished; + } + } + + /** + * Record the start of the beforeinput event. + */ + export function onBeforeInput() { + performance.mark('input/start'); + state.input = EventPhase.InProgress; + /** Schedule Task A. See explanation in {@link recordIfFinished} */ + scheduleRecordIfFinishedTask(); + } + + /** + * Record the start of the input event. + */ + export function onInput() { + if (state.input === EventPhase.Before) { + // it looks like we didn't receive a `beforeinput` + onBeforeInput(); + } + queueMicrotask(markInputEnd); + } + + function markInputEnd() { + if (state.input === EventPhase.InProgress) { + performance.mark('input/end'); + state.input = EventPhase.Finished; + } + } + + /** + * Record the start of the keyup event. + */ + export function onKeyUp() { + /** Direct Check D. See explanation in {@link recordIfFinished} */ + recordIfFinished(); + } + + /** + * Record the start of the selectionchange event. + */ + export function onSelectionChange() { + /** Direct Check E. See explanation in {@link recordIfFinished} */ + recordIfFinished(); + } + + /** + * Record the start of the animation frame performing the rendering. + */ + export function onRenderStart() { + // Render may be triggered during input, but we only measure the following animation frame + if (state.keydown === EventPhase.Finished && state.input === EventPhase.Finished && state.render === EventPhase.Before) { + // Only measure the first render after keyboard input + performance.mark('render/start'); + state.render = EventPhase.InProgress; + queueMicrotask(markRenderEnd); + /** Schedule Task B. See explanation in {@link recordIfFinished} */ + scheduleRecordIfFinishedTask(); + } + } + + /** + * Mark the end of the animation frame performing the rendering. + */ + function markRenderEnd() { + if (state.render === EventPhase.InProgress) { + performance.mark('render/end'); + state.render = EventPhase.Finished; + } + } + + function scheduleRecordIfFinishedTask() { + // Here we can safely assume that the `setTimeout` will not be + // artificially delayed by 4ms because we schedule it from + // event handlers + setTimeout(recordIfFinished); + } + + /** + * Record the input latency sample if input handling and rendering are finished. + * + * The challenge here is that we want to record the latency in such a way that it includes + * also the layout and painting work the browser does during the animation frame task. + * + * Simply scheduling a new task (via `setTimeout`) from the animation frame task would + * schedule the new task at the end of the task queue (after other code that uses `setTimeout`), + * so we need to use multiple strategies to make sure our task runs before others: + * + * We schedule tasks (A and B): + * - we schedule a task A (via a `setTimeout` call) when the input starts in `markInputStart`. + * If the animation frame task is scheduled quickly by the browser, then task A has a very good + * chance of being the very first task after the animation frame and thus will record the input latency. + * - however, if the animation frame task is scheduled a bit later, then task A might execute + * before the animation frame task. We therefore schedule another task B from `markRenderStart`. + * + * We do direct checks in browser event handlers (C, D, E): + * - if the browser has multiple keydown events queued up, they will be scheduled before the `setTimeout` tasks, + * so we do a direct check in the keydown event handler (C). + * - depending on timing, sometimes the animation frame is scheduled even before the `keyup` event, so we + * do a direct check there too (E). + * - the browser oftentimes emits a `selectionchange` event after an `input`, so we do a direct check there (D). + */ + function recordIfFinished() { + if (state.keydown === EventPhase.Finished && state.input === EventPhase.Finished && state.render === EventPhase.Finished) { + performance.mark('inputlatency/end'); + + performance.measure('keydown', 'keydown/start', 'keydown/end'); + performance.measure('input', 'input/start', 'input/end'); + performance.measure('render', 'render/start', 'render/end'); + performance.measure('inputlatency', 'inputlatency/start', 'inputlatency/end'); + + addMeasure('keydown', totalKeydownTime); + addMeasure('input', totalInputTime); + addMeasure('render', totalRenderTime); + addMeasure('inputlatency', totalInputLatencyTime); + + // console.info( + // `input latency=${performance.getEntriesByName('inputlatency')[0].duration.toFixed(1)} [` + + // `keydown=${performance.getEntriesByName('keydown')[0].duration.toFixed(1)}, ` + + // `input=${performance.getEntriesByName('input')[0].duration.toFixed(1)}, ` + + // `render=${performance.getEntriesByName('render')[0].duration.toFixed(1)}` + + // `]` + // ); + + measurementsCount++; + + reset(); + } + } + + function addMeasure(entryName: string, cumulativeMeasurement: ICumulativeMeasurement): void { + const duration = performance.getEntriesByName(entryName)[0].duration; + cumulativeMeasurement.total += duration; + cumulativeMeasurement.min = Math.min(cumulativeMeasurement.min, duration); + cumulativeMeasurement.max = Math.max(cumulativeMeasurement.max, duration); + } + + /** + * Clear the current sample. + */ + function reset() { + performance.clearMarks('keydown/start'); + performance.clearMarks('keydown/end'); + performance.clearMarks('input/start'); + performance.clearMarks('input/end'); + performance.clearMarks('render/start'); + performance.clearMarks('render/end'); + performance.clearMarks('inputlatency/start'); + performance.clearMarks('inputlatency/end'); + + performance.clearMeasures('keydown'); + performance.clearMeasures('input'); + performance.clearMeasures('render'); + performance.clearMeasures('inputlatency'); + + state.keydown = EventPhase.Before; + state.input = EventPhase.Before; + state.render = EventPhase.Before; + } + + export interface IInputLatencyMeasurements { + keydown: IInputLatencySingleMeasurement; + input: IInputLatencySingleMeasurement; + render: IInputLatencySingleMeasurement; + total: IInputLatencySingleMeasurement; + sampleCount: number; + } + + export interface IInputLatencySingleMeasurement { + average: number; + min: number; + max: number; + } + + /** + * Gets all input latency samples and clears the internal buffers to start recording a new set + * of samples. + */ + export function getAndClearMeasurements(): IInputLatencyMeasurements | undefined { + if (measurementsCount === 0) { + return undefined; + } + + // Assemble the result + const result = { + keydown: cumulativeToFinalMeasurement(totalKeydownTime), + input: cumulativeToFinalMeasurement(totalInputTime), + render: cumulativeToFinalMeasurement(totalRenderTime), + total: cumulativeToFinalMeasurement(totalInputLatencyTime), + sampleCount: measurementsCount + }; + + // Clear the cumulative measurements + clearCumulativeMeasurement(totalKeydownTime); + clearCumulativeMeasurement(totalInputTime); + clearCumulativeMeasurement(totalRenderTime); + clearCumulativeMeasurement(totalInputLatencyTime); + measurementsCount = 0; + + return result; + } + + function cumulativeToFinalMeasurement(cumulative: ICumulativeMeasurement): IInputLatencySingleMeasurement { + return { + average: cumulative.total / measurementsCount, + max: cumulative.max, + min: cumulative.min, + }; + } + + function clearCumulativeMeasurement(cumulative: ICumulativeMeasurement): void { + cumulative.total = 0; + cumulative.min = Number.MAX_VALUE; + cumulative.max = 0; + } + +} diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index f30c6dc8e2..7e3221d4c4 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -6,7 +6,8 @@ import * as DomUtils from 'vs/base/browser/dom'; import * as arrays from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { LinkedList } from 'vs/base/common/linkedList'; export namespace EventType { export const Tap = '-monaco-gesturetap'; @@ -71,11 +72,11 @@ export class Gesture extends Disposable { private static readonly HOLD_DELAY = 700; private dispatched = false; - private targets: HTMLElement[]; - private ignoreTargets: HTMLElement[]; + private readonly targets = new LinkedList<HTMLElement>(); + private readonly ignoreTargets = new LinkedList<HTMLElement>(); private handle: IDisposable | null; - private activeTouches: { [id: number]: TouchData }; + private readonly activeTouches: { [id: number]: TouchData }; private _lastSetTapCountTime: number; @@ -87,8 +88,6 @@ export class Gesture extends Disposable { this.activeTouches = {}; this.handle = null; - this.targets = []; - this.ignoreTargets = []; this._lastSetTapCountTime = 0; this._register(DomUtils.addDisposableListener(document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e), { passive: false })); this._register(DomUtils.addDisposableListener(document, 'touchend', (e: TouchEvent) => this.onTouchEnd(e))); @@ -103,13 +102,8 @@ export class Gesture extends Disposable { Gesture.INSTANCE = new Gesture(); } - Gesture.INSTANCE.targets.push(element); - - return { - dispose: () => { - Gesture.INSTANCE.targets = Gesture.INSTANCE.targets.filter(t => t !== element); - } - }; + const remove = Gesture.INSTANCE.targets.push(element); + return toDisposable(remove); } public static ignoreTarget(element: HTMLElement): IDisposable { @@ -120,13 +114,8 @@ export class Gesture extends Disposable { Gesture.INSTANCE = new Gesture(); } - Gesture.INSTANCE.ignoreTargets.push(element); - - return { - dispose: () => { - Gesture.INSTANCE.ignoreTargets = Gesture.INSTANCE.ignoreTargets.filter(t => t !== element); - } - }; + const remove = Gesture.INSTANCE.ignoreTargets.push(element); + return toDisposable(remove); } @memoize @@ -224,7 +213,7 @@ export class Gesture extends Disposable { const deltaY = finalY - data.rollingPageY[0]; // We need to get all the dispatch targets on the start of the inertia event - const dispatchTo = this.targets.filter(t => data.initialTarget instanceof Node && t.contains(data.initialTarget)); + const dispatchTo = [...this.targets].filter(t => data.initialTarget instanceof Node && t.contains(data.initialTarget)); this.inertia(dispatchTo, timestamp, // time now Math.abs(deltaX) / deltaT, // speed deltaX > 0 ? 1 : -1, // x direction @@ -273,21 +262,23 @@ export class Gesture extends Disposable { this._lastSetTapCountTime = 0; } - for (let i = 0; i < this.ignoreTargets.length; i++) { - if (event.initialTarget instanceof Node && this.ignoreTargets[i].contains(event.initialTarget)) { - return; + if (event.initialTarget instanceof Node) { + for (const ignoreTarget of this.ignoreTargets) { + if (ignoreTarget.contains(event.initialTarget)) { + return; + } + } + + for (const target of this.targets) { + if (target.contains(event.initialTarget)) { + target.dispatchEvent(event); + this.dispatched = true; + } } } - - this.targets.forEach(target => { - if (event.initialTarget instanceof Node && target.contains(event.initialTarget)) { - target.dispatchEvent(event); - this.dispatched = true; - } - }); } - private inertia(dispatchTo: EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void { + private inertia(dispatchTo: readonly EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void { this.handle = DomUtils.scheduleAtNextAnimationFrame(() => { const now = Date.now(); diff --git a/src/vs/base/browser/trustedTypes.ts b/src/vs/base/browser/trustedTypes.ts new file mode 100644 index 0000000000..6b83af8f2d --- /dev/null +++ b/src/vs/base/browser/trustedTypes.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { onUnexpectedError } from 'vs/base/common/errors'; + +export function createTrustedTypesPolicy<Options extends TrustedTypePolicyOptions>( + policyName: string, + policyOptions?: Options, +): undefined | Pick<TrustedTypePolicy<Options>, 'name' | Extract<keyof Options, keyof TrustedTypePolicyOptions>> { + + interface IMonacoEnvironment { + createTrustedTypesPolicy<Options extends TrustedTypePolicyOptions>( + policyName: string, + policyOptions?: Options, + ): undefined | Pick<TrustedTypePolicy<Options>, 'name' | Extract<keyof Options, keyof TrustedTypePolicyOptions>>; + } + const monacoEnvironment: IMonacoEnvironment | undefined = (globalThis as any).MonacoEnvironment; + + if (monacoEnvironment?.createTrustedTypesPolicy) { + try { + return monacoEnvironment.createTrustedTypesPolicy(policyName, policyOptions); + } catch (err) { + onUnexpectedError(err); + return undefined; + } + } + try { + return window.trustedTypes?.createPolicy(policyName, policyOptions); + } catch (err) { + onUnexpectedError(err); + return undefined; + } +} diff --git a/src/vs/base/browser/ui/actionbar/actionViewItems.ts b/src/vs/base/browser/ui/actionbar/actionViewItems.ts index 807cc5304d..6674b4657e 100644 --- a/src/vs/base/browser/ui/actionbar/actionViewItems.ts +++ b/src/vs/base/browser/ui/actionbar/actionViewItems.ts @@ -11,7 +11,8 @@ import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; -import { ISelectBoxOptions, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; +import { ISelectBoxOptions, ISelectBoxStyles, ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { Action, ActionRunner, IAction, IActionChangeEvent, IActionRunner, Separator } from 'vs/base/common/actions'; import { Disposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; @@ -100,10 +101,6 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { this._actionRunner = actionRunner; } - getAction(): IAction { - return this._action; - } - isEnabled(): boolean { return this._action.enabled; } @@ -219,7 +216,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } protected getTooltip(): string | undefined { - return this.getAction().tooltip; + return this.action.tooltip; } protected updateTooltip(): void { @@ -227,7 +224,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { return; } const title = this.getTooltip() ?? ''; - this.element.setAttribute('aria-label', title); + this.updateAriaLabel(); if (!this.options.hoverDelegate) { this.element.title = title; } else { @@ -241,6 +238,13 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { } } + protected updateAriaLabel(): void { + if (this.element) { + const title = this.getTooltip() ?? ''; + this.element.setAttribute('aria-label', title); + } + } + protected updateClass(): void { // implement in subclass } @@ -259,7 +263,7 @@ export class BaseActionViewItem extends Disposable implements IActionViewItem { this.element.remove(); this.element = undefined; } - + this._context = undefined; super.dispose(); } } @@ -268,6 +272,7 @@ export interface IActionViewItemOptions extends IBaseActionViewItemOptions { icon?: boolean; label?: boolean; keybinding?: string | null; + toggleStyles?: IToggleStyles; } export class ActionViewItem extends BaseActionViewItem { @@ -277,7 +282,7 @@ export class ActionViewItem extends BaseActionViewItem { private cssClass?: string; - constructor(context: unknown, action: IAction, options: IActionViewItemOptions = {}) { + constructor(context: unknown, action: IAction, options: IActionViewItemOptions) { super(context, action, options); this.options = options; @@ -294,15 +299,8 @@ export class ActionViewItem extends BaseActionViewItem { } if (this.label) { - if (this._action.id === Separator.ID) { - this.label.setAttribute('role', 'presentation'); // A separator is a presentation item - } else { - if (this.options.isMenu) { - this.label.setAttribute('role', 'menuitem'); - } else { - this.label.setAttribute('role', 'button'); - } - } + this.label.setAttribute('role', this.getDefaultAriaRole()); + } if (this.options.label && this.options.keybinding && this.element) { @@ -316,6 +314,18 @@ export class ActionViewItem extends BaseActionViewItem { this.updateChecked(); } + private getDefaultAriaRole(): 'presentation' | 'menuitem' | 'button' { + if (this._action.id === Separator.ID) { + return 'presentation'; // A separator is a presentation item + } else { + if (this.options.isMenu) { + return 'menuitem'; + } else { + return 'button'; + } + } + } + // 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 override focus(): void { @@ -341,20 +351,20 @@ export class ActionViewItem extends BaseActionViewItem { } } - override updateLabel(): void { + protected override updateLabel(): void { if (this.options.label && this.label) { - this.label.textContent = this.getAction().label; + this.label.textContent = this.action.label; } } - override getTooltip() { + protected override getTooltip() { let title: string | null = null; - if (this.getAction().tooltip) { - title = this.getAction().tooltip; + if (this.action.tooltip) { + title = this.action.tooltip; - } else if (!this.options.label && this.getAction().label && this.options.icon) { - title = this.getAction().label; + } else if (!this.options.label && this.action.label && this.options.icon) { + title = this.action.label; if (this.options.keybinding) { title = nls.localize({ key: 'titleLabel', comment: ['action title', 'action keybinding'] }, "{0} ({1})", title, this.options.keybinding); @@ -363,13 +373,13 @@ export class ActionViewItem extends BaseActionViewItem { return title ?? undefined; } - override updateClass(): void { + protected override updateClass(): void { if (this.cssClass && this.label) { this.label.classList.remove(...this.cssClass.split(' ')); } if (this.options.icon) { - this.cssClass = this.getAction().class; + this.cssClass = this.action.class; if (this.label) { this.label.classList.add('codicon'); @@ -396,8 +406,8 @@ export class ActionViewItem extends BaseActionViewItem { } } - override updateEnabled(): void { - if (this.getAction().enabled) { + protected override updateEnabled(): void { + if (this.action.enabled) { if (this.label) { this.label.removeAttribute('aria-disabled'); this.label.classList.remove('disabled'); @@ -414,28 +424,39 @@ export class ActionViewItem extends BaseActionViewItem { } } - override updateChecked(): void { + protected override updateAriaLabel(): void { if (this.label) { - if (this.getAction().checked) { - this.label.classList.add('checked'); + const title = this.getTooltip() ?? ''; + this.label.setAttribute('aria-label', title); + } + } + + protected override updateChecked(): void { + if (this.label) { + if (this.action.checked !== undefined) { + this.label.classList.toggle('checked', this.action.checked); + this.label.setAttribute('aria-checked', this.action.checked ? 'true' : 'false'); + this.label.setAttribute('role', 'checkbox'); } else { this.label.classList.remove('checked'); + this.label.setAttribute('aria-checked', ''); + this.label.setAttribute('role', this.getDefaultAriaRole()); } } } // {{SQL CARBON EDIT}} - BEGIN - override updateExpanded(): void { + protected override updateExpanded(): void { if (this.label) { - if (this.getAction().expanded !== undefined) { - this.label.setAttribute('aria-expanded', `${this.getAction().expanded}`); + if (this.action.expanded !== undefined) { + this.label.setAttribute('aria-expanded', `${this.action.expanded}`); } else { this.label.removeAttribute('aria-expanded'); } } } - override updateTooltip(): void { + protected override updateTooltip(): void { super.updateTooltip(); const tooltip = this.getTooltip(); if (tooltip) { @@ -446,13 +467,13 @@ export class ActionViewItem extends BaseActionViewItem { // {{SQL CARBON EDIT}} - END } -export class SelectActionViewItem extends BaseActionViewItem { +export class SelectActionViewItem<T = string> extends BaseActionViewItem { protected selectBox: SelectBox; - constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, selectBoxOptions?: ISelectBoxOptions) { + constructor(ctx: unknown, action: IAction, options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) { super(ctx, action); - this.selectBox = new SelectBox(options, selected, contextViewProvider, undefined, selectBoxOptions); + this.selectBox = new SelectBox(options, selected, contextViewProvider, styles, selectBoxOptions); this.selectBox.setFocusable(false); this._register(this.selectBox); @@ -468,12 +489,14 @@ export class SelectActionViewItem extends BaseActionViewItem { } private registerListeners(): void { - this._register(this.selectBox.onDidSelect(e => { - this.actionRunner.run(this._action, this.getActionContext(e.selected, e.index)); - })); + this._register(this.selectBox.onDidSelect(e => this.runAction(e.selected, e.index))); } - protected getActionContext(option: string, index: number) { + protected runAction(option: string, index: number): void { + this.actionRunner.run(this._action, this.getActionContext(option, index)); + } + + protected getActionContext(option: string, index: number): T | string { return option; } @@ -482,15 +505,11 @@ export class SelectActionViewItem extends BaseActionViewItem { } override focus(): void { - if (this.selectBox) { - this.selectBox.focus(); - } + this.selectBox?.focus(); } override blur(): void { - if (this.selectBox) { - this.selectBox.blur(); - } + this.selectBox?.blur(); } override render(container: HTMLElement): void { diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index aefa99df03..7f25471a44 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -46,6 +46,7 @@ } .monaco-action-bar .action-label { + display: flex; font-size: 11px; padding: 3px; border-radius: 5px; @@ -105,6 +106,12 @@ display: flex; } -.monaco-action-bar .action-item.action-dropdown-item > .action-label { - margin-right: 1px; +.monaco-action-bar .action-item.action-dropdown-item > .action-dropdown-item-separator { + display: flex; + align-items: center; + cursor: default; +} + +.monaco-action-bar .action-item.action-dropdown-item > .action-dropdown-item-separator > div { + width: 1px; } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index d1a3bff9a8..417ad38d49 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -10,7 +10,7 @@ import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { ActionRunner, IAction, IActionRunner, IRunEvent, Separator } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import * as types from 'vs/base/common/types'; import 'vs/css!./actionbar'; @@ -25,7 +25,7 @@ export interface IActionViewItem extends IDisposable { } export interface IActionViewItemProvider { - (action: IAction): IActionViewItem | undefined; + (action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined; } export const enum ActionsOrientation { @@ -62,17 +62,17 @@ export class ActionBar extends Disposable implements IActionRunner { private readonly options: IActionBarOptions; private _actionRunner: IActionRunner; + private readonly _actionRunnerDisposables = this._register(new DisposableStore()); private _context: unknown; private readonly _orientation: ActionsOrientation; private readonly _triggerKeys: { keys: KeyCode[]; keyDown: boolean; }; - private _actionIds: string[]; // View Items viewItems: IActionViewItem[]; - private viewItemDisposables: Map<IActionViewItem, IDisposable>; + private readonly viewItemDisposables = this._register(new DisposableMap<IActionViewItem>()); private previouslyFocusedItem?: number; protected focusedItem?: number; private focusTracker: DOM.IFocusTracker; @@ -84,20 +84,20 @@ export class ActionBar extends Disposable implements IActionRunner { // Elements domNode: HTMLElement; - protected actionsList: HTMLElement; + protected readonly actionsList: HTMLElement; - private _onDidBlur = this._register(new Emitter<void>()); + private readonly _onDidBlur = this._register(new Emitter<void>()); readonly onDidBlur = this._onDidBlur.event; - private _onDidCancel = this._register(new Emitter<void>({ onFirstListenerAdd: () => this.cancelHasListener = true })); + private readonly _onDidCancel = this._register(new Emitter<void>({ onWillAddFirstListener: () => this.cancelHasListener = true })); readonly onDidCancel = this._onDidCancel.event; private cancelHasListener = false; - private _onDidRun = this._register(new Emitter<IRunEvent>()); + private readonly _onDidRun = this._register(new Emitter<IRunEvent>()); readonly onDidRun = this._onDidRun.event; - private _onBeforeRun = this._register(new Emitter<IRunEvent>()); - readonly onBeforeRun = this._onBeforeRun.event; + private readonly _onWillRun = this._register(new Emitter<IRunEvent>()); + readonly onWillRun = this._onWillRun.event; constructor(container: HTMLElement, options: IActionBarOptions = {}) { super(); @@ -114,15 +114,13 @@ export class ActionBar extends Disposable implements IActionRunner { this._actionRunner = this.options.actionRunner; } else { this._actionRunner = new ActionRunner(); - this._register(this._actionRunner); + this._actionRunnerDisposables.add(this._actionRunner); } - this._register(this._actionRunner.onDidRun(e => this._onDidRun.fire(e))); - this._register(this._actionRunner.onBeforeRun(e => this._onBeforeRun.fire(e))); + this._actionRunnerDisposables.add(this._actionRunner.onDidRun(e => this._onDidRun.fire(e))); + this._actionRunnerDisposables.add(this._actionRunner.onWillRun(e => this._onWillRun.fire(e))); - this._actionIds = []; this.viewItems = []; - this.viewItemDisposables = new Map<IActionViewItem, IDisposable>(); this.focusedItem = undefined; this.domNode = document.createElement('div'); @@ -294,10 +292,14 @@ export class ActionBar extends Disposable implements IActionRunner { } set actionRunner(actionRunner: IActionRunner) { - if (actionRunner) { - this._actionRunner = actionRunner; - this.viewItems.forEach(item => item.actionRunner = actionRunner); - } + this._actionRunner = actionRunner; + + // when setting a new `IActionRunner` make sure to dispose old listeners and + // start to forward events from the new listener + this._actionRunnerDisposables.clear(); + this._actionRunnerDisposables.add(this._actionRunner.onDidRun(e => this._onDidRun.fire(e))); + this._actionRunnerDisposables.add(this._actionRunner.onWillRun(e => this._onWillRun.fire(e))); + this.viewItems.forEach(item => item.actionRunner = actionRunner); } getContainer(): HTMLElement { @@ -305,11 +307,32 @@ export class ActionBar extends Disposable implements IActionRunner { } hasAction(action: IAction): boolean { - return this._actionIds.includes(action.id); + return this.viewItems.findIndex(candidate => candidate.action.id === action.id) !== -1; } - getAction(index: number): IAction { - return this.viewItems[index].action; + getAction(indexOrElement: number | HTMLElement): IAction | undefined { + + // by index + if (typeof indexOrElement === 'number') { + return this.viewItems[indexOrElement]?.action; + } + + // by element + if (indexOrElement instanceof HTMLElement) { + while (indexOrElement.parentElement !== this.actionsList) { + if (!indexOrElement.parentElement) { + return undefined; + } + indexOrElement = indexOrElement.parentElement; + } + for (let i = 0; i < this.actionsList.childNodes.length; i++) { + if (this.actionsList.childNodes[i] === indexOrElement) { + return this.viewItems[i].action; + } + } + } + + return undefined; } push(arg: IAction | ReadonlyArray<IAction>, options: IActionOptions = {}): void { @@ -324,12 +347,13 @@ export class ActionBar extends Disposable implements IActionRunner { let item: IActionViewItem | undefined; + const viewItemOptions = { hoverDelegate: this.options.hoverDelegate, ...options }; if (this.options.actionViewItemProvider) { - item = this.options.actionViewItemProvider(action); + item = this.options.actionViewItemProvider(action, viewItemOptions); } if (!item) { - item = new ActionViewItem(this.context, action, { hoverDelegate: this.options.hoverDelegate, ...options }); + item = new ActionViewItem(this.context, action, viewItemOptions); } // Prevent native context menu on actions @@ -351,11 +375,9 @@ export class ActionBar extends Disposable implements IActionRunner { if (index === null || index < 0 || index >= this.actionsList.children.length) { this.actionsList.appendChild(actionViewItemElement); this.viewItems.push(item); - this._actionIds.push(action.id); } else { this.actionsList.insertBefore(actionViewItemElement, this.actionsList.children[index]); this.viewItems.splice(index, 0, item); - this._actionIds.splice(index, 0, action.id); index++; } }); @@ -391,20 +413,19 @@ export class ActionBar extends Disposable implements IActionRunner { pull(index: number): void { if (index >= 0 && index < this.viewItems.length) { this.actionsList.removeChild(this.actionsList.childNodes[index]); - this.viewItemDisposables.get(this.viewItems[index])?.dispose(); - this.viewItemDisposables.delete(this.viewItems[index]); + this.viewItemDisposables.deleteAndDispose(this.viewItems[index]); dispose(this.viewItems.splice(index, 1)); - this._actionIds.splice(index, 1); this.refreshRole(); } } clear(): void { - dispose(this.viewItems); - this.viewItemDisposables.forEach(d => d.dispose()); - this.viewItemDisposables.clear(); - this.viewItems = []; - this._actionIds = []; + if (this.isEmpty()) { + return; + } + + this.viewItems = dispose(this.viewItems); + this.viewItemDisposables.clearAndDisposeAll(); DOM.clearNode(this.actionsList); this.refreshRole(); } @@ -559,13 +580,9 @@ export class ActionBar extends Disposable implements IActionRunner { } override dispose(): void { - dispose(this.viewItems); - this.viewItems = []; - - this._actionIds = []; - + this._context = undefined; + this.viewItems = dispose(this.viewItems); this.getContainer().remove(); - super.dispose(); } } diff --git a/src/vs/base/browser/ui/aria/aria.ts b/src/vs/base/browser/ui/aria/aria.ts index f7aa17f160..bc2c1d2866 100644 --- a/src/vs/base/browser/ui/aria/aria.ts +++ b/src/vs/base/browser/ui/aria/aria.ts @@ -93,3 +93,77 @@ function insertMessage(target: HTMLElement, msg: string): void { target.style.visibility = 'hidden'; target.style.visibility = 'visible'; } + +// Copied from @types/react which original came from https://www.w3.org/TR/wai-aria-1.1/#role_definitions +export type AriaRole = + | 'alert' + | 'alertdialog' + | 'application' + | 'article' + | 'banner' + | 'button' + | 'cell' + | 'checkbox' + | 'columnheader' + | 'combobox' + | 'complementary' + | 'contentinfo' + | 'definition' + | 'dialog' + | 'directory' + | 'document' + | 'feed' + | 'figure' + | 'form' + | 'grid' + | 'gridcell' + | 'group' + | 'heading' + | 'img' + | 'link' + | 'list' + | 'listbox' + | 'listitem' + | 'log' + | 'main' + | 'marquee' + | 'math' + | 'menu' + | 'menubar' + | 'menuitem' + | 'menuitemcheckbox' + | 'menuitemradio' + | 'navigation' + | 'none' + | 'note' + | 'option' + | 'presentation' + | 'progressbar' + | 'radio' + | 'radiogroup' + | 'region' + | 'row' + | 'rowgroup' + | 'rowheader' + | 'scrollbar' + | 'search' + | 'searchbox' + | 'separator' + | 'slider' + | 'spinbutton' + | 'status' + | 'switch' + | 'tab' + | 'table' + | 'tablist' + | 'tabpanel' + | 'term' + | 'textbox' + | 'timer' + | 'toolbar' + | 'tooltip' + | 'tree' + | 'treegrid' + | 'treeitem' + | (string & {}) // Prevent type collapsing to `string` + ; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css index 3db5fcfe17..b3d6ec6a5b 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.css @@ -6,7 +6,6 @@ .monaco-breadcrumbs { user-select: none; -webkit-user-select: none; - -ms-user-select: none; display: flex; flex-direction: row; flex-wrap: nowrap; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 642145c356..debe10eeab 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -7,8 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { commonPrefixLength } from 'vs/base/common/arrays'; -import { CSSIcon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; +import { ThemeIcon } from 'vs/base/common/themables'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @@ -21,11 +20,11 @@ export abstract class BreadcrumbsItem { } export interface IBreadcrumbsWidgetStyles { - breadcrumbsBackground?: Color; - breadcrumbsForeground?: Color; - breadcrumbsHoverForeground?: Color; - breadcrumbsFocusForeground?: Color; - breadcrumbsFocusAndSelectionForeground?: Color; + readonly breadcrumbsBackground: string | undefined; + readonly breadcrumbsForeground: string | undefined; + readonly breadcrumbsHoverForeground: string | undefined; + readonly breadcrumbsFocusForeground: string | undefined; + readonly breadcrumbsFocusAndSelectionForeground: string | undefined; } export interface IBreadcrumbsItemEvent { @@ -39,7 +38,6 @@ export class BreadcrumbsWidget { private readonly _disposables = new DisposableStore(); private readonly _domNode: HTMLDivElement; - private readonly _styleElement: HTMLStyleElement; private readonly _scrollable: DomScrollableElement; private readonly _onDidSelectItem = new Emitter<IBreadcrumbsItemEvent>(); @@ -53,7 +51,7 @@ export class BreadcrumbsWidget { private readonly _items = new Array<BreadcrumbsItem>(); private readonly _nodes = new Array<HTMLDivElement>(); private readonly _freeNodes = new Array<HTMLDivElement>(); - private readonly _separatorIcon: CSSIcon; + private readonly _separatorIcon: ThemeIcon; private _enabled: boolean = true; private _focusedItemIdx: number = -1; @@ -65,7 +63,8 @@ export class BreadcrumbsWidget { constructor( container: HTMLElement, horizontalScrollbarSize: number, - separatorIcon: CSSIcon + separatorIcon: ThemeIcon, + styles: IBreadcrumbsWidgetStyles ) { this._domNode = document.createElement('div'); this._domNode.className = 'monaco-breadcrumbs'; @@ -83,7 +82,8 @@ export class BreadcrumbsWidget { this._disposables.add(dom.addStandardDisposableListener(this._domNode, 'click', e => this._onClick(e))); container.appendChild(this._scrollable.getDomNode()); - this._styleElement = dom.createStyleSheet(this._domNode); + const styleElement = dom.createStyleSheet(this._domNode); + this._style(styleElement, styles); const focusTracker = dom.trackFocus(this._domNode); this._disposables.add(focusTracker); @@ -142,7 +142,7 @@ export class BreadcrumbsWidget { }); } - style(style: IBreadcrumbsWidgetStyles): void { + private _style(styleElement: HTMLStyleElement, style: IBreadcrumbsWidgetStyles): void { let content = ''; if (style.breadcrumbsBackground) { content += `.monaco-breadcrumbs { background-color: ${style.breadcrumbsBackground}}`; @@ -159,9 +159,7 @@ export class BreadcrumbsWidget { if (style.breadcrumbsHoverForeground) { content += `.monaco-breadcrumbs:not(.disabled ) .monaco-breadcrumb-item:hover:not(.focused):not(.selected) { color: ${style.breadcrumbsHoverForeground}}\n`; } - if (this._styleElement.innerText !== content) { - this._styleElement.innerText = content; - } + styleElement.innerText = content; } setEnabled(value: boolean) { @@ -232,16 +230,24 @@ export class BreadcrumbsWidget { } } + revealLast(): void { + this._reveal(this._items.length - 1, false); + } + private _reveal(nth: number, minimal: boolean): void { + if (nth < 0 || nth >= this._nodes.length) { + return; + } const node = this._nodes[nth]; - if (node) { - const { width } = this._scrollable.getScrollDimensions(); - const { scrollLeft } = this._scrollable.getScrollPosition(); - if (!minimal || node.offsetLeft > scrollLeft + width || node.offsetLeft < scrollLeft) { - this._scrollable.setRevealOnScroll(false); - this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); - this._scrollable.setRevealOnScroll(true); - } + if (!node) { + return; + } + const { width } = this._scrollable.getScrollDimensions(); + const { scrollLeft } = this._scrollable.getScrollPosition(); + if (!minimal || node.offsetLeft > scrollLeft + width || node.offsetLeft < scrollLeft) { + this._scrollable.setRevealOnScroll(false); + this._scrollable.setScrollPosition({ scrollLeft: node.offsetLeft }); + this._scrollable.setRevealOnScroll(true); } } @@ -334,7 +340,7 @@ export class BreadcrumbsWidget { container.tabIndex = -1; container.setAttribute('role', 'listitem'); container.classList.add('monaco-breadcrumb-item'); - const iconContainer = dom.$(CSSIcon.asCSSSelector(this._separatorIcon)); + const iconContainer = dom.$(ThemeIcon.asCSSSelector(this._separatorIcon)); container.appendChild(iconContainer); } diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index 3781de7daa..010726adea 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -8,10 +8,13 @@ display: flex; width: 100%; padding: 4px; + border-radius: 2px; text-align: center; cursor: pointer; justify-content: center; align-items: center; + border: 1px solid var(--vscode-button-border, transparent); + line-height: 18px; } .monaco-text-button:focus { @@ -28,11 +31,39 @@ cursor: default; } -.monaco-text-button > .codicon { +.monaco-text-button .codicon { margin: 0 0.2em; color: inherit !important; } +.monaco-text-button.monaco-text-button-with-short-label { + flex-direction: row; + flex-wrap: wrap; + padding: 0 4px; + overflow: hidden; + height: 28px; +} + +.monaco-text-button.monaco-text-button-with-short-label > .monaco-button-label { + flex-basis: 100%; +} + +.monaco-text-button.monaco-text-button-with-short-label > .monaco-button-label-short { + flex-grow: 1; + width: 0; + overflow: hidden; +} + +.monaco-text-button.monaco-text-button-with-short-label > .monaco-button-label, +.monaco-text-button.monaco-text-button-with-short-label > .monaco-button-label-short { + display: flex; + justify-content: center; + align-items: center; + font-weight: normal; + font-style: inherit; + padding: 4px 0; +} + .monaco-button-dropdown { display: flex; cursor: pointer; @@ -67,32 +98,37 @@ } .monaco-button-dropdown > .monaco-button.monaco-dropdown-button { + border: 1px solid var(--vscode-button-border, transparent); border-left-width: 0 !important; + border-radius: 0 2px 2px 0; +} + +.monaco-button-dropdown > .monaco-button.monaco-text-button { + border-radius: 2px 0 0 2px; } .monaco-description-button { + display: flex; flex-direction: column; -} - -.monaco-description-button .monaco-button-label { - font-weight: 500; + align-items: center; + margin: 4px 5px; /* allows button focus outline to be visible */ } .monaco-description-button .monaco-button-description { font-style: italic; + font-size: 11px; + padding: 4px 20px; } .monaco-description-button .monaco-button-label, -.monaco-description-button .monaco-button-description -{ +.monaco-description-button .monaco-button-description { display: flex; justify-content: center; align-items: center; } .monaco-description-button .monaco-button-label > .codicon, -.monaco-description-button .monaco-button-description > .codicon -{ +.monaco-description-button .monaco-button-description > .codicon { margin: 0 0.2em; color: inherit !important; } diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index cfaa963689..1cf8fa7976 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -4,56 +4,72 @@ *--------------------------------------------------------------------------------------------*/ import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; -import { addDisposableListener, EventHelper, EventType, IFocusTracker, removeTabIndexAndUpdateFocus, reset, trackFocus } from 'vs/base/browser/dom'; // {{SQL CARBON EDIT}} Add removeTabIndexAndUpdateFocus +import { addDisposableListener, EventHelper, EventType, IFocusTracker, removeTabIndexAndUpdateFocus, reset, trackFocus } from 'vs/base/browser/dom'; +import { sanitize } from 'vs/base/browser/dompurify/dompurify'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch'; +import { renderMarkdown, renderStringAsPlaintext } from 'vs/base/browser/markdownRenderer'; +import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; -import { Codicon, CSSIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { Color } from 'vs/base/common/color'; -import { Emitter, Event as BaseEvent } from 'vs/base/common/event'; +import { Event as BaseEvent, Emitter } from 'vs/base/common/event'; +import { IMarkdownString, isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { mixin } from 'vs/base/common/objects'; -import { localize } from 'vs/nls'; +import { ThemeIcon } from 'vs/base/common/themables'; import 'vs/css!./button'; +import { localize } from 'vs/nls'; -export interface IButtonOptions extends IButtonStyles { +export interface IButtonOptions extends Partial<IButtonStyles> { readonly title?: boolean | string; readonly supportIcons?: boolean; + readonly supportShortLabel?: boolean; readonly secondary?: boolean; } export interface IButtonStyles { - buttonBackground?: Color; - buttonHoverBackground?: Color; - buttonForeground?: Color; - buttonSeparator?: Color; - buttonSecondaryBackground?: Color; - buttonSecondaryHoverBackground?: Color; - buttonSecondaryForeground?: Color; - buttonBorder?: Color; - // {{SQL CARBON EDIT}} - buttonSecondaryBorder?: Color; - buttonDisabledBackground?: Color; - buttonDisabledForeground?: Color; - buttonDisabledBorder?: Color; + readonly buttonBackground: string | undefined; + readonly buttonHoverBackground: string | undefined; + readonly buttonForeground: string | undefined; + readonly buttonSeparator: string | undefined; + readonly buttonSecondaryBackground: string | undefined; + readonly buttonSecondaryHoverBackground: string | undefined; + readonly buttonSecondaryForeground: string | undefined; + readonly buttonBorder: string | undefined; + // {{SQL CARBON EDIT}} - Start + readonly buttonSecondaryBorder: string | undefined; + readonly buttonDisabledBackground: string | undefined; + readonly buttonDisabledForeground: string | undefined; + readonly buttonDisabledBorder: string | undefined; + // {{SQL CARBON EDIT}} - End } -const defaultOptions: IButtonStyles = { - buttonBackground: Color.fromHex('#0E639C'), - buttonHoverBackground: Color.fromHex('#006BB3'), - buttonSeparator: Color.white, - buttonForeground: Color.white +export const unthemedButtonStyles: IButtonStyles = { + buttonBackground: '#0E639C', + buttonHoverBackground: '#006BB3', + buttonSeparator: Color.white.toString(), + buttonForeground: Color.white.toString(), + buttonBorder: undefined, + buttonSecondaryBackground: undefined, + buttonSecondaryForeground: undefined, + buttonSecondaryHoverBackground: undefined, + // {{SQL CARBON EDIT}} - Start + buttonSecondaryBorder: undefined, + buttonDisabledBackground: undefined, + buttonDisabledForeground: undefined, + buttonDisabledBorder: undefined + // {{SQL CARBON EDIT}} - End }; export interface IButton extends IDisposable { readonly element: HTMLElement; readonly onDidClick: BaseEvent<Event | undefined>; - label: string; - icon: CSSIcon; - enabled: boolean; - style(styles: IButtonStyles): void; + + set label(value: string | IMarkdownString); + set icon(value: ThemeIcon | string); // {{SQL CARBON EDIT}} - add string to the value type. + set enabled(value: boolean); + focus(): void; hasFocus(): boolean; } @@ -64,56 +80,48 @@ export interface IButtonWithDescription extends IButton { export class Button extends Disposable implements IButton { - protected _element: HTMLElement; protected options: IButtonOptions; - - private buttonBackground: Color | undefined; - private buttonHoverBackground: Color | undefined; - private buttonForeground: Color | undefined; - private buttonSecondaryBackground: Color | undefined; - private buttonSecondaryHoverBackground: Color | undefined; - private buttonSecondaryForeground: Color | undefined; - private buttonBorder: Color | undefined; - // {{SQL CARBON EDIT}} - private buttonSecondaryBorder: Color | undefined; - private buttonDisabledBackground: Color | undefined; - private buttonDisabledForeground: Color | undefined; - private buttonDisabledBorder: Color | undefined; + protected _element: HTMLElement; + protected _label: string | IMarkdownString = ''; + protected _labelElement: HTMLElement | undefined; + protected _labelShortElement: HTMLElement | undefined; + // {{SQL CARBON EDIT}} - Start private hasIcon: boolean = false; - // {{SQL CARBON EDIT}} - END + // {{SQL CARBON EDIT}} - End private _onDidClick = this._register(new Emitter<Event>()); get onDidClick(): BaseEvent<Event> { return this._onDidClick.event; } private focusTracker: IFocusTracker; - constructor(container: HTMLElement, options?: IButtonOptions) { + constructor(container: HTMLElement, options: IButtonOptions) { super(); - this.options = options || Object.create(null); - mixin(this.options, defaultOptions, false); - - this.buttonForeground = this.options.buttonForeground; - this.buttonBackground = this.options.buttonBackground; - this.buttonHoverBackground = this.options.buttonHoverBackground; - - this.buttonSecondaryForeground = this.options.buttonSecondaryForeground; - this.buttonSecondaryBackground = this.options.buttonSecondaryBackground; - this.buttonSecondaryHoverBackground = this.options.buttonSecondaryHoverBackground; - - this.buttonBorder = this.options.buttonBorder; - // {{SQL CARBON EDIT}} - this.buttonSecondaryBorder = this.options.buttonSecondaryBorder; - this.buttonDisabledBackground = this.options.buttonDisabledBackground; - this.buttonDisabledForeground = this.options.buttonDisabledForeground; - this.buttonDisabledBorder = this.options.buttonDisabledBorder; - + this.options = options; this._element = document.createElement('a'); this._element.classList.add('monaco-button'); this._element.tabIndex = 0; this._element.setAttribute('role', 'button'); + const background = options.secondary ? options.buttonSecondaryBackground : options.buttonBackground; + const foreground = options.secondary ? options.buttonSecondaryForeground : options.buttonForeground; + + this._element.style.color = foreground || ''; + this._element.style.backgroundColor = background || ''; + + if (options.supportShortLabel) { + this._labelShortElement = document.createElement('div'); + this._labelShortElement.classList.add('monaco-button-label-short'); + this._element.appendChild(this._labelShortElement); + + this._labelElement = document.createElement('div'); + this._labelElement.classList.add('monaco-button-label'); + this._element.appendChild(this._labelElement); + + this._element.classList.add('monaco-text-button-with-short-label'); + } + container.appendChild(this._element); this._register(Gesture.addTarget(this._element)); @@ -147,148 +155,176 @@ export class Button extends Disposable implements IButton { this._register(addDisposableListener(this._element, EventType.MOUSE_OVER, e => { if (!this._element.classList.contains('disabled')) { - this.setHoverBackground(); + this.updateBackground(true); } })); this._register(addDisposableListener(this._element, EventType.MOUSE_OUT, e => { - this.applyStyles(); // restore standard styles + this.updateBackground(false); // restore standard styles })); // Also set hover background when button is focused for feedback this.focusTracker = this._register(trackFocus(this._element)); - this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.setHoverBackground(); } })); - this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.applyStyles(); } })); - - this.applyStyles(); + this._register(this.focusTracker.onDidFocus(() => { if (this.enabled) { this.updateBackground(true); } })); + this._register(this.focusTracker.onDidBlur(() => { if (this.enabled) { this.updateBackground(false); } })); + // {{SQL CARBON EDIT}} - Start + this.updateStyles(); + // {{SQL CARBON EDIT}} - End } - protected setHoverBackground(): void { // {{SQL CARBON EDIT}} - mark as protected - // {{SQL CARBON EDIT}} - skip if this is an icon button - if (this.hasIcon) { + private getContentElements(content: string): HTMLElement[] { + const elements: HTMLSpanElement[] = []; + for (let segment of renderLabelWithIcons(content)) { + if (typeof (segment) === 'string') { + segment = segment.trim(); + + // Ignore empty segment + if (segment === '') { + continue; + } + + // Convert string segments to <span> nodes + const node = document.createElement('span'); + node.textContent = segment; + elements.push(node); + } else { + elements.push(segment); + } + } + + return elements; + } + + // {{ SQL CARBON EDIT}} - Mark as protected + protected updateBackground(hover: boolean): void { + // // {{SQL CARBON EDIT}} - Start + if (!this.enabled || this.hasIcon) { return; } - let hoverBackground; + // {{SQL CARBON EDIT}} - End + let background; if (this.options.secondary) { - hoverBackground = this.buttonSecondaryHoverBackground ? this.buttonSecondaryHoverBackground.toString() : null; + background = hover ? this.options.buttonSecondaryHoverBackground : this.options.buttonSecondaryBackground; } else { - hoverBackground = this.buttonHoverBackground ? this.buttonHoverBackground.toString() : null; + background = hover ? this.options.buttonHoverBackground : this.options.buttonBackground; } - if (hoverBackground) { - this._element.style.backgroundColor = hoverBackground; - } - } - - style(styles: IButtonStyles): void { - this.buttonForeground = styles.buttonForeground; - this.buttonBackground = styles.buttonBackground; - this.buttonHoverBackground = styles.buttonHoverBackground; - this.buttonSecondaryForeground = styles.buttonSecondaryForeground; - this.buttonSecondaryBackground = styles.buttonSecondaryBackground; - this.buttonSecondaryHoverBackground = styles.buttonSecondaryHoverBackground; - this.buttonBorder = styles.buttonBorder; - - // {{SQL CARBON EDIT}} - this.buttonSecondaryBorder = styles.buttonSecondaryBorder; - this.buttonDisabledBackground = styles.buttonDisabledBackground; - this.buttonDisabledForeground = styles.buttonDisabledForeground; - this.buttonDisabledBorder = styles.buttonDisabledBorder; - - this.applyStyles(); - } - - /** - // {{SQL CARBON EDIT}} -- removed 'private' access modifier @todo anthonydresser 4/12/19 things needs investigation whether we need this - applyStyles(): void { - if (this._element) { - let background, foreground; - if (this.options.secondary) { - foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : ''; - background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : ''; - } else { - foreground = this.buttonForeground ? this.buttonForeground.toString() : ''; - background = this.buttonBackground ? this.buttonBackground.toString() : ''; - } - - const border = this.buttonBorder ? this.buttonBorder.toString() : ''; - - this._element.style.color = foreground; + if (background) { this._element.style.backgroundColor = background; - - this._element.style.borderWidth = border ? '1px' : ''; - this._element.style.borderStyle = border ? 'solid' : ''; - this._element.style.borderColor = border; } } - */ - // {{SQL CARBON EDIT}} - custom applyStyles - applyStyles(): void { - if (this._element) { - let background, foreground, border, fontWeight, fontSize; - if (this.hasIcon) { - background = border = 'transparent'; - foreground = this.buttonSecondaryForeground; - fontWeight = fontSize = 'inherit'; - } else { - if (this.enabled) { - if (this.options.secondary) { - foreground = this.buttonSecondaryForeground ? this.buttonSecondaryForeground.toString() : ''; - background = this.buttonSecondaryBackground ? this.buttonSecondaryBackground.toString() : ''; - border = this.buttonSecondaryBorder ? this.buttonSecondaryBorder.toString() : ''; - } else { - foreground = this.buttonForeground ? this.buttonForeground.toString() : ''; - background = this.buttonBackground ? this.buttonBackground.toString() : ''; - border = this.buttonBorder ? this.buttonBorder.toString() : ''; - } + // {{SQL CARBON EDIT}} - Start + protected updateStyles(): void { + let background, foreground, border, fontWeight, fontSize: string; + if (this.hasIcon) { + background = border = 'transparent'; + foreground = this.options.buttonSecondaryForeground; + fontWeight = fontSize = 'inherit'; + } else { + if (this.enabled) { + if (this.options.secondary) { + foreground = this.options.buttonSecondaryForeground; + background = this.options.buttonSecondaryBackground; + border = this.options.buttonSecondaryBorder; + } else { + foreground = this.options.buttonForeground; + background = this.options.buttonBackground; + border = this.options.buttonBorder; } - else { - foreground = this.buttonDisabledForeground; - background = this.buttonDisabledBackground; - border = this.buttonDisabledBorder; - } - fontWeight = '600'; - fontSize = '12px'; } - - this._element.style.color = foreground; - this._element.style.backgroundColor = background; - this._element.style.borderWidth = border ? '1px' : ''; - this._element.style.borderStyle = border ? 'solid' : ''; - this._element.style.borderColor = border; - this._element.style.opacity = this.hasIcon ? '' : '1'; - this._element.style.fontWeight = fontWeight; - this._element.style.fontSize = fontSize; - this._element.style.borderRadius = '2px'; + else { + foreground = this.options.buttonDisabledForeground; + background = this.options.buttonDisabledBackground; + border = this.options.buttonDisabledBorder; + } + fontWeight = '600'; + fontSize = '12px'; } + this._element.style.color = foreground || ''; + this._element.style.backgroundColor = background || ''; + this._element.style.borderWidth = border ? '1px' : ''; + this._element.style.borderStyle = border ? 'solid' : ''; + this._element.style.borderColor = border || ''; + this._element.style.opacity = this.hasIcon ? '' : '1'; + this._element.style.fontWeight = fontWeight; + this._element.style.fontSize = fontSize; + this._element.style.borderRadius = '2px'; } - // {{SQL CARBON EDIT}} - end custom applyStyles + // {{SQL CARBON EDIT}} - End get element(): HTMLElement { return this._element; } - set label(value: string) { - this._element.classList.add('monaco-text-button'); - if (this.options.supportIcons) { - reset(this._element, ...renderLabelWithIcons(value)); - } else { - this._element.textContent = value; + set label(value: string | IMarkdownString) { + if (this._label === value) { + return; } - this._element.setAttribute('aria-label', value); // {{SQL CARBON EDIT}} + + if (isMarkdownString(this._label) && isMarkdownString(value) && markdownStringEqual(this._label, value)) { + return; + } + + this._element.classList.add('monaco-text-button'); + const labelElement = this.options.supportShortLabel ? this._labelElement! : this._element; + + if (isMarkdownString(value)) { + const rendered = renderMarkdown(value, { inline: true }); + rendered.dispose(); + + // Don't include outer `<p>` + const root = rendered.element.querySelector('p')?.innerHTML; + if (root) { + // Only allow a very limited set of inline html tags + const sanitized = sanitize(root, { ADD_TAGS: ['b', 'i', 'u', 'code', 'span'], ALLOWED_ATTR: ['class'], RETURN_TRUSTED_TYPE: true }); + labelElement.innerHTML = sanitized as unknown as string; + } else { + reset(labelElement); + } + } else { + if (this.options.supportIcons) { + reset(labelElement, ...this.getContentElements(value)); + } else { + labelElement.textContent = value; + } + } + this._element.setAttribute('aria-label', <any>value); // {{SQL CARBON EDIT}} if (typeof this.options.title === 'string') { this._element.title = this.options.title; } else if (this.options.title) { - this._element.title = value; + this._element.title = renderStringAsPlaintext(value); + } + + this._label = value; + } + + get label(): string | IMarkdownString { + return this._label; + } + + set labelShort(value: string) { + if (!this.options.supportShortLabel || !this._labelShortElement) { + return; + } + + if (this.options.supportIcons) { + reset(this._labelShortElement, ...this.getContentElements(value)); + } else { + this._labelShortElement.textContent = value; } } - // {{SQL CARBON EDIT}} - set icon(icon: CSSIcon) { + // {{SQL CARBON EDIT}} - accept class name directly + set icon(icon: ThemeIcon | string) { + if (typeof icon === 'string') { + this._element.classList.add(...icon.split(' ')); + } else { + this._element.classList.add(...ThemeIcon.asClassNameArray(icon)); + } this.hasIcon = icon !== undefined; - this._element.classList.add(...icon.id.split(' ')); - this.applyStyles(); + this.updateStyles(); } + // {{SQL CARBON EDIT}} - End set enabled(value: boolean) { if (value) { @@ -300,7 +336,9 @@ export class Button extends Disposable implements IButton { this._element.setAttribute('aria-disabled', String(true)); removeTabIndexAndUpdateFocus(this._element); // {{SQL CARBON EDIT}} - remove tabindex when disabled otherwise disabled control is still keyboard focusable. } - this.applyStyles(); // {{SQL CARBON EDIT}} + // {{SQL CARBON EDIT}} - Start + this.updateStyles(); + // {{SQL CARBON EDIT}} - End } get enabled() { @@ -344,7 +382,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.button = this._register(new Button(this.element, options)); this._register(this.button.onDidClick(e => this._onDidClick.fire(e))); - this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined))); + this.action = this._register(new Action('primaryAction', renderStringAsPlaintext(this.button.label), undefined, true, async () => this._onDidClick.fire(undefined))); this.separatorContainer = document.createElement('div'); this.separatorContainer.classList.add('monaco-button-dropdown-separator'); @@ -353,6 +391,17 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.separatorContainer.appendChild(this.separator); this.element.appendChild(this.separatorContainer); + // Separator styles + const border = options.buttonBorder; + if (border) { + this.separatorContainer.style.borderTop = '1px solid ' + border; + this.separatorContainer.style.borderBottom = '1px solid ' + border; + } + + const buttonBackground = options.secondary ? options.buttonSecondaryBackground : options.buttonBackground; + this.separatorContainer.style.backgroundColor = buttonBackground ?? ''; + this.separator.style.backgroundColor = options.buttonSeparator ?? ''; + this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...'); this.dropdownButton.element.setAttribute('aria-haspopup', 'true'); @@ -375,7 +424,7 @@ export class ButtonWithDropdown extends Disposable implements IButton { this.action.label = value; } - set icon(icon: CSSIcon) { + set icon(icon: ThemeIcon) { this.button.icon = icon; } @@ -390,25 +439,6 @@ export class ButtonWithDropdown extends Disposable implements IButton { return this.button.enabled; } - style(styles: IButtonStyles): void { - this.button.style(styles); - this.dropdownButton.style(styles); - - // Separator - const border = styles.buttonBorder ? styles.buttonBorder.toString() : ''; - - this.separatorContainer.style.borderTopWidth = border ? '1px' : ''; - this.separatorContainer.style.borderTopStyle = border ? 'solid' : ''; - this.separatorContainer.style.borderTopColor = border; - - this.separatorContainer.style.borderBottomWidth = border ? '1px' : ''; - this.separatorContainer.style.borderBottomStyle = border ? 'solid' : ''; - this.separatorContainer.style.borderBottomColor = border; - - this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? ''; - this.separator.style.backgroundColor = styles.buttonSeparator?.toString() ?? ''; - } - focus(): void { this.button.focus(); } @@ -418,37 +448,55 @@ export class ButtonWithDropdown extends Disposable implements IButton { } } -export class ButtonWithDescription extends Button implements IButtonWithDescription { - - private _labelElement: HTMLElement; +export class ButtonWithDescription implements IButtonWithDescription { + private _button: Button; + private _element: HTMLElement; private _descriptionElement: HTMLElement; - constructor(container: HTMLElement, options?: IButtonOptions) { - super(container, options); - + constructor(container: HTMLElement, private readonly options: IButtonOptions) { + this._element = document.createElement('div'); this._element.classList.add('monaco-description-button'); - - this._labelElement = document.createElement('div'); - this._labelElement.classList.add('monaco-button-label'); - this._element.appendChild(this._labelElement); + this._button = new Button(this._element, options); this._descriptionElement = document.createElement('div'); this._descriptionElement.classList.add('monaco-button-description'); this._element.appendChild(this._descriptionElement); + + container.appendChild(this._element); } - override set label(value: string) { - this._element.classList.add('monaco-text-button'); - if (this.options.supportIcons) { - reset(this._labelElement, ...renderLabelWithIcons(value)); - } else { - this._labelElement.textContent = value; - } - if (typeof this.options.title === 'string') { - this._element.title = this.options.title; - } else if (this.options.title) { - this._element.title = value; - } + get onDidClick(): BaseEvent<Event | undefined> { + return this._button.onDidClick; + } + + get element(): HTMLElement { + return this._element; + } + + set label(value: string) { + this._button.label = value; + } + + set icon(icon: ThemeIcon) { + this._button.icon = icon; + } + + get enabled(): boolean { + return this._button.enabled; + } + + set enabled(enabled: boolean) { + this._button.enabled = enabled; + } + + focus(): void { + this._button.focus(); + } + hasFocus(): boolean { + return this._button.hasFocus(); + } + dispose(): void { + this._button.dispose(); } set description(value: string) { @@ -472,13 +520,13 @@ export class ButtonBar extends Disposable { return this._buttons; } - addButton(options?: IButtonOptions): IButton { + addButton(options: IButtonOptions): IButton { const button = this._register(new Button(this.container, options)); this.pushButton(button); return button; } - addButtonWithDescription(options?: IButtonOptions): IButtonWithDescription { + addButtonWithDescription(options: IButtonOptions): IButtonWithDescription { const button = this._register(new ButtonWithDescription(this.container, options)); this.pushButton(button); return button; @@ -514,7 +562,5 @@ export class ButtonBar extends Disposable { } })); - } - } diff --git a/src/vs/base/browser/ui/centered/centeredViewLayout.ts b/src/vs/base/browser/ui/centered/centeredViewLayout.ts index 01d058b344..4f67b0195a 100644 --- a/src/vs/base/browser/ui/centered/centeredViewLayout.ts +++ b/src/vs/base/browser/ui/centered/centeredViewLayout.ts @@ -3,24 +3,31 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $ } from 'vs/base/browser/dom'; +import { $, IDomNodePagePosition } from 'vs/base/browser/dom'; import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid'; -import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview'; -import { ISplitViewStyles, IView as ISplitViewView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; +import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; +import { DistributeSizing, ISplitViewStyles, IView as ISplitViewView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { Color } from 'vs/base/common/color'; import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; export interface CenteredViewState { + // width of the fixed centered layout + targetWidth: number; + // proportional size of left margin leftMarginRatio: number; + // proportional size of right margin rightMarginRatio: number; } -const GOLDEN_RATIO = { +const defaultState: CenteredViewState = { + targetWidth: 900, leftMarginRatio: 0.1909, - rightMarginRatio: 0.1909 + rightMarginRatio: 0.1909, }; +const distributeSizing: DistributeSizing = { type: 'distribute' }; + function createEmptyView(background: Color | undefined): ISplitViewView<{ top: number; left: number }> { const element = $('.centered-layout-margin'); element.style.height = '100%'; @@ -54,14 +61,18 @@ export interface ICenteredViewStyles extends ISplitViewStyles { export class CenteredViewLayout implements IDisposable { private splitView?: SplitView<{ top: number; left: number }>; - private width: number = 0; - private height: number = 0; + private lastLayoutPosition: IDomNodePagePosition = { width: 0, height: 0, left: 0, top: 0 }; private style!: ICenteredViewStyles; private didLayout = false; private emptyViews: ISplitViewView<{ top: number; left: number }>[] | undefined; private readonly splitViewDisposables = new DisposableStore(); + private centeredLayoutFixedWidth = true; - constructor(private container: HTMLElement, private view: IView, public readonly state: CenteredViewState = { leftMarginRatio: GOLDEN_RATIO.leftMarginRatio, rightMarginRatio: GOLDEN_RATIO.rightMarginRatio }) { + constructor( + private container: HTMLElement, + private view: IView, + public state: CenteredViewState = { ...defaultState } + ) { this.container.appendChild(this.view.element); // Make sure to hide the split view overflow like sashes #52892 this.container.style.overflow = 'hidden'; @@ -87,25 +98,53 @@ export class CenteredViewLayout implements IDisposable { } layout(width: number, height: number, top: number, left: number): void { - this.width = width; - this.height = height; + this.lastLayoutPosition = { width, height, top, left }; if (this.splitView) { - this.splitView.layout(width); - if (!this.didLayout) { - this.resizeMargins(); + this.splitView.layout(width, this.lastLayoutPosition); + if (!this.didLayout || this.centeredLayoutFixedWidth) { + this.resizeSplitViews(); } } else { this.view.layout(width, height, top, left); } + this.didLayout = true; } - private resizeMargins(): void { + private resizeSplitViews(): void { if (!this.splitView) { return; } - this.splitView.resizeView(0, this.state.leftMarginRatio * this.width); - this.splitView.resizeView(2, this.state.rightMarginRatio * this.width); + if (this.centeredLayoutFixedWidth) { + const centerViewWidth = Math.min(this.lastLayoutPosition.width, this.state.targetWidth); + const marginWidthFloat = (this.lastLayoutPosition.width - centerViewWidth) / 2; + this.splitView.resizeView(0, Math.floor(marginWidthFloat)); + this.splitView.resizeView(1, centerViewWidth); + this.splitView.resizeView(2, Math.ceil(marginWidthFloat)); + } else { + const leftMargin = this.state.leftMarginRatio * this.lastLayoutPosition.width; + const rightMargin = this.state.rightMarginRatio * this.lastLayoutPosition.width; + const center = this.lastLayoutPosition.width - leftMargin - rightMargin; + this.splitView.resizeView(0, leftMargin); + this.splitView.resizeView(1, center); + this.splitView.resizeView(2, rightMargin); + } + } + + setFixedWidth(option: boolean) { + this.centeredLayoutFixedWidth = option; + if (!!this.splitView) { + this.updateState(); + this.resizeSplitViews(); + } + } + + private updateState() { + if (!!this.splitView) { + this.state.targetWidth = this.splitView.getViewSize(1); + this.state.leftMarginRatio = this.splitView.getViewSize(0) / this.lastLayoutPosition.width; + this.state.rightMarginRatio = this.splitView.getViewSize(2) / this.lastLayoutPosition.width; + } } isActive(): boolean { @@ -137,40 +176,44 @@ export class CenteredViewLayout implements IDisposable { this.splitView.orthogonalEndSash = this.boundarySashes.bottom; this.splitViewDisposables.add(this.splitView.onDidSashChange(() => { - if (this.splitView) { - this.state.leftMarginRatio = this.splitView.getViewSize(0) / this.width; - this.state.rightMarginRatio = this.splitView.getViewSize(2) / this.width; + if (!!this.splitView) { + this.updateState(); } })); this.splitViewDisposables.add(this.splitView.onDidSashReset(() => { - this.state.leftMarginRatio = GOLDEN_RATIO.leftMarginRatio; - this.state.rightMarginRatio = GOLDEN_RATIO.rightMarginRatio; - this.resizeMargins(); + this.state = { ...defaultState }; + this.resizeSplitViews(); })); - this.splitView.layout(this.width); - this.splitView.addView(toSplitViewView(this.view, () => this.height), 0); + this.splitView.layout(this.lastLayoutPosition.width, this.lastLayoutPosition); const backgroundColor = this.style ? this.style.background : undefined; this.emptyViews = [createEmptyView(backgroundColor), createEmptyView(backgroundColor)]; - this.splitView.addView(this.emptyViews[0], this.state.leftMarginRatio * this.width, 0); - this.splitView.addView(this.emptyViews[1], this.state.rightMarginRatio * this.width, 2); + + this.splitView.addView(this.emptyViews[0], distributeSizing, 0); + this.splitView.addView(toSplitViewView(this.view, () => this.lastLayoutPosition.height), distributeSizing, 1); + this.splitView.addView(this.emptyViews[1], distributeSizing, 2); + + this.resizeSplitViews(); } else { if (this.splitView) { this.container.removeChild(this.splitView.el); } this.splitViewDisposables.clear(); - if (this.splitView) { - this.splitView.dispose(); - } + this.splitView?.dispose(); this.splitView = undefined; this.emptyViews = undefined; this.container.appendChild(this.view.element); - this.view.layout(this.width, this.height, 0, 0); + this.view.layout(this.lastLayoutPosition.width, this.lastLayoutPosition.height, this.lastLayoutPosition.top, this.lastLayoutPosition.left); } } isDefault(state: CenteredViewState): boolean { - return state.leftMarginRatio === GOLDEN_RATIO.leftMarginRatio && state.rightMarginRatio === GOLDEN_RATIO.rightMarginRatio; + if (this.centeredLayoutFixedWidth) { + return state.targetWidth === defaultState.targetWidth; + } else { + return state.leftMarginRatio === defaultState.leftMarginRatio + && state.rightMarginRatio === defaultState.rightMarginRatio; + } } dispose(): void { diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.css b/src/vs/base/browser/ui/codicons/codicon/codicon.css index 5fdac3595f..c1daa11112 100644 --- a/src/vs/base/browser/ui/codicons/codicon/codicon.css +++ b/src/vs/base/browser/ui/codicons/codicon/codicon.css @@ -20,7 +20,6 @@ -moz-osx-font-smoothing: grayscale; user-select: none; -webkit-user-select: none; - -ms-user-select: none; } /* icon rules are dynamically created by the platform theme service (see iconsStyleSheet.ts) */ diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index 5abfa748fb..c4a33a4d56 100644 Binary files a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/codicons/codiconStyles.ts b/src/vs/base/browser/ui/codicons/codiconStyles.ts index e0359ddfe7..6acd62bda5 100644 --- a/src/vs/base/browser/ui/codicons/codiconStyles.ts +++ b/src/vs/base/browser/ui/codicons/codiconStyles.ts @@ -3,15 +3,5 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Codicon } from 'vs/base/common/codicons'; import 'vs/css!./codicon/codicon'; import 'vs/css!./codicon/codicon-modifiers'; - - -export function formatRule(c: Codicon) { - let def = c.definition; - while (def instanceof Codicon) { - def = def.definition; - } - return `.codicon-${c.id}:before { content: '${def.fontCharacter}'; }`; -} diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 9e8449d5d8..585f055029 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -367,12 +367,6 @@ const SHADOW_ROOT_CSS = /* css */ ` all: initial; /* 1st rule so subsequent properties are reset. */ } - @font-face { - font-family: "codicon"; - font-display: block; - src: url("./codicon.ttf?5d4d76ab2ce5108968ad644d591a16a6") format("truetype"); - } - .codicon[class*='codicon-'] { font: normal normal normal 16px/1 codicon; display: inline-block; diff --git a/src/vs/base/browser/ui/countBadge/countBadge.ts b/src/vs/base/browser/ui/countBadge/countBadge.ts index 276ffa8625..ecedc52af6 100644 --- a/src/vs/base/browser/ui/countBadge/countBadge.ts +++ b/src/vs/base/browser/ui/countBadge/countBadge.ts @@ -4,49 +4,35 @@ *--------------------------------------------------------------------------------------------*/ import { $, append } from 'vs/base/browser/dom'; -import { Color } from 'vs/base/common/color'; -import { mixin } from 'vs/base/common/objects'; import { format } from 'vs/base/common/strings'; -import { IThemable } from 'vs/base/common/styler'; import 'vs/css!./countBadge'; -export interface ICountBadgeOptions extends ICountBadgetyles { - count?: number; - countFormat?: string; - titleFormat?: string; +export interface ICountBadgeOptions { + readonly count?: number; + readonly countFormat?: string; + readonly titleFormat?: string; } -export interface ICountBadgetyles { - badgeBackground?: Color; - badgeForeground?: Color; - badgeBorder?: Color; +export interface ICountBadgeStyles { + readonly badgeBackground: string | undefined; + readonly badgeForeground: string | undefined; + readonly badgeBorder: string | undefined; } -const defaultOpts = { - badgeBackground: Color.fromHex('#4D4D4D'), - badgeForeground: Color.fromHex('#FFFFFF') +export const unthemedCountStyles: ICountBadgeStyles = { + badgeBackground: '#4D4D4D', + badgeForeground: '#FFFFFF', + badgeBorder: undefined }; -export class CountBadge implements IThemable { +export class CountBadge { private element: HTMLElement; private count: number = 0; private countFormat: string; private titleFormat: string; - private badgeBackground: Color | undefined; - private badgeForeground: Color | undefined; - private badgeBorder: Color | undefined; - - private options: ICountBadgeOptions; - - constructor(container: HTMLElement, options?: ICountBadgeOptions) { - this.options = options || Object.create(null); - mixin(this.options, defaultOpts, false); - - this.badgeBackground = this.options.badgeBackground; - this.badgeForeground = this.options.badgeForeground; - this.badgeBorder = this.options.badgeBorder; + constructor(container: HTMLElement, private readonly options: ICountBadgeOptions, private readonly styles: ICountBadgeStyles) { this.element = append(container, $('.monaco-count-badge')); this.countFormat = this.options.countFormat || '{0}'; @@ -73,29 +59,11 @@ export class CountBadge implements IThemable { this.element.textContent = format(this.countFormat, this.count); this.element.title = format(this.titleFormat, this.count); - this.applyStyles(); - } + this.element.style.backgroundColor = this.styles.badgeBackground ?? ''; + this.element.style.color = this.styles.badgeForeground ?? ''; - style(styles: ICountBadgetyles): void { - this.badgeBackground = styles.badgeBackground; - this.badgeForeground = styles.badgeForeground; - this.badgeBorder = styles.badgeBorder; - - this.applyStyles(); - } - - private applyStyles(): void { - if (this.element) { - const background = this.badgeBackground ? this.badgeBackground.toString() : ''; - const foreground = this.badgeForeground ? this.badgeForeground.toString() : ''; - const border = this.badgeBorder ? this.badgeBorder.toString() : ''; - - this.element.style.backgroundColor = background; - this.element.style.color = foreground; - - this.element.style.borderWidth = border ? '1px' : ''; - this.element.style.borderStyle = border ? 'solid' : ''; - this.element.style.borderColor = border; + if (this.styles.badgeBorder) { + this.element.style.border = `1px solid ${this.styles.badgeBorder}`; } } } diff --git a/src/vs/base/browser/ui/dialog/dialog.css b/src/vs/base/browser/ui/dialog/dialog.css index 6c09d5c1e4..c0b2df64c0 100644 --- a/src/vs/base/browser/ui/dialog/dialog.css +++ b/src/vs/base/browser/ui/dialog/dialog.css @@ -66,7 +66,6 @@ padding-left: 24px; user-select: text; -webkit-user-select: text; - -ms-user-select: text; word-wrap: break-word; /* never overflow long words, but break to next line */ white-space: normal; } @@ -105,7 +104,6 @@ cursor: pointer; user-select: none; -webkit-user-select: none; - -ms-user-select: none; } /** Dialog: Input */ @@ -148,7 +146,6 @@ .monaco-dialog-box > .dialog-buttons-row > .dialog-buttons > .monaco-button { width: fit-content; - width: -moz-fit-content; padding: 5px 10px; overflow: hidden; text-overflow: ellipsis; diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 2dc0b2c077..d08a532863 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -8,14 +8,14 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ButtonBar, ButtonWithDescription, IButtonStyles } from 'vs/base/browser/ui/button/button'; import { ICheckboxStyles, Checkbox } from 'vs/base/browser/ui/toggle/toggle'; -import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IInputBoxStyles, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { Action } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; +import { ThemeIcon } from 'vs/base/common/themables'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Disposable } from 'vs/base/common/lifecycle'; -import { isLinux, isMacintosh } from 'vs/base/common/platform'; +import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import 'vs/css!./dialog'; import * as nls from 'vs/nls'; @@ -34,10 +34,14 @@ export interface IDialogOptions { readonly inputs?: IDialogInputOptions[]; readonly keyEventProcessor?: (event: StandardKeyboardEvent) => void; readonly renderBody?: (container: HTMLElement) => void; - readonly icon?: Codicon; + readonly icon?: ThemeIcon; readonly buttonDetails?: string[]; readonly disableCloseAction?: boolean; readonly disableDefaultAction?: boolean; + readonly buttonStyles: IButtonStyles; + readonly checkboxStyles: ICheckboxStyles; + readonly inputBoxStyles: IInputBoxStyles; + readonly dialogStyles: IDialogStyles; } export interface IDialogResult { @@ -46,19 +50,15 @@ export interface IDialogResult { readonly values?: string[]; } -export interface IDialogStyles extends IButtonStyles, ICheckboxStyles { - readonly dialogForeground?: Color; - readonly dialogBackground?: Color; - readonly dialogShadow?: Color; - readonly dialogBorder?: Color; - readonly errorIconForeground?: Color; - readonly warningIconForeground?: Color; - readonly infoIconForeground?: Color; - readonly inputBackground?: Color; - readonly inputForeground?: Color; - readonly inputBorder?: Color; - readonly textLinkForeground?: Color; - +export interface IDialogStyles { + readonly dialogForeground: string | undefined; + readonly dialogBackground: string | undefined; + readonly dialogShadow: string | undefined; + readonly dialogBorder: string | undefined; + readonly errorIconForeground: string | undefined; + readonly warningIconForeground: string | undefined; + readonly infoIconForeground: string | undefined; + readonly textLinkForeground: string | undefined; } interface ButtonMapEntry { @@ -77,12 +77,12 @@ export class Dialog extends Disposable { private readonly checkbox: Checkbox | undefined; private readonly toolbarContainer: HTMLElement; private buttonBar: ButtonBar | undefined; - private styles: IDialogStyles | undefined; private focusToReturn: HTMLElement | undefined; private readonly inputs: InputBox[]; private readonly buttons: string[]; + private readonly buttonStyles: IButtonStyles; - constructor(private container: HTMLElement, private message: string, buttons: string[] | undefined, private options: IDialogOptions) { + constructor(private container: HTMLElement, private message: string, buttons: string[] | undefined, private readonly options: IDialogOptions) { super(); this.modalElement = this.container.appendChild($(`.monaco-dialog-modal-block.dimmed`)); @@ -92,6 +92,8 @@ export class Dialog extends Disposable { this.element.tabIndex = -1; hide(this.element); + this.buttonStyles = options.buttonStyles; + if (Array.isArray(buttons) && buttons.length > 0) { this.buttons = buttons; } else if (!this.options.disableDefaultAction) { @@ -136,6 +138,7 @@ export class Dialog extends Disposable { const inputBox = this._register(new InputBox(inputRowElement, undefined, { placeholder: input.placeholder, type: input.type ?? 'text', + inputBoxStyles: options.inputBoxStyles })); if (input.value) { @@ -151,7 +154,9 @@ export class Dialog extends Disposable { if (this.options.checkboxLabel) { const checkboxRowElement = this.messageContainer.appendChild($('.dialog-checkbox-row')); - const checkbox = this.checkbox = this._register(new Checkbox(this.options.checkboxLabel, !!this.options.checkboxChecked)); + const checkbox = this.checkbox = this._register( + new Checkbox(this.options.checkboxLabel, !!this.options.checkboxChecked, options.checkboxStyles) + ); checkboxRowElement.appendChild(checkbox.domNode); @@ -162,19 +167,21 @@ export class Dialog extends Disposable { const toolbarRowElement = this.element.appendChild($('.dialog-toolbar-row')); this.toolbarContainer = toolbarRowElement.appendChild($('.dialog-toolbar')); + + this.applyStyles(); } private getIconAriaLabel(): string { - const typeLabel = nls.localize('dialogInfoMessage', 'Info'); + let typeLabel = nls.localize('dialogInfoMessage', 'Info'); switch (this.options.type) { case 'error': - nls.localize('dialogErrorMessage', 'Error'); + typeLabel = nls.localize('dialogErrorMessage', 'Error'); break; case 'warning': - nls.localize('dialogWarningMessage', 'Warning'); + typeLabel = nls.localize('dialogWarningMessage', 'Warning'); break; case 'pending': - nls.localize('dialogPendingMessage', 'In Progress'); + typeLabel = nls.localize('dialogPendingMessage', 'In Progress'); break; case 'none': case 'info': @@ -202,7 +209,7 @@ export class Dialog extends Disposable { // Handle button clicks buttonMap.forEach((entry, index) => { const primary = buttonMap[index].index === 0; - const button = this.options.buttonDetails ? this._register(buttonBar.addButtonWithDescription({ title: true, secondary: !primary })) : this._register(buttonBar.addButton({ title: true, secondary: !primary })); + const button = this.options.buttonDetails ? this._register(buttonBar.addButtonWithDescription({ title: true, secondary: !primary, ...this.buttonStyles })) : this._register(buttonBar.addButton({ title: true, secondary: !primary, ...this.buttonStyles })); button.label = mnemonicButtonLabel(buttonMap[index].label, true); if (button instanceof ButtonWithDescription) { button.description = this.options.buttonDetails![buttonMap[index].index]; @@ -352,26 +359,28 @@ export class Dialog extends Disposable { const spinModifierClassName = 'codicon-modifier-spin'; - this.iconElement.classList.remove(...Codicon.dialogError.classNamesArray, ...Codicon.dialogWarning.classNamesArray, ...Codicon.dialogInfo.classNamesArray, ...Codicon.loading.classNamesArray, spinModifierClassName); + this.iconElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.dialogError), ...ThemeIcon.asClassNameArray(Codicon.dialogWarning), ...ThemeIcon.asClassNameArray(Codicon.dialogInfo), ...ThemeIcon.asClassNameArray(Codicon.loading), spinModifierClassName); if (this.options.icon) { - this.iconElement.classList.add(...this.options.icon.classNamesArray); + this.iconElement.classList.add(...ThemeIcon.asClassNameArray(this.options.icon)); } else { switch (this.options.type) { case 'error': - this.iconElement.classList.add(...Codicon.dialogError.classNamesArray); + this.iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.dialogError)); break; case 'warning': - this.iconElement.classList.add(...Codicon.dialogWarning.classNamesArray); + this.iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.dialogWarning)); break; case 'pending': - this.iconElement.classList.add(...Codicon.loading.classNamesArray, spinModifierClassName); + this.iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.loading), spinModifierClassName); break; case 'none': + this.iconElement.classList.add('no-codicon'); + break; case 'info': case 'question': default: - this.iconElement.classList.add(...Codicon.dialogInfo.classNamesArray); + this.iconElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.dialogInfo)); break; } } @@ -380,14 +389,14 @@ export class Dialog extends Disposable { if (!this.options.disableCloseAction) { const actionBar = this._register(new ActionBar(this.toolbarContainer, {})); - const action = this._register(new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), Codicon.dialogClose.classNames, true, async () => { + const action = this._register(new Action('dialog.close', nls.localize('dialogClose', "Close Dialog"), ThemeIcon.asClassName(Codicon.dialogClose), true, async () => { resolve({ button: this.options.cancelId || 0, checkboxChecked: this.checkbox ? this.checkbox.checked : undefined }); })); - actionBar.push(action, { icon: true, label: false, }); + actionBar.push(action, { icon: true, label: false }); } this.applyStyles(); @@ -412,62 +421,47 @@ export class Dialog extends Disposable { } private applyStyles() { - if (this.styles) { - const style = this.styles; + const style = this.options.dialogStyles; - const fgColor = style.dialogForeground; - const bgColor = style.dialogBackground; - const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : ''; - const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : ''; - const linkFgColor = style.textLinkForeground; + const fgColor = style.dialogForeground; + const bgColor = style.dialogBackground; + const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : ''; + const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : ''; + const linkFgColor = style.textLinkForeground; - this.shadowElement.style.boxShadow = shadowColor; + this.shadowElement.style.boxShadow = shadowColor; - this.element.style.color = fgColor?.toString() ?? ''; - this.element.style.backgroundColor = bgColor?.toString() ?? ''; - this.element.style.border = border; + this.element.style.color = fgColor ?? ''; + this.element.style.backgroundColor = bgColor ?? ''; + this.element.style.border = border; - this.buttonBar?.buttons.forEach(button => button.style(style)); + // TODO fix + // if (fgColor && bgColor) { + // const messageDetailColor = fgColor.transparent(.9); + // this.messageDetailElement.style.mixBlendMode = messageDetailColor.makeOpaque(bgColor).toString(); + // } - this.checkbox?.style(style); - - if (fgColor && bgColor) { - const messageDetailColor = fgColor.transparent(.9); - this.messageDetailElement.style.color = messageDetailColor.makeOpaque(bgColor).toString(); - } - - if (linkFgColor) { - for (const el of this.messageContainer.getElementsByTagName('a')) { - el.style.color = linkFgColor.toString(); - } - } - - let color; - switch (this.options.type) { - case 'error': - color = style.errorIconForeground; - break; - case 'warning': - color = style.warningIconForeground; - break; - default: - color = style.infoIconForeground; - break; - } - if (color) { - this.iconElement.style.color = color.toString(); - } - - for (const input of this.inputs) { - input.style(style); + if (linkFgColor) { + for (const el of this.messageContainer.getElementsByTagName('a')) { + el.style.color = linkFgColor; } } - } - style(style: IDialogStyles): void { - this.styles = style; - - this.applyStyles(); + let color; + switch (this.options.type) { + case 'error': + color = style.errorIconForeground; + break; + case 'warning': + color = style.warningIconForeground; + break; + default: + color = style.infoIconForeground; + break; + } + if (color) { + this.iconElement.style.color = color; + } } override dispose(): void { @@ -485,22 +479,45 @@ export class Dialog extends Disposable { } private rearrangeButtons(buttons: Array<string>, cancelId: number | undefined): ButtonMapEntry[] { - const buttonMap: ButtonMapEntry[] = []; - if (buttons.length === 0) { - return buttonMap; + + // Maps each button to its current label and old index + // so that when we move them around it's not a problem + const buttonMap: ButtonMapEntry[] = buttons.map((label, index) => ({ label, index })); + + if (buttons.length < 2) { + return buttonMap; // only need to rearrange if there are 2+ buttons } - // Maps each button to its current label and old index so that when we move them around it's not a problem - buttons.forEach((button, index) => { - buttonMap.push({ label: button, index }); - }); - - // macOS/linux: reverse button order if `cancelId` is defined if (isMacintosh || isLinux) { - if (cancelId !== undefined && cancelId < buttons.length) { + + // Linux: the GNOME HIG (https://developer.gnome.org/hig/patterns/feedback/dialogs.html?highlight=dialog) + // recommend the following: + // "Always ensure that the cancel button appears first, before the affirmative button. In left-to-right + // locales, this is on the left. This button order ensures that users become aware of, and are reminded + // of, the ability to cancel prior to encountering the affirmative button." + + // macOS: the HIG (https://developer.apple.com/design/human-interface-guidelines/components/presentation/alerts) + // recommend the following: + // "Place buttons where people expect. In general, place the button people are most likely to choose on the trailing side in a + // row of buttons or at the top in a stack of buttons. Always place the default button on the trailing side of a row or at the + // top of a stack. Cancel buttons are typically on the leading side of a row or at the bottom of a stack." + + if (typeof cancelId === 'number' && buttonMap[cancelId]) { const cancelButton = buttonMap.splice(cancelId, 1)[0]; - buttonMap.reverse(); - buttonMap.splice(buttonMap.length - 1, 0, cancelButton); + buttonMap.splice(1, 0, cancelButton); + } + + buttonMap.reverse(); + } else if (isWindows) { + + // Windows: the HIG (https://learn.microsoft.com/en-us/windows/win32/uxguide/win-dialog-box) + // recommend the following: + // "One of the following sets of concise commands: Yes/No, Yes/No/Cancel, [Do it]/Cancel, + // [Do it]/[Don't do it], [Do it]/[Don't do it]/Cancel." + + if (typeof cancelId === 'number' && buttonMap[cancelId]) { + const cancelButton = buttonMap.splice(cancelId, 1)[0]; + buttonMap.push(cancelButton); } } diff --git a/src/vs/base/browser/ui/dropdown/dropdown.ts b/src/vs/base/browser/ui/dropdown/dropdown.ts index b35fd37dff..6cb4a98bea 100644 --- a/src/vs/base/browser/ui/dropdown/dropdown.ts +++ b/src/vs/base/browser/ui/dropdown/dropdown.ts @@ -7,7 +7,7 @@ import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; import { $, addDisposableListener, append, EventHelper, EventType } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch'; -import { AnchorAlignment, IAnchor, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IMenuOptions } from 'vs/base/browser/ui/menu/menu'; import { ActionRunner, IAction } from 'vs/base/common/actions'; import { Emitter } from 'vs/base/common/event'; @@ -19,12 +19,12 @@ export interface ILabelRenderer { (container: HTMLElement): IDisposable | null; } -export interface IBaseDropdownOptions { +export interface IBaseDropdownOptions { // {{SQL CARBON EDIT}} - export interface label?: string; labelRenderer?: ILabelRenderer; } -export class BaseDropdown extends ActionRunner { +export class BaseDropdown extends ActionRunner { // {{SQL CARBON EDIT}} - export class private _element: HTMLElement; private boxContainer?: HTMLElement; private _label?: HTMLElement; @@ -148,60 +148,6 @@ export class BaseDropdown extends ActionRunner { } } -export interface IDropdownOptions extends IBaseDropdownOptions { - contextViewProvider: IContextViewProvider; -} - -export class Dropdown extends BaseDropdown { - private contextViewProvider: IContextViewProvider; - - constructor(container: HTMLElement, options: IDropdownOptions) { - super(container, options); - - this.contextViewProvider = options.contextViewProvider; - } - - override show(): void { - super.show(); - - this.element.classList.add('active'); - - this.contextViewProvider.showContextView({ - getAnchor: () => this.getAnchor(), - - render: (container) => { - return this.renderContents(container); - }, - - onDOMEvent: (e, activeElement) => { - this.onEvent(e, activeElement); - }, - - onHide: () => this.onHide() - }); - } - - protected getAnchor(): HTMLElement | IAnchor { - return this.element; - } - - protected onHide(): void { - this.element.classList.remove('active'); - } - - override hide(): void { - super.hide(); - - if (this.contextViewProvider) { - this.contextViewProvider.hideContextView(); - } - } - - protected renderContents(container: HTMLElement): IDisposable | null { - return null; - } -} - export interface IActionProvider { getActions(): readonly IAction[]; } @@ -261,7 +207,7 @@ export class DropdownMenu extends BaseDropdown { getAnchor: () => this.element, getActions: () => this.actions, getActionsContext: () => this.menuOptions ? this.menuOptions.context : null, - getActionViewItem: action => this.menuOptions && this.menuOptions.actionViewItemProvider ? this.menuOptions.actionViewItemProvider(action) : undefined, + getActionViewItem: (action, options) => this.menuOptions && this.menuOptions.actionViewItemProvider ? this.menuOptions.actionViewItemProvider(action, options) : undefined, getKeyBinding: action => this.menuOptions && this.menuOptions.getKeyBinding ? this.menuOptions.getKeyBinding(action) : undefined, getMenuClassName: () => this.menuClassName, onHide: () => this.onHide(), diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index 7b4bffa10d..e27c4437fb 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -3,8 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { IContextMenuProvider } from 'vs/base/browser/contextmenu'; -import { $, addDisposableListener, append, EventType } from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, EventType, h } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions, IBaseActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; @@ -12,6 +13,7 @@ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { DropdownMenu, IActionProvider, IDropdownMenuOptions, ILabelRenderer } from 'vs/base/browser/ui/dropdown/dropdown'; import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import { Emitter } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; @@ -130,13 +132,13 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { this.updateEnabled(); } - override getTooltip(): string | undefined { + protected override getTooltip(): string | undefined { let title: string | null = null; - if (this.getAction().tooltip) { - title = this.getAction().tooltip; - } else if (this.getAction().label) { - title = this.getAction().label; + if (this.action.tooltip) { + title = this.action.tooltip; + } else if (this.action.label) { + title = this.action.label; } return title ?? undefined; @@ -155,13 +157,11 @@ export class DropdownMenuActionViewItem extends BaseActionViewItem { } show(): void { - if (this.dropdownMenu) { - this.dropdownMenu.show(); - } + this.dropdownMenu?.show(); } protected override updateEnabled(): void { - const disabled = !this.getAction().enabled; + const disabled = !this.action.enabled; this.actionItem?.classList.toggle('disabled', disabled); this.element?.classList.toggle('disabled', disabled); } @@ -195,7 +195,13 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { return 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, ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] }); + + const menuActionClassNames = (<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []; + const separator = h('div.action-dropdown-item-separator', [h('div', {})]).root; + separator.classList.toggle('prominent', menuActionClassNames.includes('prominent')); + append(this.element, separator); + + this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames] }); this.dropdownMenuActionViewItem.render(this.element); this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => { @@ -232,5 +238,3 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { this.dropdownMenuActionViewItem?.setFocusable(focusable); } } - - diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 1fed42abf6..4b996d0879 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -11,14 +11,14 @@ import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview import { CaseSensitiveToggle, RegexToggle, WholeWordsToggle } from 'vs/base/browser/ui/findinput/findInputToggles'; import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; +import { DisposableStore } from 'vs/base/common/lifecycle'; -export interface IFindInputOptions extends IFindInputStyles { +export interface IFindInputOptions { readonly placeholder?: string; readonly width?: number; readonly validation?: IInputValidator; @@ -27,18 +27,15 @@ export interface IFindInputOptions extends IFindInputStyles { readonly flexibleWidth?: boolean; readonly flexibleMaxHeight?: number; + readonly showCommonFindToggles?: boolean; readonly appendCaseSensitiveLabel?: string; readonly appendWholeWordsLabel?: string; readonly appendRegexLabel?: string; readonly history?: string[]; readonly additionalToggles?: Toggle[]; readonly showHistoryHint?: () => boolean; -} - -export interface IFindInputStyles extends IInputBoxStyles { - inputActiveOptionBorder?: Color; - inputActiveOptionForeground?: Color; - inputActiveOptionBackground?: Color; + readonly toggleStyles: IToggleStyles; + readonly inputBoxStyles: IInputBoxStyles; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -47,37 +44,21 @@ export class FindInput extends Widget { static readonly OPTION_CHANGE: string = 'optionChange'; - private contextViewProvider: IContextViewProvider; private placeholder: string; private validation?: IInputValidator; private label: string; + private readonly showCommonFindToggles: boolean; private fixFocusOnOptionClickEnabled = true; private imeSessionInProgress = false; + private additionalTogglesDisposables: DisposableStore = new DisposableStore(); - protected inputActiveOptionBorder?: Color; - protected inputActiveOptionForeground?: Color; - protected inputActiveOptionBackground?: Color; - protected inputBackground?: Color; - protected inputForeground?: Color; - protected inputBorder?: Color; - - protected inputValidationInfoBorder?: Color; - protected inputValidationInfoBackground?: Color; - protected inputValidationInfoForeground?: Color; - protected inputValidationWarningBorder?: Color; - protected inputValidationWarningBackground?: Color; - protected inputValidationWarningForeground?: Color; - protected inputValidationErrorBorder?: Color; - protected inputValidationErrorBackground?: Color; - protected inputValidationErrorForeground?: Color; - - protected controls: HTMLDivElement; - protected regex: RegexToggle; - protected wholeWords: WholeWordsToggle; - protected caseSensitive: CaseSensitiveToggle; + protected readonly controls: HTMLDivElement; + protected readonly regex?: RegexToggle; + protected readonly wholeWords?: WholeWordsToggle; + protected readonly caseSensitive?: CaseSensitiveToggle; protected additionalToggles: Toggle[] = []; - public domNode: HTMLElement; - public inputBox: HistoryInputBox; + public readonly domNode: HTMLElement; + public readonly inputBox: HistoryInputBox; private readonly _onDidOptionChange = this._register(new Emitter<boolean>()); public readonly onDidOptionChange: Event<boolean /* via keyboard */> = this._onDidOptionChange.event; @@ -100,29 +81,12 @@ export class FindInput extends Widget { private _onRegexKeyDown = this._register(new Emitter<IKeyboardEvent>()); public readonly onRegexKeyDown: Event<IKeyboardEvent> = this._onRegexKeyDown.event; - constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider, private readonly _showOptionButtons: boolean, options: IFindInputOptions) { + constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider | undefined, options: IFindInputOptions) { super(); - this.contextViewProvider = contextViewProvider; this.placeholder = options.placeholder || ''; this.validation = options.validation; this.label = options.label || NLS_DEFAULT_LABEL; - - this.inputActiveOptionBorder = options.inputActiveOptionBorder; - this.inputActiveOptionForeground = options.inputActiveOptionForeground; - this.inputActiveOptionBackground = options.inputActiveOptionBackground; - this.inputBackground = options.inputBackground; - this.inputForeground = options.inputForeground; - this.inputBorder = options.inputBorder; - - this.inputValidationInfoBorder = options.inputValidationInfoBorder; - this.inputValidationInfoBackground = options.inputValidationInfoBackground; - this.inputValidationInfoForeground = options.inputValidationInfoForeground; - this.inputValidationWarningBorder = options.inputValidationWarningBorder; - this.inputValidationWarningBackground = options.inputValidationWarningBackground; - this.inputValidationWarningForeground = options.inputValidationWarningForeground; - this.inputValidationErrorBorder = options.inputValidationErrorBorder; - this.inputValidationErrorBackground = options.inputValidationErrorBackground; - this.inputValidationErrorForeground = options.inputValidationErrorForeground; + this.showCommonFindToggles = !!options.showCommonFindToggles; const appendCaseSensitiveLabel = options.appendCaseSensitiveLabel || ''; const appendWholeWordsLabel = options.appendWholeWordsLabel || ''; @@ -135,148 +99,114 @@ export class FindInput extends Widget { this.domNode = document.createElement('div'); this.domNode.classList.add('monaco-findInput'); - this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { + this.inputBox = this._register(new HistoryInputBox(this.domNode, contextViewProvider, { placeholder: this.placeholder || '', ariaLabel: this.label || '', validationOptions: { validation: this.validation }, - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder, history, showHistoryHint: options.showHistoryHint, flexibleHeight, flexibleWidth, - flexibleMaxHeight + flexibleMaxHeight, + inputBoxStyles: options.inputBoxStyles, })); - this.regex = this._register(new RegexToggle({ - appendTitle: appendRegexLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.regex.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - this._register(this.regex.onKeyDown(e => { - this._onRegexKeyDown.fire(e); - })); - - this.wholeWords = this._register(new WholeWordsToggle({ - appendTitle: appendWholeWordsLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.wholeWords.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - - this.caseSensitive = this._register(new CaseSensitiveToggle({ - appendTitle: appendCaseSensitiveLabel, - isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground - })); - this._register(this.caseSensitive.onChange(viaKeyboard => { - this._onDidOptionChange.fire(viaKeyboard); - if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { - this.inputBox.focus(); - } - this.validate(); - })); - this._register(this.caseSensitive.onKeyDown(e => { - this._onCaseSensitiveKeyDown.fire(e); - })); - - // Arrow-Key support to navigate between options - const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; - this.onkeydown(this.domNode, (event: IKeyboardEvent) => { - if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { - const index = indexes.indexOf(<HTMLElement>document.activeElement); - if (index >= 0) { - let newIndex: number = -1; - if (event.equals(KeyCode.RightArrow)) { - newIndex = (index + 1) % indexes.length; - } else if (event.equals(KeyCode.LeftArrow)) { - if (index === 0) { - newIndex = indexes.length - 1; - } else { - newIndex = index - 1; - } - } - - if (event.equals(KeyCode.Escape)) { - indexes[index].blur(); - this.inputBox.focus(); - } else if (newIndex >= 0) { - indexes[newIndex].focus(); - } - - dom.EventHelper.stop(event, true); - } - } - }); - - - this.controls = document.createElement('div'); - this.controls.className = 'controls'; - this.controls.style.display = this._showOptionButtons ? 'block' : 'none'; - this.controls.appendChild(this.caseSensitive.domNode); - this.controls.appendChild(this.wholeWords.domNode); - this.controls.appendChild(this.regex.domNode); - - if (!this._showOptionButtons) { - this.caseSensitive.domNode.style.display = 'none'; - this.wholeWords.domNode.style.display = 'none'; - this.regex.domNode.style.display = 'none'; - } - - for (const toggle of options?.additionalToggles ?? []) { - this._register(toggle); - this.controls.appendChild(toggle.domNode); - - this._register(toggle.onChange(viaKeyboard => { + if (this.showCommonFindToggles) { + this.regex = this._register(new RegexToggle({ + appendTitle: appendRegexLabel, + isChecked: false, + ...options.toggleStyles + })); + this._register(this.regex.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { this.inputBox.focus(); } + this.validate(); + })); + this._register(this.regex.onKeyDown(e => { + this._onRegexKeyDown.fire(e); })); - this.additionalToggles.push(toggle); + this.wholeWords = this._register(new WholeWordsToggle({ + appendTitle: appendWholeWordsLabel, + isChecked: false, + ...options.toggleStyles + })); + this._register(this.wholeWords.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + + this.caseSensitive = this._register(new CaseSensitiveToggle({ + appendTitle: appendCaseSensitiveLabel, + isChecked: false, + ...options.toggleStyles + })); + this._register(this.caseSensitive.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + this.validate(); + })); + this._register(this.caseSensitive.onKeyDown(e => { + this._onCaseSensitiveKeyDown.fire(e); + })); + + // Arrow-Key support to navigate between options + const indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; + this.onkeydown(this.domNode, (event: IKeyboardEvent) => { + if (event.equals(KeyCode.LeftArrow) || event.equals(KeyCode.RightArrow) || event.equals(KeyCode.Escape)) { + const index = indexes.indexOf(<HTMLElement>document.activeElement); + if (index >= 0) { + let newIndex: number = -1; + if (event.equals(KeyCode.RightArrow)) { + newIndex = (index + 1) % indexes.length; + } else if (event.equals(KeyCode.LeftArrow)) { + if (index === 0) { + newIndex = indexes.length - 1; + } else { + newIndex = index - 1; + } + } + + if (event.equals(KeyCode.Escape)) { + indexes[index].blur(); + this.inputBox.focus(); + } else if (newIndex >= 0) { + indexes[newIndex].focus(); + } + + dom.EventHelper.stop(event, true); + } + } + }); } - if (this.additionalToggles.length > 0) { - this.controls.style.display = 'block'; + this.controls = document.createElement('div'); + this.controls.className = 'controls'; + this.controls.style.display = this.showCommonFindToggles ? '' : 'none'; + if (this.caseSensitive) { + this.controls.append(this.caseSensitive.domNode); + } + if (this.wholeWords) { + this.controls.appendChild(this.wholeWords.domNode); + } + if (this.regex) { + this.controls.appendChild(this.regex.domNode); } - this.inputBox.paddingRight = - (this._showOptionButtons ? this.caseSensitive.width() + this.wholeWords.width() + this.regex.width() : 0) - + this.additionalToggles.reduce((r, t) => r + t.width(), 0); + this.setAdditionalToggles(options?.additionalToggles); - this.domNode.appendChild(this.controls); + if (this.controls) { + this.domNode.appendChild(this.controls); + } parent?.appendChild(this.domNode); @@ -302,12 +232,17 @@ export class FindInput extends Widget { return this.inputBox.onDidChange; } + public layout(style: { collapsedFindWidget: boolean; narrowFindWidget: boolean; reducedFindWidget: boolean }) { + this.inputBox.layout(); + this.updateInputBoxPadding(style.collapsedFindWidget); + } + public enable(): void { this.domNode.classList.remove('disabled'); this.inputBox.enable(); - this.regex.enable(); - this.wholeWords.enable(); - this.caseSensitive.enable(); + this.regex?.enable(); + this.wholeWords?.enable(); + this.caseSensitive?.enable(); for (const toggle of this.additionalToggles) { toggle.enable(); @@ -317,9 +252,9 @@ export class FindInput extends Widget { public disable(): void { this.domNode.classList.add('disabled'); this.inputBox.disable(); - this.regex.disable(); - this.wholeWords.disable(); - this.caseSensitive.disable(); + this.regex?.disable(); + this.wholeWords?.disable(); + this.caseSensitive?.disable(); for (const toggle of this.additionalToggles) { toggle.disable(); @@ -338,6 +273,45 @@ export class FindInput extends Widget { } } + public setAdditionalToggles(toggles: Toggle[] | undefined): void { + for (const currentToggle of this.additionalToggles) { + currentToggle.domNode.remove(); + } + this.additionalToggles = []; + this.additionalTogglesDisposables.dispose(); + this.additionalTogglesDisposables = new DisposableStore(); + + for (const toggle of toggles ?? []) { + this.additionalTogglesDisposables.add(toggle); + this.controls.appendChild(toggle.domNode); + + this.additionalTogglesDisposables.add(toggle.onChange(viaKeyboard => { + this._onDidOptionChange.fire(viaKeyboard); + if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { + this.inputBox.focus(); + } + })); + + this.additionalToggles.push(toggle); + } + + if (this.additionalToggles.length > 0) { + this.controls.style.display = ''; + } + + this.updateInputBoxPadding(); + } + + private updateInputBoxPadding(controlsHidden = false) { + if (controlsHidden) { + this.inputBox.paddingRight = 0; + } else { + this.inputBox.paddingRight = + ((this.caseSensitive?.width() ?? 0) + (this.wholeWords?.width() ?? 0) + (this.regex?.width() ?? 0)) + + this.additionalToggles.reduce((r, t) => r + t.width(), 0); + } + } + public clear(): void { this.clearValidation(); this.setValue(''); @@ -358,60 +332,6 @@ export class FindInput extends Widget { this.inputBox.addToHistory(); } - public style(styles: IFindInputStyles): void { - this.inputActiveOptionBorder = styles.inputActiveOptionBorder; - this.inputActiveOptionForeground = styles.inputActiveOptionForeground; - this.inputActiveOptionBackground = styles.inputActiveOptionBackground; - this.inputBackground = styles.inputBackground; - this.inputForeground = styles.inputForeground; - this.inputBorder = styles.inputBorder; - - this.inputValidationInfoBackground = styles.inputValidationInfoBackground; - this.inputValidationInfoForeground = styles.inputValidationInfoForeground; - this.inputValidationInfoBorder = styles.inputValidationInfoBorder; - this.inputValidationWarningBackground = styles.inputValidationWarningBackground; - this.inputValidationWarningForeground = styles.inputValidationWarningForeground; - this.inputValidationWarningBorder = styles.inputValidationWarningBorder; - this.inputValidationErrorBackground = styles.inputValidationErrorBackground; - this.inputValidationErrorForeground = styles.inputValidationErrorForeground; - this.inputValidationErrorBorder = styles.inputValidationErrorBorder; - - this.applyStyles(); - } - - protected applyStyles(): void { - if (this.domNode) { - const toggleStyles: IToggleStyles = { - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground, - }; - this.regex.style(toggleStyles); - this.wholeWords.style(toggleStyles); - this.caseSensitive.style(toggleStyles); - - for (const toggle of this.additionalToggles) { - toggle.style(toggleStyles); - } - - const inputBoxStyles: IInputBoxStyles = { - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder - }; - this.inputBox.style(inputBoxStyles); - } - } - public select(): void { this.inputBox.select(); } @@ -421,36 +341,42 @@ export class FindInput extends Widget { } public getCaseSensitive(): boolean { - return this.caseSensitive.checked; + return this.caseSensitive?.checked ?? false; } public setCaseSensitive(value: boolean): void { - this.caseSensitive.checked = value; + if (this.caseSensitive) { + this.caseSensitive.checked = value; + } } public getWholeWords(): boolean { - return this.wholeWords.checked; + return this.wholeWords?.checked ?? false; } public setWholeWords(value: boolean): void { - this.wholeWords.checked = value; + if (this.wholeWords) { + this.wholeWords.checked = value; + } } public getRegex(): boolean { - return this.regex.checked; + return this.regex?.checked ?? false; } public setRegex(value: boolean): void { - this.regex.checked = value; - this.validate(); + if (this.regex) { + this.regex.checked = value; + this.validate(); + } } public focusOnCaseSensitive(): void { - this.caseSensitive.focus(); + this.caseSensitive?.focus(); } public focusOnRegex(): void { - this.regex.focus(); + this.regex?.focus(); } private _lastHighlightFindOptions: number = 0; diff --git a/src/vs/base/browser/ui/findinput/findInputToggles.ts b/src/vs/base/browser/ui/findinput/findInputToggles.ts index 36268b557c..ba961f4626 100644 --- a/src/vs/base/browser/ui/findinput/findInputToggles.ts +++ b/src/vs/base/browser/ui/findinput/findInputToggles.ts @@ -5,15 +5,14 @@ import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import * as nls from 'vs/nls'; export interface IFindInputToggleOpts { readonly appendTitle: string; readonly isChecked: boolean; - readonly inputActiveOptionBorder?: Color; - readonly inputActiveOptionForeground?: Color; - readonly inputActiveOptionBackground?: Color; + readonly inputActiveOptionBorder: string | undefined; + readonly inputActiveOptionForeground: string | undefined; + readonly inputActiveOptionBackground: string | undefined; } const NLS_CASE_SENSITIVE_TOGGLE_LABEL = nls.localize('caseDescription', "Match Case"); diff --git a/src/vs/base/browser/ui/findinput/replaceInput.ts b/src/vs/base/browser/ui/findinput/replaceInput.ts index ae34d42d37..970b962535 100644 --- a/src/vs/base/browser/ui/findinput/replaceInput.ts +++ b/src/vs/base/browser/ui/findinput/replaceInput.ts @@ -6,20 +6,19 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; -import { Toggle, IToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; +import { IToggleStyles, Toggle } from 'vs/base/browser/ui/toggle/toggle'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IFindInputToggleOpts } from 'vs/base/browser/ui/findinput/findInputToggles'; import { HistoryInputBox, IInputBoxStyles, IInputValidator, IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox'; import { Widget } from 'vs/base/browser/ui/widget'; import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./findInput'; import * as nls from 'vs/nls'; -export interface IReplaceInputOptions extends IReplaceInputStyles { +export interface IReplaceInputOptions { readonly placeholder?: string; readonly width?: number; readonly validation?: IInputValidator; @@ -31,18 +30,14 @@ export interface IReplaceInputOptions extends IReplaceInputStyles { readonly appendPreserveCaseLabel?: string; readonly history?: string[]; readonly showHistoryHint?: () => boolean; -} - -export interface IReplaceInputStyles extends IInputBoxStyles { - inputActiveOptionBorder?: Color; - inputActiveOptionForeground?: Color; - inputActiveOptionBackground?: Color; + readonly inputBoxStyles: IInputBoxStyles; + readonly toggleStyles: IToggleStyles; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); const NLS_PRESERVE_CASE_LABEL = nls.localize('label.preserveCaseToggle', "Preserve Case"); -export class PreserveCaseToggle extends Toggle { +class PreserveCaseToggle extends Toggle { constructor(opts: IFindInputToggleOpts) { super({ // TODO: does this need its own icon? @@ -66,23 +61,6 @@ export class ReplaceInput extends Widget { private label: string; private fixFocusOnOptionClickEnabled = true; - private inputActiveOptionBorder?: Color; - private inputActiveOptionForeground?: Color; - private inputActiveOptionBackground?: Color; - private inputBackground?: Color; - private inputForeground?: Color; - private inputBorder?: Color; - - private inputValidationInfoBorder?: Color; - private inputValidationInfoBackground?: Color; - private inputValidationInfoForeground?: Color; - private inputValidationWarningBorder?: Color; - private inputValidationWarningBackground?: Color; - private inputValidationWarningForeground?: Color; - private inputValidationErrorBorder?: Color; - private inputValidationErrorBackground?: Color; - private inputValidationErrorForeground?: Color; - private preserveCase: PreserveCaseToggle; private cachedOptionsWidth: number = 0; public domNode: HTMLElement; @@ -113,23 +91,6 @@ export class ReplaceInput extends Widget { this.validation = options.validation; this.label = options.label || NLS_DEFAULT_LABEL; - this.inputActiveOptionBorder = options.inputActiveOptionBorder; - this.inputActiveOptionForeground = options.inputActiveOptionForeground; - this.inputActiveOptionBackground = options.inputActiveOptionBackground; - this.inputBackground = options.inputBackground; - this.inputForeground = options.inputForeground; - this.inputBorder = options.inputBorder; - - this.inputValidationInfoBorder = options.inputValidationInfoBorder; - this.inputValidationInfoBackground = options.inputValidationInfoBackground; - this.inputValidationInfoForeground = options.inputValidationInfoForeground; - this.inputValidationWarningBorder = options.inputValidationWarningBorder; - this.inputValidationWarningBackground = options.inputValidationWarningBackground; - this.inputValidationWarningForeground = options.inputValidationWarningForeground; - this.inputValidationErrorBorder = options.inputValidationErrorBorder; - this.inputValidationErrorBackground = options.inputValidationErrorBackground; - this.inputValidationErrorForeground = options.inputValidationErrorForeground; - const appendPreserveCaseLabel = options.appendPreserveCaseLabel || ''; const history = options.history || []; const flexibleHeight = !!options.flexibleHeight; @@ -145,31 +106,18 @@ export class ReplaceInput extends Widget { validationOptions: { validation: this.validation }, - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder, history, showHistoryHint: options.showHistoryHint, flexibleHeight, flexibleWidth, - flexibleMaxHeight + flexibleMaxHeight, + inputBoxStyles: options.inputBoxStyles })); this.preserveCase = this._register(new PreserveCaseToggle({ appendTitle: appendPreserveCaseLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground, + ...options.toggleStyles })); this._register(this.preserveCase.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); @@ -277,52 +225,7 @@ export class ReplaceInput extends Widget { this.inputBox.addToHistory(); } - public style(styles: IReplaceInputStyles): void { - this.inputActiveOptionBorder = styles.inputActiveOptionBorder; - this.inputActiveOptionForeground = styles.inputActiveOptionForeground; - this.inputActiveOptionBackground = styles.inputActiveOptionBackground; - this.inputBackground = styles.inputBackground; - this.inputForeground = styles.inputForeground; - this.inputBorder = styles.inputBorder; - - this.inputValidationInfoBackground = styles.inputValidationInfoBackground; - this.inputValidationInfoForeground = styles.inputValidationInfoForeground; - this.inputValidationInfoBorder = styles.inputValidationInfoBorder; - this.inputValidationWarningBackground = styles.inputValidationWarningBackground; - this.inputValidationWarningForeground = styles.inputValidationWarningForeground; - this.inputValidationWarningBorder = styles.inputValidationWarningBorder; - this.inputValidationErrorBackground = styles.inputValidationErrorBackground; - this.inputValidationErrorForeground = styles.inputValidationErrorForeground; - this.inputValidationErrorBorder = styles.inputValidationErrorBorder; - - this.applyStyles(); - } - protected applyStyles(): void { - if (this.domNode) { - const toggleStyles: IToggleStyles = { - inputActiveOptionBorder: this.inputActiveOptionBorder, - inputActiveOptionForeground: this.inputActiveOptionForeground, - inputActiveOptionBackground: this.inputActiveOptionBackground, - }; - this.preserveCase.style(toggleStyles); - - const inputBoxStyles: IInputBoxStyles = { - inputBackground: this.inputBackground, - inputForeground: this.inputForeground, - inputBorder: this.inputBorder, - inputValidationInfoBackground: this.inputValidationInfoBackground, - inputValidationInfoForeground: this.inputValidationInfoForeground, - inputValidationInfoBorder: this.inputValidationInfoBorder, - inputValidationWarningBackground: this.inputValidationWarningBackground, - inputValidationWarningForeground: this.inputValidationWarningForeground, - inputValidationWarningBorder: this.inputValidationWarningBorder, - inputValidationErrorBackground: this.inputValidationErrorBackground, - inputValidationErrorForeground: this.inputValidationErrorForeground, - inputValidationErrorBorder: this.inputValidationErrorBorder - }; - this.inputBox.style(inputBoxStyles); - } } public select(): void { @@ -353,9 +256,7 @@ export class ReplaceInput extends Widget { } public validate(): void { - if (this.inputBox) { - this.inputBox.validate(); - } + this.inputBox?.validate(); } public showMessage(message: InputBoxMessage): void { @@ -363,20 +264,15 @@ export class ReplaceInput extends Widget { } public clearMessage(): void { - if (this.inputBox) { - this.inputBox.hideMessage(); - } + this.inputBox?.hideMessage(); } private clearValidation(): void { - if (this.inputBox) { - this.inputBox.hideMessage(); - } + this.inputBox?.hideMessage(); } public set width(newWidth: number) { this.inputBox.paddingRight = this.cachedOptionsWidth; - this.inputBox.width = newWidth; this.domNode.style.width = newWidth + 'px'; } diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 5c00b23928..c773c0ba41 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -3,12 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { IBoundarySashes, Orientation } from 'vs/base/browser/ui/sash/sash'; import { equals, tail2 as tail } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import 'vs/css!./gridview'; -import { Box, GridView, IBoundarySashes, IGridViewOptions, IGridViewStyles, IView as IGridViewView, IViewSize, orthogonal, Sizing as GridViewSizing } from './gridview'; +import { Box, GridView, IGridViewOptions, IGridViewStyles, IView as IGridViewView, IViewSize, orthogonal, Sizing as GridViewSizing } from './gridview'; import type { GridLocation } from 'vs/base/browser/ui/grid/gridview'; ///@ts-ignore import type { SplitView } from 'vs/base/browser/ui/splitview/splitview'; diff --git a/src/vs/base/browser/ui/grid/gridview.ts b/src/vs/base/browser/ui/grid/gridview.ts index 47d83d510c..d31c777f2f 100644 --- a/src/vs/base/browser/ui/grid/gridview.ts +++ b/src/vs/base/browser/ui/grid/gridview.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { $ } from 'vs/base/browser/dom'; -import { Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; +import { IBoundarySashes, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; import { DistributeSizing, ISplitViewStyles, IView as ISplitView, LayoutPriority, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { equals as arrayEquals, tail2 as tail } from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; @@ -35,13 +35,6 @@ interface IRelativeBoundarySashes { readonly orthogonalEnd?: Sash; } -export interface IBoundarySashes { - readonly top?: Sash; - readonly right?: Sash; - readonly bottom?: Sash; - readonly left?: Sash; -} - /** * The interface to implement for views within a {@link GridView}. */ @@ -88,6 +81,14 @@ export interface IView { */ readonly priority?: LayoutPriority; + /** + * If the {@link GridView} supports proportional layout, + * this property allows for finer control over the proportional layout algorithm, per view. + * + * @defaultValue `true` + */ + readonly proportionalLayout?: boolean; + /** * Whether the view will snap whenever the user reaches its minimum size or * attempts to grow it beyond the minimum size. @@ -255,8 +256,12 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable { private _orthogonalSize: number; get orthogonalSize(): number { return this._orthogonalSize; } - private absoluteOffset: number = 0; - private absoluteOrthogonalOffset: number = 0; + private _absoluteOffset: number = 0; + get absoluteOffset(): number { return this._absoluteOffset; } + + private _absoluteOrthogonalOffset: number = 0; + get absoluteOrthogonalOffset(): number { return this._absoluteOrthogonalOffset; } + private absoluteOrthogonalSize: number = 0; private _styles: IGridViewStyles; @@ -271,11 +276,11 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable { } get top(): number { - return this.orientation === Orientation.HORIZONTAL ? this.absoluteOffset : this.absoluteOrthogonalOffset; + return this.orientation === Orientation.HORIZONTAL ? this._absoluteOffset : this._absoluteOrthogonalOffset; } get left(): number { - return this.orientation === Orientation.HORIZONTAL ? this.absoluteOrthogonalOffset : this.absoluteOffset; + return this.orientation === Orientation.HORIZONTAL ? this._absoluteOrthogonalOffset : this._absoluteOffset; } get minimumSize(): number { @@ -302,6 +307,14 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable { return LayoutPriority.Normal; } + get proportionalLayout(): boolean { + if (this.children.length === 0) { + return true; + } + + return this.children.every(c => c.proportionalLayout); + } + get minimumOrthogonalSize(): number { return this.splitview.minimumSize; } @@ -384,7 +397,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable { readonly orientation: Orientation, readonly layoutController: LayoutController, styles: IGridViewStyles, - readonly proportionalLayout: boolean, + readonly splitviewProportionalLayout: boolean, size: number = 0, orthogonalSize: number = 0, edgeSnapping: boolean = false, @@ -398,7 +411,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable { if (!childDescriptors) { // Normal behavior, we have no children yet, just set up the splitview - this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout }); + this.splitview = new SplitView(this.element, { orientation, styles, proportionalLayout: splitviewProportionalLayout }); this.splitview.layout(size, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize }); } else { // Reconstruction behavior, we want to reconstruct a splitview @@ -413,7 +426,7 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable { size: this.orthogonalSize }; - const options = { proportionalLayout, orientation, styles }; + const options = { proportionalLayout: splitviewProportionalLayout, orientation, styles }; this.children = childDescriptors.map(c => c.node); this.splitview = new SplitView(this.element, { ...options, descriptor }); @@ -460,14 +473,14 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable { // branch nodes should flip the normal/orthogonal directions this._size = ctx.orthogonalSize; this._orthogonalSize = size; - this.absoluteOffset = ctx.absoluteOffset + offset; - this.absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset; + this._absoluteOffset = ctx.absoluteOffset + offset; + this._absoluteOrthogonalOffset = ctx.absoluteOrthogonalOffset; this.absoluteOrthogonalSize = ctx.absoluteOrthogonalSize; this.splitview.layout(ctx.orthogonalSize, { orthogonalSize: size, - absoluteOffset: this.absoluteOrthogonalOffset, - absoluteOrthogonalOffset: this.absoluteOffset, + absoluteOffset: this._absoluteOrthogonalOffset, + absoluteOrthogonalOffset: this._absoluteOffset, absoluteSize: ctx.absoluteOrthogonalSize, absoluteOrthogonalSize: ctx.absoluteSize }); @@ -705,8 +718,8 @@ class BranchNode implements ISplitView<ILayoutContext>, IDisposable { } private updateSplitviewEdgeSnappingEnablement(): void { - this.splitview.startSnappingEnabled = this._edgeSnapping || this.absoluteOrthogonalOffset > 0; - this.splitview.endSnappingEnabled = this._edgeSnapping || this.absoluteOrthogonalOffset + this._size < this.absoluteOrthogonalSize; + this.splitview.startSnappingEnabled = this._edgeSnapping || this._absoluteOrthogonalOffset > 0; + this.splitview.endSnappingEnabled = this._edgeSnapping || this._absoluteOrthogonalOffset + this._size < this.absoluteOrthogonalSize; } dispose(): void { @@ -844,6 +857,10 @@ class LeafNode implements ISplitView<ILayoutContext>, IDisposable { return this.view.priority; } + get proportionalLayout(): boolean { + return this.view.proportionalLayout ?? true; + } + get snap(): boolean | undefined { return this.view.snap; } @@ -916,7 +933,7 @@ export interface INodeDescriptor { function flipNode<T extends Node>(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, node.edgeSnapping); + const result = new BranchNode(orthogonal(node.orientation), node.layoutController, node.styles, node.splitviewProportionalLayout, size, orthogonalSize, node.edgeSnapping); let totalSize = 0; @@ -1097,9 +1114,9 @@ export class GridView implements IDisposable { return; } - const { size, orthogonalSize } = this._root; + const { size, orthogonalSize, absoluteOffset, absoluteOrthogonalOffset } = this._root; this.root = flipNode(this._root, orthogonalSize, size); - this.root.layout(size, 0, { orthogonalSize, absoluteOffset: 0, absoluteOrthogonalOffset: 0, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize }); + this.root.layout(size, 0, { orthogonalSize, absoluteOffset: absoluteOrthogonalOffset, absoluteOrthogonalOffset: absoluteOffset, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize }); this.boundarySashes = this.boundarySashes; } @@ -1153,7 +1170,7 @@ export class GridView implements IDisposable { this.layoutController.isLayoutEnabled = true; const [size, orthogonalSize, offset, orthogonalOffset] = this.root.orientation === Orientation.HORIZONTAL ? [height, width, top, left] : [width, height, left, top]; - this.root.layout(size, offset, { orthogonalSize, absoluteOffset: offset, absoluteOrthogonalOffset: orthogonalOffset, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize }); + this.root.layout(size, 0, { orthogonalSize, absoluteOffset: offset, absoluteOrthogonalOffset: orthogonalOffset, absoluteSize: size, absoluteOrthogonalSize: orthogonalSize }); } /** diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index f5fcd52e28..866c83cd30 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -13,13 +13,13 @@ import * as objects from 'vs/base/common/objects'; export interface IHighlight { start: number; end: number; - extraClasses?: string[]; + readonly extraClasses?: readonly string[]; } -export interface IOptions { +export interface IHighlightedLabelOptions { /** - * Whether + * Whether the label supports rendering icons. */ readonly supportIcons?: boolean; } @@ -33,7 +33,7 @@ export class HighlightedLabel { private readonly domNode: HTMLElement; private text: string = ''; private title: string = ''; - private highlights: IHighlight[] = []; + private highlights: readonly IHighlight[] = []; private supportIcons: boolean; private didEverRender: boolean = false; @@ -42,7 +42,7 @@ export class HighlightedLabel { * * @param container The parent container to append to. */ - constructor(container: HTMLElement, options?: IOptions) { + constructor(container: HTMLElement, options?: IHighlightedLabelOptions) { this.supportIcons = options?.supportIcons ?? false; this.domNode = dom.append(container, dom.$('span.monaco-highlighted-label')); } @@ -63,7 +63,7 @@ export class HighlightedLabel { * @param escapeNewLines Whether to escape new lines. * @returns */ - set(text: string | undefined, highlights: IHighlight[] = [], title: string = '', escapeNewLines?: boolean) { + set(text: string | undefined, highlights: readonly IHighlight[] = [], title: string = '', escapeNewLines?: boolean) { if (!text) { text = ''; } @@ -85,7 +85,7 @@ export class HighlightedLabel { private render(): void { - const children: HTMLSpanElement[] = []; + const children: Array<HTMLSpanElement | string> = []; let pos = 0; for (const highlight of this.highlights) { @@ -95,11 +95,15 @@ export class HighlightedLabel { if (pos < highlight.start) { const substring = this.text.substring(pos, highlight.start); - children.push(dom.$('span', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring])); - pos = highlight.end; + if (this.supportIcons) { + children.push(...renderLabelWithIcons(substring)); + } else { + children.push(substring); + } + pos = highlight.start; } - const substring = this.text.substring(highlight.start, highlight.end); + const substring = this.text.substring(pos, highlight.end); const element = dom.$('span.highlight', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring]); if (highlight.extraClasses) { @@ -112,7 +116,11 @@ export class HighlightedLabel { if (pos < this.text.length) { const substring = this.text.substring(pos,); - children.push(dom.$('span', undefined, ...this.supportIcons ? renderLabelWithIcons(substring) : [substring])); + if (this.supportIcons) { + children.push(...renderLabelWithIcons(substring)); + } else { + children.push(substring); + } } dom.reset(this.domNode, ...children); @@ -126,7 +134,7 @@ export class HighlightedLabel { this.didEverRender = true; } - static escapeNewLines(text: string, highlights: IHighlight[]): string { + static escapeNewLines(text: string, highlights: readonly IHighlight[]): string { let total = 0; let extra = 0; diff --git a/src/vs/base/browser/ui/hover/hover.css b/src/vs/base/browser/ui/hover/hover.css index a3c34caaf9..e4a11a5925 100644 --- a/src/vs/base/browser/ui/hover/hover.css +++ b/src/vs/base/browser/ui/hover/hover.css @@ -10,7 +10,6 @@ z-index: 50; user-select: text; -webkit-user-select: text; - -ms-user-select: text; box-sizing: initial; animation: fadein 100ms linear; line-height: 1.5em; @@ -20,7 +19,7 @@ display: none; } -.monaco-hover a:hover { +.monaco-hover a:hover:not(.disabled) { cursor: pointer; } @@ -39,10 +38,25 @@ .monaco-hover p, .monaco-hover .code, -.monaco-hover ul { +.monaco-hover ul, +.monaco-hover h1, +.monaco-hover h2, +.monaco-hover h3, +.monaco-hover h4, +.monaco-hover h5, +.monaco-hover h6 { margin: 8px 0; } +.monaco-hover h1, +.monaco-hover h2, +.monaco-hover h3, +.monaco-hover h4, +.monaco-hover h5, +.monaco-hover h6 { + line-height: 1.1; +} + .monaco-hover code { font-family: var(--monaco-monospace-font); } @@ -100,6 +114,11 @@ line-height: 22px; } +.monaco-hover .hover-row.status-bar .info { + font-style: italic; + padding: 0px 8px; +} + .monaco-hover .hover-row.status-bar .actions { display: flex; padding: 0px 8px; @@ -138,6 +157,11 @@ /** Hack to force underline to show **/ border-bottom: 1px solid transparent; text-underline-position: under; + color: var(--vscode-textLink-foreground); +} + +.monaco-hover .hover-contents a.code-link > span:hover { + color: var(--vscode-textLink-activeForeground); } /** Spans in markdown hovers need a margin-bottom to avoid looking cramped: https://github.com/microsoft/vscode/issues/101496 **/ diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index f21c1890b1..4e1f9c09db 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -72,9 +72,9 @@ export class HoverAction extends Disposable { actionOptions.run(this.actionContainer); })); - this._register(dom.addDisposableListener(this.actionContainer, dom.EventType.KEY_UP, e => { + this._register(dom.addDisposableListener(this.actionContainer, dom.EventType.KEY_DOWN, e => { const event = new StandardKeyboardEvent(e); - if (event.equals(KeyCode.Enter)) { + if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { e.stopPropagation(); e.preventDefault(); actionOptions.run(this.actionContainer); diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 738047d69d..b4b8a3fbec 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -14,11 +14,40 @@ export interface IHoverDelegateTarget extends IDisposable { } export interface IHoverDelegateOptions extends IUpdatableHoverOptions { + /** + * The content to display in the primary section of the hover. The type of text determines the + * default `hideOnHover` behavior. + */ content: IMarkdownString | string | HTMLElement; + /** + * The target for the hover. This determines the position of the hover and it will only be + * hidden when the mouse leaves both the hover and the target. A HTMLElement can be used for + * simple cases and a IHoverDelegateTarget for more complex cases where multiple elements and/or a + * dispose method is required. + */ target: IHoverDelegateTarget | HTMLElement; + /** + * Position of the hover. The default is to show above the target. This option will be ignored + * if there is not enough room to layout the hover in the specified position, unless the + * forcePosition option is set. + */ hoverPosition?: HoverPosition; + /** + * Whether to show the hover pointer + */ showPointer?: boolean; + /** + * Whether to skip the fade in animation, this should be used when hovering from one hover to + * another in the same group so it looks like the hover is moving from one element to the other. + */ skipFadeInAnimation?: boolean; + /** + * The container to pass to {@link IContextViewProvider.showContextView} which renders the hover + * in. This is particularly useful for more natural tab focusing behavior, where the hover is + * created as the next tab index after the element being hovered and/or to workaround the + * element's container hiding on `focusout`. + */ + container?: HTMLElement; } export interface IHoverDelegate { diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index ee8084f585..3610b6929f 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -14,22 +14,23 @@ import { equals } from 'vs/base/common/objects'; import { Range } from 'vs/base/common/range'; export interface IIconLabelCreationOptions { - supportHighlights?: boolean; - supportDescriptionHighlights?: boolean; - supportIcons?: boolean; - hoverDelegate?: IHoverDelegate; + readonly supportHighlights?: boolean; + readonly supportDescriptionHighlights?: boolean; + readonly supportIcons?: boolean; + readonly hoverDelegate?: IHoverDelegate; } export interface IIconLabelValueOptions { title?: string | ITooltipMarkdownString; descriptionTitle?: string; hideIcon?: boolean; - extraClasses?: string[]; + extraClasses?: readonly string[]; italic?: boolean; strikethrough?: boolean; - matches?: IMatch[]; + matches?: readonly IMatch[]; labelEscapeNewLines?: boolean; - descriptionMatches?: IMatch[]; + descriptionMatches?: readonly IMatch[]; + disabledCommand?: boolean; readonly separator?: string; readonly domId?: string; } @@ -81,13 +82,12 @@ class FastLabelNode { export class IconLabel extends Disposable { - private readonly domNode: FastLabelNode; + private readonly creationOptions?: IIconLabelCreationOptions; + private readonly domNode: FastLabelNode; private readonly nameNode: Label | LabelWithHighlights; - private readonly descriptionContainer: FastLabelNode; private descriptionNode: FastLabelNode | HighlightedLabel | undefined; - private readonly descriptionNodeFactory: () => FastLabelNode | HighlightedLabel; private readonly labelContainer: HTMLElement; @@ -96,13 +96,13 @@ export class IconLabel extends Disposable { constructor(container: HTMLElement, options?: IIconLabelCreationOptions) { super(); + this.creationOptions = options; this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label')))); this.labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-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 || options?.supportIcons) { this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportIcons); @@ -110,12 +110,6 @@ export class IconLabel extends Disposable { this.nameNode = new Label(nameContainer); } - if (options?.supportDescriptionHighlights) { - this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!options.supportIcons }); - } else { - this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description')))); - } - this.hoverDelegate = options?.hoverDelegate; } @@ -124,38 +118,41 @@ export class IconLabel extends Disposable { } setLabel(label: string | string[], description?: string, options?: IIconLabelValueOptions): void { - const classes = ['monaco-icon-label']; + const labelClasses = ['monaco-icon-label']; + const containerClasses = ['monaco-icon-label-container']; if (options) { if (options.extraClasses) { - classes.push(...options.extraClasses); + labelClasses.push(...options.extraClasses); } if (options.italic) { - classes.push('italic'); + labelClasses.push('italic'); } if (options.strikethrough) { - classes.push('strikethrough'); + labelClasses.push('strikethrough'); + } + + if (options.disabledCommand) { + containerClasses.push('disabled'); } } - this.domNode.className = classes.join(' '); + this.domNode.className = labelClasses.join(' '); + this.labelContainer.className = containerClasses.join(' '); this.setupHover(options?.descriptionTitle ? this.labelContainer : this.element, options?.title); this.nameNode.setLabel(label, options); if (description || this.descriptionNode) { - if (!this.descriptionNode) { - this.descriptionNode = this.descriptionNodeFactory(); // description node is created lazily on demand - } - - if (this.descriptionNode instanceof HighlightedLabel) { - this.descriptionNode.set(description || '', options ? options.descriptionMatches : undefined); - this.setupHover(this.descriptionNode.element, options?.descriptionTitle); + const descriptionNode = this.getOrCreateDescriptionNode(); + if (descriptionNode instanceof HighlightedLabel) { + descriptionNode.set(description || '', options ? options.descriptionMatches : undefined, undefined, options?.labelEscapeNewLines); + this.setupHover(descriptionNode.element, options?.descriptionTitle); } else { - this.descriptionNode.textContent = description || ''; - this.setupHover(this.descriptionNode.element, options?.descriptionTitle || ''); - this.descriptionNode.empty = !description; + descriptionNode.textContent = description && options?.labelEscapeNewLines ? HighlightedLabel.escapeNewLines(description, []) : (description || ''); + this.setupHover(descriptionNode.element, options?.descriptionTitle || ''); + descriptionNode.empty = !description; } } } @@ -189,6 +186,19 @@ export class IconLabel extends Disposable { } this.customHovers.clear(); } + + private getOrCreateDescriptionNode() { + if (!this.descriptionNode) { + const descriptionContainer = this._register(new FastLabelNode(dom.append(this.labelContainer, dom.$('span.monaco-icon-description-container')))); + if (this.creationOptions?.supportDescriptionHighlights) { + this.descriptionNode = new HighlightedLabel(dom.append(descriptionContainer.element, dom.$('span.label-description')), { supportIcons: !!this.creationOptions.supportIcons }); + } else { + this.descriptionNode = this._register(new FastLabelNode(dom.append(descriptionContainer.element, dom.$('span.label-description')))); + } + } + + return this.descriptionNode; + } } class Label { @@ -234,7 +244,7 @@ class Label { } } -function splitMatches(labels: string[], separator: string, matches: IMatch[] | undefined): IMatch[][] | undefined { +function splitMatches(labels: string[], separator: string, matches: readonly IMatch[] | undefined): IMatch[][] | undefined { if (!matches) { return undefined; } diff --git a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts index 3079fa99f6..850e41a8ea 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabelHover.ts @@ -30,14 +30,14 @@ export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | ITo } } -export type IHoverContent = string | ITooltipMarkdownString | HTMLElement | undefined; +type IHoverContent = string | ITooltipMarkdownString | HTMLElement | undefined; type IResolvedHoverContent = IMarkdownString | string | HTMLElement | undefined; /** * Copied from src\vs\workbench\services\hover\browser\hover.ts * @deprecated Use IHoverService */ -export interface IHoverAction { +interface IHoverAction { label: string; commandId: string; iconClass?: string; @@ -164,6 +164,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM let hoverWidget: UpdatableHoverWidget | undefined; const hideHover = (disposeWidget: boolean, disposePreparation: boolean) => { + const hadHover = hoverWidget !== undefined; if (disposeWidget) { hoverWidget?.dispose(); hoverWidget = undefined; @@ -172,7 +173,9 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM hoverPreparation?.dispose(); hoverPreparation = undefined; } - hoverDelegate.onDidHideHover?.(); + if (hadHover) { + hoverDelegate.onDidHideHover?.(); + } }; const triggerShowHover = (delay: number, focus?: boolean, target?: IHoverDelegateTarget) => { diff --git a/src/vs/base/browser/ui/iconLabel/iconLabels.ts b/src/vs/base/browser/ui/iconLabel/iconLabels.ts index 001d1fe956..7c3c96f4b3 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabels.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabels.ts @@ -4,17 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { CSSIcon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; -const labelWithIconsRegex = new RegExp(`(\\\\)?\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)`, 'g'); +const labelWithIconsRegex = new RegExp(`(\\\\)?\\$\\((${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?)\\)`, 'g'); export function renderLabelWithIcons(text: string): Array<HTMLSpanElement | string> { const elements = new Array<HTMLSpanElement | string>(); - let match: RegExpMatchArray | null; + let match: RegExpExecArray | null; let textStart = 0, textStop = 0; while ((match = labelWithIconsRegex.exec(text)) !== null) { textStop = match.index || 0; - elements.push(text.substring(textStart, textStop)); + if (textStart < textStop) { + elements.push(text.substring(textStart, textStop)); + } textStart = (match.index || 0) + match[0].length; const [, escaped, codicon] = match; @@ -27,8 +29,8 @@ export function renderLabelWithIcons(text: string): Array<HTMLSpanElement | stri return elements; } -export function renderIcon(icon: CSSIcon): HTMLSpanElement { +export function renderIcon(icon: ThemeIcon): HTMLSpanElement { const node = dom.$(`span`); - node.classList.add(...CSSIcon.asClassNameArray(icon)); + node.classList.add(...ThemeIcon.asClassNameArray(icon)); return node; } diff --git a/src/vs/base/browser/ui/iconLabel/iconlabel.css b/src/vs/base/browser/ui/iconLabel/iconlabel.css index a8402fc4ff..0080be6d7b 100644 --- a/src/vs/base/browser/ui/iconLabel/iconlabel.css +++ b/src/vs/base/browser/ui/iconLabel/iconlabel.css @@ -31,6 +31,9 @@ flex-shrink: 0; /* fix for https://github.com/microsoft/vscode/issues/13787 */ } +.monaco-icon-label-container.disabled { + color: var(--vscode-disabledForeground); +} .monaco-icon-label > .monaco-icon-label-container { min-width: 0; overflow: hidden; diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css index 49fae20a71..80c2dfce1b 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.css +++ b/src/vs/base/browser/ui/inputbox/inputBox.css @@ -8,20 +8,17 @@ display: block; padding: 0; box-sizing: border-box; + border-radius: 2px; /* Customizable */ font-size: inherit; } -.monaco-inputbox.idle { - border: 1px solid transparent; -} - .monaco-inputbox > .ibwrapper > .input, .monaco-inputbox > .ibwrapper > .mirror { /* Customizable */ - padding: 4px; + padding: 4px 6px; } .monaco-inputbox > .ibwrapper { @@ -49,7 +46,6 @@ .monaco-inputbox > .ibwrapper > textarea.input { display: block; - -ms-overflow-style: none; /* IE 10+: hide scrollbars */ scrollbar-width: none; /* Firefox: hide scrollbars */ outline: none; } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index 60d2591d4d..9c6c545fb8 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -14,10 +14,9 @@ import { AnchorAlignment, IContextViewProvider } from 'vs/base/browser/ui/contex import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { HistoryNavigator } from 'vs/base/common/history'; -import { mixin } from 'vs/base/common/objects'; +import { equals } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import 'vs/css!./inputBox'; import * as nls from 'vs/nls'; @@ -25,7 +24,7 @@ import * as nls from 'vs/nls'; const $ = dom.$; -export interface IInputOptions extends IInputBoxStyles { +export interface IInputOptions { readonly placeholder?: string; readonly showPlaceholderOnFocus?: boolean; readonly tooltip?: string; @@ -36,6 +35,7 @@ export interface IInputOptions extends IInputBoxStyles { readonly flexibleWidth?: boolean; readonly flexibleMaxHeight?: number; readonly actions?: ReadonlyArray<IAction>; + readonly inputBoxStyles: IInputBoxStyles; // {{SQL CARBON EDIT}} Candidate for addition to vscode @@ -45,18 +45,18 @@ export interface IInputOptions extends IInputBoxStyles { } export interface IInputBoxStyles { - readonly inputBackground?: Color; - readonly inputForeground?: Color; - readonly inputBorder?: Color; - readonly inputValidationInfoBorder?: Color; - readonly inputValidationInfoBackground?: Color; - readonly inputValidationInfoForeground?: Color; - readonly inputValidationWarningBorder?: Color; - readonly inputValidationWarningBackground?: Color; - readonly inputValidationWarningForeground?: Color; - readonly inputValidationErrorBorder?: Color; - readonly inputValidationErrorBackground?: Color; - readonly inputValidationErrorForeground?: Color; + readonly inputBackground: string | undefined; + readonly inputForeground: string | undefined; + readonly inputBorder: string | undefined; + readonly inputValidationInfoBorder: string | undefined; + readonly inputValidationInfoBackground: string | undefined; + readonly inputValidationInfoForeground: string | undefined; + readonly inputValidationWarningBorder: string | undefined; + readonly inputValidationWarningBackground: string | undefined; + readonly inputValidationWarningForeground: string | undefined; + readonly inputValidationErrorBorder: string | undefined; + readonly inputValidationErrorBackground: string | undefined; + readonly inputValidationErrorForeground: string | undefined; } export interface IInputValidator { @@ -64,7 +64,7 @@ export interface IInputValidator { } export interface IMessage { - readonly content: string; + readonly content?: string; readonly formatContent?: boolean; // defaults to false readonly type?: MessageType; } @@ -85,15 +85,19 @@ export interface IRange { } // {{SQL CARBON EDIT}} -export const defaultOpts = { - inputBackground: Color.fromHex('#3C3C3C'), - inputForeground: Color.fromHex('#CCCCCC'), - inputValidationInfoBorder: Color.fromHex('#55AAFF'), - inputValidationInfoBackground: Color.fromHex('#063B49'), - inputValidationWarningBorder: Color.fromHex('#B89500'), - inputValidationWarningBackground: Color.fromHex('#352A05'), - inputValidationErrorBorder: Color.fromHex('#BE1100'), - inputValidationErrorBackground: Color.fromHex('#5A1D1D') +export const unthemedInboxStyles: IInputBoxStyles = { + inputBackground: '#3C3C3C', + inputForeground: '#CCCCCC', + inputValidationInfoBorder: '#55AAFF', + inputValidationInfoBackground: '#063B49', + inputValidationWarningBorder: '#B89500', + inputValidationWarningBackground: '#352A05', + inputValidationErrorBorder: '#BE1100', + inputValidationErrorBackground: '#5A1D1D', + inputBorder: undefined, + inputValidationErrorForeground: undefined, + inputValidationInfoForeground: undefined, + inputValidationWarningForeground: undefined }; export class InputBox extends Widget { @@ -101,7 +105,7 @@ export class InputBox extends Widget { element: HTMLElement; protected input: HTMLInputElement; private actionbar?: ActionBar; - private options: IInputOptions; + private readonly options: IInputOptions; private message: IMessage | null; protected placeholder: string; private tooltip: string; @@ -117,52 +121,24 @@ export class InputBox extends Widget { // {{SQL CARBON EDIT}} - Add showValidationMessage and set inputBackground, inputForeground, and inputBorder as protected protected showValidationMessage?: boolean; - protected inputBackground?: Color; - protected inputForeground?: Color; - protected inputBorder?: Color; // {{SQL CARBON EDIT}} - End - - private inputValidationInfoBorder?: Color; - private inputValidationInfoBackground?: Color; - private inputValidationInfoForeground?: Color; - private inputValidationWarningBorder?: Color; - private inputValidationWarningBackground?: Color; - private inputValidationWarningForeground?: Color; - private inputValidationErrorBorder?: Color; - private inputValidationErrorBackground?: Color; - private inputValidationErrorForeground?: Color; - private _onDidChange = this._register(new Emitter<string>()); public readonly onDidChange: Event<string> = this._onDidChange.event; private _onDidHeightChange = this._register(new Emitter<number>()); public readonly onDidHeightChange: Event<number> = this._onDidHeightChange.event; - constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options?: IInputOptions) { + constructor(container: HTMLElement, contextViewProvider: IContextViewProvider | undefined, options: IInputOptions) { super(); this.contextViewProvider = contextViewProvider; - this.options = options || Object.create(null); - mixin(this.options, defaultOpts, false); + this.options = options; + this.message = null; this.placeholder = this.options.placeholder || ''; this.tooltip = this.options.tooltip ?? (this.placeholder || ''); this.ariaLabel = this.options.ariaLabel || ''; - this.inputBackground = this.options.inputBackground; - this.inputForeground = this.options.inputForeground; - this.inputBorder = this.options.inputBorder; - - this.inputValidationInfoBorder = this.options.inputValidationInfoBorder; - this.inputValidationInfoBackground = this.options.inputValidationInfoBackground; - this.inputValidationInfoForeground = this.options.inputValidationInfoForeground; - this.inputValidationWarningBorder = this.options.inputValidationWarningBorder; - this.inputValidationWarningBackground = this.options.inputValidationWarningBackground; - this.inputValidationWarningForeground = this.options.inputValidationWarningForeground; - this.inputValidationErrorBorder = this.options.inputValidationErrorBorder; - this.inputValidationErrorBackground = this.options.inputValidationErrorBackground; - this.inputValidationErrorForeground = this.options.inputValidationErrorForeground; - if (this.options.validationOptions) { this.validation = this.options.validationOptions.validation; // {{SQL CARBON EDIT}} Candidate for addition to vscode @@ -242,7 +218,7 @@ export class InputBox extends Widget { this.onblur(this.input, () => this.onBlur()); this.onfocus(this.input, () => this.onFocus()); - this.ignoreGesture(this.input); + this._register(this.ignoreGesture(this.input)); setTimeout(() => this.updateMirror(), 0); @@ -257,6 +233,9 @@ export class InputBox extends Widget { this.applyStyles(); } + public style(styles: IInputBoxStyles): void { // {{SQL CARBON TODO}} - remove this method + } + protected onBlur(): void { this._hideMessage(); if (this.options.showPlaceholderOnFocus) { @@ -314,6 +293,14 @@ export class InputBox extends Widget { } } + public get step(): string { + return this.input.step; + } + + public set step(newValue: string) { + this.input.step = newValue; + } + public get height(): number { return typeof this.cachedHeight === 'number' ? this.cachedHeight : dom.getTotalHeight(this.element); } @@ -409,6 +396,11 @@ export class InputBox extends Widget { } public showMessage(message: IMessage, force?: boolean): void { + if (this.state === 'open' && equals(this.message, message)) { + // Already showing + return; + } + this.message = message; this.element.classList.remove('idle'); @@ -418,9 +410,9 @@ export class InputBox extends Widget { this.element.classList.add(this.classForType(message.type)); const styles = this.stylesForType(this.message.type); - this.element.style.border = styles.border ? `1px solid ${styles.border}` : ''; + this.element.style.border = `1px solid ${dom.asCssValueWithDefault(styles.border, 'transparent')}`; - if (this.hasFocus() || force) { + if (this.message.content && (this.hasFocus() || force)) { this._showMessage(); } } @@ -468,11 +460,12 @@ export class InputBox extends Widget { return errorMsg?.type; } - public stylesForType(type: MessageType | undefined): { border: Color | undefined; background: Color | undefined; foreground: Color | undefined } { + public stylesForType(type: MessageType | undefined): { border: string | undefined; background: string | undefined; foreground: string | undefined } { + const styles = this.options.inputBoxStyles; switch (type) { - case MessageType.INFO: return { border: this.inputValidationInfoBorder, background: this.inputValidationInfoBackground, foreground: this.inputValidationInfoForeground }; - case MessageType.WARNING: return { border: this.inputValidationWarningBorder, background: this.inputValidationWarningBackground, foreground: this.inputValidationWarningForeground }; - default: return { border: this.inputValidationErrorBorder, background: this.inputValidationErrorBackground, foreground: this.inputValidationErrorForeground }; + case MessageType.INFO: return { border: styles.inputValidationInfoBorder, background: styles.inputValidationInfoBackground, foreground: styles.inputValidationInfoForeground }; + case MessageType.WARNING: return { border: styles.inputValidationWarningBorder, background: styles.inputValidationWarningBackground, foreground: styles.inputValidationWarningForeground }; + default: return { border: styles.inputValidationErrorBorder, background: styles.inputValidationErrorBackground, foreground: styles.inputValidationErrorForeground }; } } @@ -510,13 +503,13 @@ export class InputBox extends Widget { }; const spanElement = (this.message.formatContent - ? renderFormattedText(this.message.content, renderOptions) - : renderText(this.message.content, renderOptions)); + ? renderFormattedText(this.message.content!, renderOptions) + : renderText(this.message.content!, renderOptions)); spanElement.classList.add(this.classForType(this.message.type)); const styles = this.stylesForType(this.message.type); - spanElement.style.backgroundColor = styles.background ? styles.background.toString() : ''; - spanElement.style.color = styles.foreground ? styles.foreground.toString() : ''; + spanElement.style.backgroundColor = styles.background ?? ''; + spanElement.style.color = styles.foreground ?? ''; spanElement.style.border = styles.border ? `1px solid ${styles.border}` : ''; dom.append(div, spanElement); @@ -588,37 +581,24 @@ export class InputBox extends Widget { this.layout(); } - public style(styles: IInputBoxStyles): void { - this.inputBackground = styles.inputBackground; - this.inputForeground = styles.inputForeground; - this.inputBorder = styles.inputBorder; - - this.inputValidationInfoBackground = styles.inputValidationInfoBackground; - this.inputValidationInfoForeground = styles.inputValidationInfoForeground; - this.inputValidationInfoBorder = styles.inputValidationInfoBorder; - this.inputValidationWarningBackground = styles.inputValidationWarningBackground; - this.inputValidationWarningForeground = styles.inputValidationWarningForeground; - this.inputValidationWarningBorder = styles.inputValidationWarningBorder; - this.inputValidationErrorBackground = styles.inputValidationErrorBackground; - this.inputValidationErrorForeground = styles.inputValidationErrorForeground; - this.inputValidationErrorBorder = styles.inputValidationErrorBorder; - - this.applyStyles(); - } - protected applyStyles(): void { - const background = this.inputBackground ? this.inputBackground.toString() : ''; - const foreground = this.inputForeground ? this.inputForeground.toString() : ''; - const border = this.inputBorder ? this.inputBorder.toString() : ''; + if (!this.options?.inputBoxStyles) { + return; + } + + const styles = this.options.inputBoxStyles; + + const background = styles.inputBackground ?? ''; + const foreground = styles.inputForeground ?? ''; + const border = styles.inputBorder ?? ''; this.element.style.backgroundColor = background; this.element.style.color = foreground; this.input.style.backgroundColor = 'inherit'; this.input.style.color = foreground; - this.element.style.borderWidth = border ? '1px' : ''; - this.element.style.borderStyle = border ? 'solid' : ''; - this.element.style.borderColor = border; + // there's always a border, even if the color is not set. + this.element.style.border = `1px solid ${dom.asCssValueWithDefault(border, 'transparent')}`; } public layout(): void { @@ -654,9 +634,7 @@ export class InputBox extends Widget { this.message = null; - if (this.actionbar) { - this.actionbar.dispose(); - } + this.actionbar?.dispose(); super.dispose(); } @@ -743,8 +721,8 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge } } - public addToHistory(): void { - if (this.value && this.value !== this.getCurrentValue()) { + public addToHistory(always?: boolean): void { + if (this.value && (always || this.value !== this.getCurrentValue())) { this.history.add(this.value); } } @@ -753,6 +731,18 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge return this.history.getHistory(); } + public isAtFirstInHistory(): boolean { + return this.history.isFirst(); + } + + public isAtLastInHistory(): boolean { + return this.history.isLast(); + } + + public isNowhereInHistory(): boolean { + return this.history.isNowhere(); + } + public showNextValue(): void { if (!this.history.has(this.value)) { this.addToHistory(); @@ -763,10 +753,8 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge next = next === this.value ? this.getNextValue() : next; } - if (next) { - this.value = next; - aria.status(this.value); - } + this.value = next ?? ''; + aria.status(this.value ? this.value : nls.localize('clearedInput', "Cleared Input")); } public showPreviousValue(): void { @@ -813,6 +801,6 @@ export class HistoryInputBox extends InputBox implements IHistoryNavigationWidge } private getNextValue(): string | null { - return this.history.next() || this.history.last(); + return this.history.next(); } } diff --git a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts index 0eb2c54792..5638db3896 100644 --- a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts +++ b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts @@ -4,18 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { Color } from 'vs/base/common/color'; import { UILabelProvider } from 'vs/base/common/keybindingLabels'; -import { ResolvedKeybinding, ResolvedKeybindingPart } from 'vs/base/common/keybindings'; +import { ResolvedKeybinding, ResolvedChord } from 'vs/base/common/keybindings'; import { equals } from 'vs/base/common/objects'; import { OperatingSystem } from 'vs/base/common/platform'; -import { IThemable } from 'vs/base/common/styler'; import 'vs/css!./keybindingLabel'; import { localize } from 'vs/nls'; const $ = dom.$; -export interface PartMatches { +export interface ChordMatches { ctrlKey?: boolean; shiftKey?: boolean; altKey?: boolean; @@ -24,23 +22,35 @@ export interface PartMatches { } export interface Matches { - firstPart: PartMatches; - chordPart: PartMatches; + firstPart: ChordMatches; + chordPart: ChordMatches; } export interface KeybindingLabelOptions extends IKeybindingLabelStyles { renderUnboundKeybindings?: boolean; + /** + * Default false. + */ + disableTitle?: boolean; } export interface IKeybindingLabelStyles { - keybindingLabelBackground?: Color; - keybindingLabelForeground?: Color; - keybindingLabelBorder?: Color; - keybindingLabelBottomBorder?: Color; - keybindingLabelShadow?: Color; + keybindingLabelBackground: string | undefined; + keybindingLabelForeground: string | undefined; + keybindingLabelBorder: string | undefined; + keybindingLabelBottomBorder: string | undefined; + keybindingLabelShadow: string | undefined; } -export class KeybindingLabel implements IThemable { +export const unthemedKeybindingLabelOptions: KeybindingLabelOptions = { + keybindingLabelBackground: undefined, + keybindingLabelForeground: undefined, + keybindingLabelBorder: undefined, + keybindingLabelBottomBorder: undefined, + keybindingLabelShadow: undefined +}; + +export class KeybindingLabel { private domNode: HTMLElement; private options: KeybindingLabelOptions; @@ -51,22 +61,16 @@ export class KeybindingLabel implements IThemable { private matches: Matches | undefined; private didEverRender: boolean; - private labelBackground: Color | undefined; - private labelForeground: Color | undefined; - private labelBorder: Color | undefined; - private labelBottomBorder: Color | undefined; - private labelShadow: Color | undefined; - constructor(container: HTMLElement, private os: OperatingSystem, options?: KeybindingLabelOptions) { this.options = options || Object.create(null); - this.labelBackground = this.options.keybindingLabelBackground; - this.labelForeground = this.options.keybindingLabelForeground; - this.labelBorder = this.options.keybindingLabelBorder; - this.labelBottomBorder = this.options.keybindingLabelBottomBorder; - this.labelShadow = this.options.keybindingLabelShadow; + const labelForeground = this.options.keybindingLabelForeground; this.domNode = dom.append(container, $('.monaco-keybinding')); + if (labelForeground) { + this.domNode.style.color = labelForeground; + } + this.didEverRender = false; container.appendChild(this.domNode); } @@ -89,21 +93,24 @@ export class KeybindingLabel implements IThemable { this.clear(); if (this.keybinding) { - const [firstPart, chordPart] = this.keybinding.getParts(); - if (firstPart) { - this.renderPart(this.domNode, firstPart, this.matches ? this.matches.firstPart : null); + const chords = this.keybinding.getChords(); + if (chords[0]) { + this.renderChord(this.domNode, chords[0], this.matches ? this.matches.firstPart : null); } - if (chordPart) { + for (let i = 1; i < chords.length; i++) { dom.append(this.domNode, $('span.monaco-keybinding-key-chord-separator', undefined, ' ')); - this.renderPart(this.domNode, chordPart, this.matches ? this.matches.chordPart : null); + this.renderChord(this.domNode, chords[i], this.matches ? this.matches.chordPart : null); + } + const title = (this.options.disableTitle ?? false) ? undefined : this.keybinding.getAriaLabel() || undefined; + if (title !== undefined) { + this.domNode.title = title; + } else { + this.domNode.removeAttribute('title'); } - this.domNode.title = this.keybinding.getAriaLabel() || ''; } else if (this.options && this.options.renderUnboundKeybindings) { this.renderUnbound(this.domNode); } - this.applyStyles(); - this.didEverRender = true; } @@ -112,21 +119,21 @@ export class KeybindingLabel implements IThemable { this.keyElements.clear(); } - private renderPart(parent: HTMLElement, part: ResolvedKeybindingPart, match: PartMatches | null) { + private renderChord(parent: HTMLElement, chord: ResolvedChord, match: ChordMatches | null) { const modifierLabels = UILabelProvider.modifierLabels[this.os]; - if (part.ctrlKey) { + if (chord.ctrlKey) { this.renderKey(parent, modifierLabels.ctrlKey, Boolean(match?.ctrlKey), modifierLabels.separator); } - if (part.shiftKey) { + if (chord.shiftKey) { this.renderKey(parent, modifierLabels.shiftKey, Boolean(match?.shiftKey), modifierLabels.separator); } - if (part.altKey) { + if (chord.altKey) { this.renderKey(parent, modifierLabels.altKey, Boolean(match?.altKey), modifierLabels.separator); } - if (part.metaKey) { + if (chord.metaKey) { this.renderKey(parent, modifierLabels.metaKey, Boolean(match?.metaKey), modifierLabels.separator); } - const keyLabel = part.keyLabel; + const keyLabel = chord.keyLabel; if (keyLabel) { this.renderKey(parent, keyLabel, Boolean(match?.keyCode), ''); } @@ -147,40 +154,20 @@ export class KeybindingLabel implements IThemable { const keyElement = $('span.monaco-keybinding-key' + extraClass, undefined, label); this.keyElements.add(keyElement); - return keyElement; - } - - style(styles: IKeybindingLabelStyles): void { - this.labelBackground = styles.keybindingLabelBackground; - this.labelForeground = styles.keybindingLabelForeground; - this.labelBorder = styles.keybindingLabelBorder; - this.labelBottomBorder = styles.keybindingLabelBottomBorder; - this.labelShadow = styles.keybindingLabelShadow; - - this.applyStyles(); - } - - private applyStyles() { - if (this.element) { - for (const keyElement of this.keyElements) { - if (this.labelBackground) { - keyElement.style.backgroundColor = this.labelBackground?.toString(); - } - if (this.labelBorder) { - keyElement.style.borderColor = this.labelBorder.toString(); - } - if (this.labelBottomBorder) { - keyElement.style.borderBottomColor = this.labelBottomBorder.toString(); - } - if (this.labelShadow) { - keyElement.style.boxShadow = `inset 0 -1px 0 ${this.labelShadow}`; - } - } - - if (this.labelForeground) { - this.element.style.color = this.labelForeground.toString(); - } + if (this.options.keybindingLabelBackground) { + keyElement.style.backgroundColor = this.options.keybindingLabelBackground; } + if (this.options.keybindingLabelBorder) { + keyElement.style.borderColor = this.options.keybindingLabelBorder; + } + if (this.options.keybindingLabelBottomBorder) { + keyElement.style.borderBottomColor = this.options.keybindingLabelBottomBorder; + } + if (this.options.keybindingLabelShadow) { + keyElement.style.boxShadow = `inset 0 -1px 0 ${this.options.keybindingLabelShadow}`; + } + + return keyElement; } private static areSame(a: Matches | undefined, b: Matches | undefined): boolean { diff --git a/src/vs/base/browser/ui/list/list.css b/src/vs/base/browser/ui/list/list.css index 1eee599f26..e65d1d73ce 100644 --- a/src/vs/base/browser/ui/list/list.css +++ b/src/vs/base/browser/ui/list/list.css @@ -13,7 +13,6 @@ .monaco-list.mouse-support { user-select: none; -webkit-user-select: none; - -ms-user-select: none; } .monaco-list > .monaco-scrollable-element { diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index e475636268..3cf32a425d 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -24,40 +24,44 @@ export interface IListRenderer<T, TTemplateData> { } export interface IListEvent<T> { - elements: T[]; - indexes: number[]; - browserEvent?: UIEvent; + readonly elements: readonly T[]; + readonly indexes: readonly number[]; + readonly browserEvent?: UIEvent; +} + +export interface IListBrowserMouseEvent extends MouseEvent { + isHandledByList?: boolean; } export interface IListMouseEvent<T> { - browserEvent: MouseEvent; - element: T | undefined; - index: number | undefined; + readonly browserEvent: IListBrowserMouseEvent; + readonly element: T | undefined; + readonly index: number | undefined; } export interface IListTouchEvent<T> { - browserEvent: TouchEvent; - element: T | undefined; - index: number | undefined; + readonly browserEvent: TouchEvent; + readonly element: T | undefined; + readonly index: number | undefined; } export interface IListGestureEvent<T> { - browserEvent: GestureEvent; - element: T | undefined; - index: number | undefined; + readonly browserEvent: GestureEvent; + readonly element: T | undefined; + readonly index: number | undefined; } export interface IListDragEvent<T> { - browserEvent: DragEvent; - element: T | undefined; - index: number | undefined; + readonly browserEvent: DragEvent; + readonly element: T | undefined; + readonly index: number | undefined; } export interface IListContextMenuEvent<T> { - browserEvent: UIEvent; - element: T | undefined; - index: number | undefined; - anchor: HTMLElement | { x: number; y: number }; + readonly browserEvent: UIEvent; + readonly element: T | undefined; + readonly index: number | undefined; + readonly anchor: HTMLElement | { readonly x: number; readonly y: number }; } export interface IIdentityProvider<T> { diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index cd1498ef3d..9486e006b0 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -9,7 +9,6 @@ import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IPagedModel } from 'vs/base/common/paging'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { IThemable } from 'vs/base/common/styler'; import 'vs/css!./list'; import { IListContextMenuEvent, IListEvent, IListMouseEvent, IListRenderer, IListVirtualDelegate } from './list'; import { IListAccessibilityProvider, IListOptions, IListOptionsUpdate, IListStyles, List, TypeNavigationMode } from './listWidget'; @@ -38,9 +37,7 @@ class PagedRenderer<TElement, TTemplateData> implements IListRenderer<number, IT } renderElement(index: number, _: number, data: ITemplateData<TTemplateData>, height: number | undefined): void { - if (data.disposable) { - data.disposable.dispose(); - } + data.disposable?.dispose(); if (!data.data) { return; @@ -110,6 +107,7 @@ export interface IPagedListOptions<T> { readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; + readonly scrollByPage?: boolean; readonly additionalScrollHeight?: number; } @@ -120,7 +118,7 @@ function fromPagedListOptions<T>(modelProvider: () => IPagedModel<T>, options: I }; } -export class PagedList<T> implements IThemable, IDisposable { +export class PagedList<T> implements IDisposable { private list: List<number>; private _model!: IPagedModel<T>; diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index decc1896e5..db2b6f7bac 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -3,9 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isFirefox } from 'vs/base/browser/browser'; -import { DataTransfers, IDragAndDropData, StaticDND } from 'vs/base/browser/dnd'; -import { $, addDisposableListener, animate, getContentHeight, getContentWidth, getTopLeftOffset, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; +import { $, addDisposableListener, animate, Dimension, getContentHeight, getContentWidth, getTopLeftOffset, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { EventType as TouchEventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; @@ -22,6 +21,9 @@ import { IListDragAndDrop, IListDragEvent, IListGestureEvent, IListMouseEvent, I import { RangeMap, shift } from 'vs/base/browser/ui/list/rangeMap'; import { IRow, RowCache } from 'vs/base/browser/ui/list/rowCache'; import { IObservableValue } from 'vs/base/common/observableValue'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { AriaRole } from 'vs/base/browser/ui/aria/aria'; +import { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions'; interface IItem<T> { readonly id: string; @@ -38,6 +40,10 @@ interface IItem<T> { checkedDisposable: IDisposable; } +export const StaticDND = { + CurrentDragAndDropData: undefined as IDragAndDropData | undefined +}; + export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> { getDragElements(element: T): T[]; } @@ -45,7 +51,7 @@ export interface IListViewDragAndDrop<T> extends IListDragAndDrop<T> { export interface IListViewAccessibilityProvider<T> { getSetSize?(element: T, index: number, listLength: number): number; getPosInSet?(element: T, index: number): number; - getRole?(element: T): string | undefined; + getRole?(element: T): AriaRole | undefined; isChecked?(element: T): boolean | IObservableValue<boolean> | undefined; } @@ -53,6 +59,7 @@ export interface IListViewOptionsUpdate { readonly additionalScrollHeight?: number; readonly smoothScrolling?: boolean; readonly horizontalScrolling?: boolean; + readonly scrollByPage?: boolean; readonly mouseWheelScrollSensitivity?: number; readonly fastScrollSensitivity?: number; } @@ -68,6 +75,7 @@ export interface IListViewOptions<T> extends IListViewOptionsUpdate { readonly accessibilityProvider?: IListViewAccessibilityProvider<T>; readonly transformOptimization?: boolean; readonly alwaysConsumeMouseWheel?: boolean; + readonly initialSize?: Dimension; } const DefaultOptions = { @@ -174,7 +182,7 @@ class ListViewAccessibilityProvider<T> implements Required<IListViewAccessibilit readonly getSetSize: (element: any, index: number, listLength: number) => number; readonly getPosInSet: (element: any, index: number) => number; - readonly getRole: (element: T) => string | undefined; + readonly getRole: (element: T) => AriaRole | undefined; readonly isChecked: (element: T) => boolean | IObservableValue<boolean> | undefined; constructor(accessibilityProvider?: IListViewAccessibilityProvider<T>) { @@ -204,6 +212,54 @@ class ListViewAccessibilityProvider<T> implements Required<IListViewAccessibilit } } +export interface IListView<T> extends ISpliceable<T>, IDisposable { + readonly domId: string; + readonly domNode: HTMLElement; + readonly containerDomNode: HTMLElement; + readonly scrollableElementDomNode: HTMLElement; + readonly length: number; + readonly contentHeight: number; + readonly contentWidth: number; + readonly onDidChangeContentHeight: Event<number>; + readonly onDidChangeContentWidth: Event<number>; + readonly renderHeight: number; + readonly scrollHeight: number; + readonly firstVisibleIndex: number; + readonly lastVisibleIndex: number; + onDidScroll: Event<ScrollEvent>; + onWillScroll: Event<ScrollEvent>; + onMouseClick: Event<IListMouseEvent<T>>; + onMouseDblClick: Event<IListMouseEvent<T>>; + onMouseMiddleClick: Event<IListMouseEvent<T>>; + onMouseUp: Event<IListMouseEvent<T>>; + onMouseDown: Event<IListMouseEvent<T>>; + onMouseOver: Event<IListMouseEvent<T>>; + onMouseMove: Event<IListMouseEvent<T>>; + onMouseOut: Event<IListMouseEvent<T>>; + onContextMenu: Event<IListMouseEvent<T>>; + onTouchStart: Event<IListTouchEvent<T>>; + onTap: Event<IListGestureEvent<T>>; + element(index: number): T; + domElement(index: number): HTMLElement | null; + getElementDomId(index: number): string; + elementHeight(index: number): number; + elementTop(index: number): number; + indexOf(element: T): number; + indexAt(position: number): number; + indexAfter(position: number): number; + updateOptions(options: IListViewOptionsUpdate): void; + getScrollTop(): number; + setScrollTop(scrollTop: number, reuseAnimation?: boolean): void; + getScrollLeft(): number; + setScrollLeft(scrollLeft: number): void; + delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent): void; + delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent): void; + updateWidth(index: number): void; + updateElementHeight(index: number, size: number | undefined, anchorIndex: number | null): void; + rerender(): void; + layout(height?: number, width?: number): void; +} + /** * The {@link ListView} is a virtual scrolling engine. * @@ -214,7 +270,7 @@ class ListViewAccessibilityProvider<T> implements Required<IListViewAccessibilit * @remarks It is a low-level widget, not meant to be used directly. Refer to the * List widget instead. */ -export class ListView<T> implements ISpliceable<T>, IDisposable { +export class ListView<T> implements IListView<T> { private static InstanceCount = 0; readonly domId = `list_id_${++ListView.InstanceCount}`; @@ -256,8 +312,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { private readonly disposables: DisposableStore = new DisposableStore(); private readonly _onDidChangeContentHeight = new Emitter<number>(); + private readonly _onDidChangeContentWidth = new Emitter<number>(); readonly onDidChangeContentHeight: Event<number> = Event.latch(this._onDidChangeContentHeight.event, undefined, this.disposables); + readonly onDidChangeContentWidth: Event<number> = Event.latch(this._onDidChangeContentWidth.event, undefined, this.disposables); get contentHeight(): number { return this.rangeMap.size; } + get contentWidth(): number { return this.scrollWidth ?? 0; } get onDidScroll(): Event<ScrollEvent> { return this.scrollableElement.onScroll; } get onWillScroll(): Event<ScrollEvent> { return this.scrollableElement.onWillScroll; } @@ -337,6 +396,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { const transformOptimization = options.transformOptimization ?? DefaultOptions.transformOptimization; if (transformOptimization) { this.rowsContainer.style.transform = 'translate3d(0px, 0px, 0px)'; + this.rowsContainer.style.overflow = 'hidden'; + this.rowsContainer.style.contain = 'strict'; } this.disposables.add(Gesture.addTarget(this.rowsContainer)); @@ -352,7 +413,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { vertical: options.verticalScrollMode ?? DefaultOptions.verticalScrollMode, useShadows: options.useShadows ?? DefaultOptions.useShadows, mouseWheelScrollSensitivity: options.mouseWheelScrollSensitivity, - fastScrollSensitivity: options.fastScrollSensitivity + fastScrollSensitivity: options.fastScrollSensitivity, + scrollByPage: options.scrollByPage }, this.scrollable)); this.domNode.appendChild(this.scrollableElement.getDomNode()); @@ -375,7 +437,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { this.supportDynamicHeights = options.supportDynamicHeights ?? DefaultOptions.supportDynamicHeights; this.dnd = options.dnd ?? DefaultOptions.dnd; - this.layout(); + this.layout(options.initialSize?.height, options.initialSize?.width); } updateOptions(options: IListViewOptionsUpdate) { @@ -392,17 +454,31 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { this.horizontalScrolling = options.horizontalScrolling; } + let scrollableOptions: ScrollableElementChangeOptions | undefined; + + if (options.scrollByPage !== undefined) { + scrollableOptions = { ...(scrollableOptions ?? {}), scrollByPage: options.scrollByPage }; + } + if (options.mouseWheelScrollSensitivity !== undefined) { - this.scrollableElement.updateOptions({ mouseWheelScrollSensitivity: options.mouseWheelScrollSensitivity }); + scrollableOptions = { ...(scrollableOptions ?? {}), mouseWheelScrollSensitivity: options.mouseWheelScrollSensitivity }; } if (options.fastScrollSensitivity !== undefined) { - this.scrollableElement.updateOptions({ fastScrollSensitivity: options.fastScrollSensitivity }); + scrollableOptions = { ...(scrollableOptions ?? {}), fastScrollSensitivity: options.fastScrollSensitivity }; + } + + if (scrollableOptions) { + this.scrollableElement.updateOptions(scrollableOptions); } } - triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { - this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent); + delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + this.scrollableElement.delegateScrollFromMouseWheelEvent(browserEvent); + } + + delegateVerticalScrollbarPointerDown(browserEvent: PointerEvent) { + this.scrollableElement.delegateVerticalScrollbarPointerDown(browserEvent); } updateElementHeight(index: number, size: number | undefined, anchorIndex: number | null): void { @@ -456,7 +532,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { } } - splice(start: number, deleteCount: number, elements: T[] = []): T[] { + splice(start: number, deleteCount: number, elements: readonly T[] = []): T[] { if (this.splicing) { throw new Error('Can\'t run recursive splices.'); } @@ -471,7 +547,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { } } - private _splice(start: number, deleteCount: number, elements: T[] = []): T[] { + private _splice(start: number, deleteCount: number, elements: readonly T[] = []): T[] { const previousRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); const deleteRange = { start, end: start + deleteCount }; const removeRange = Range.intersect(previousRenderRange, deleteRange); @@ -618,6 +694,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { this.scrollWidth = scrollWidth; this.scrollableElement.setScrollDimensions({ scrollWidth: scrollWidth === 0 ? 0 : (scrollWidth + 10) }); + this._onDidChangeContentWidth.fire(this.scrollWidth); } updateWidth(index: number): void { @@ -631,6 +708,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { if (typeof item.width !== 'undefined' && item.width > this.scrollWidth) { this.scrollWidth = item.width; this.scrollableElement.setScrollDimensions({ scrollWidth: this.scrollWidth + 10 }); + this._onDidChangeContentWidth.fire(this.scrollWidth); } } @@ -733,7 +811,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { // Render - private render(previousRenderRange: IRange, renderTop: number, renderHeight: number, renderLeft: number | undefined, scrollWidth: number | undefined, updateItemsInDOM: boolean = false): void { + protected render(previousRenderRange: IRange, renderTop: number, renderHeight: number, renderLeft: number | undefined, scrollWidth: number | undefined, updateItemsInDOM: boolean = false): void { const renderRange = this.getRenderRange(renderTop, renderHeight); const rangesToInsert = Range.relativeComplement(renderRange, previousRenderRange); @@ -748,17 +826,19 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { } } - for (const range of rangesToInsert) { - for (let i = range.start; i < range.end; i++) { - this.insertItemInDOM(i, beforeElement); + this.cache.transact(() => { + for (const range of rangesToRemove) { + for (let i = range.start; i < range.end; i++) { + this.removeItemFromDOM(i); + } } - } - for (const range of rangesToRemove) { - for (let i = range.start; i < range.end; i++) { - this.removeItemFromDOM(i); + for (const range of rangesToInsert) { + for (let i = range.start; i < range.end; i++) { + this.insertItemInDOM(i, beforeElement); + } } - } + }); if (renderLeft !== undefined) { this.rowsContainer.style.left = `-${renderLeft}px`; @@ -779,8 +859,15 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { private insertItemInDOM(index: number, beforeElement: HTMLElement | null, row?: IRow): void { const item = this.items[index]; + let isStale = false; if (!item.row) { - item.row = row ?? this.cache.alloc(item.templateId); + if (row) { + item.row = row; + } else { + const result = this.cache.alloc(item.templateId); + item.row = result.row; + isStale = result.isReusingConnectedDomNode; + } } const role = this.accessibilityProvider.getRole(item.element) || 'listitem'; @@ -796,7 +883,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { item.checkedDisposable = checked.onDidChange(update); } - if (!item.row.domNode.parentElement) { + if (isStale || !item.row.domNode.parentElement) { if (beforeElement) { this.rowsContainer.insertBefore(item.row.domNode, beforeElement); } else { @@ -833,7 +920,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { return; } - item.row.domNode.style.width = isFirefox ? '-moz-fit-content' : 'fit-content'; + item.row.domNode.style.width = 'fit-content'; item.width = getContentWidth(item.row.domNode); const style = window.getComputedStyle(item.row.domNode); @@ -943,7 +1030,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { @memoize get onMouseOver(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseover')).event, e => this.toMouseEvent(e), this.disposables); } @memoize get onMouseMove(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mousemove')).event, e => this.toMouseEvent(e), this.disposables); } @memoize get onMouseOut(): Event<IListMouseEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'mouseout')).event, e => this.toMouseEvent(e), this.disposables); } - @memoize get onContextMenu(): Event<IListMouseEvent<T> | IListGestureEvent<T>> { return Event.any(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'contextmenu')).event, e => this.toMouseEvent(e), this.disposables), Event.map(this.disposables.add(new DomEmitter(this.domNode, TouchEventType.Contextmenu)).event as Event<GestureEvent>, e => this.toGestureEvent(e), this.disposables)); } + @memoize get onContextMenu(): Event<IListMouseEvent<T> | IListGestureEvent<T>> { return Event.any<IListMouseEvent<any> | IListGestureEvent<any>>(Event.map(this.disposables.add(new DomEmitter(this.domNode, 'contextmenu')).event, e => this.toMouseEvent(e), this.disposables), Event.map(this.disposables.add(new DomEmitter(this.domNode, TouchEventType.Contextmenu)).event as Event<GestureEvent>, e => this.toGestureEvent(e), this.disposables)); } @memoize get onTouchStart(): Event<IListTouchEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.domNode, 'touchstart')).event, e => this.toTouchEvent(e), this.disposables); } @memoize get onTap(): Event<IListGestureEvent<T>> { return Event.map(this.disposables.add(new DomEmitter(this.rowsContainer, TouchEventType.Tap)).event, e => this.toGestureEvent(e as GestureEvent), this.disposables); } @@ -1021,11 +1108,21 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { const dragImage = $('.monaco-drag-image'); dragImage.textContent = label; - document.body.appendChild(dragImage); + + const getDragImageContainer = (e: HTMLElement | null) => { + while (e && !e.classList.contains('monaco-workbench')) { + e = e.parentElement; + } + return e || document.body; + }; + + const container = getDragImageContainer(this.domNode); + container.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, -10, -10); - setTimeout(() => document.body.removeChild(dragImage), 0); + setTimeout(() => container.removeChild(dragImage), 0); } + this.domNode.classList.add('dragging'); this.currentDragData = new ElementsDragAndDropData(elements); StaticDND.CurrentDragAndDropData = new ExternalElementsDragAndDropData(elements); @@ -1141,6 +1238,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { const dragData = this.currentDragData; this.teardownDragAndDropScrollTopAnimation(); this.clearDragOverFeedback(); + this.domNode.classList.remove('dragging'); this.currentDragData = undefined; StaticDND.CurrentDragAndDropData = undefined; @@ -1157,6 +1255,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { this.canDrop = false; this.teardownDragAndDropScrollTopAnimation(); this.clearDragOverFeedback(); + this.domNode.classList.remove('dragging'); this.currentDragData = undefined; StaticDND.CurrentDragAndDropData = undefined; @@ -1246,7 +1345,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { * Given a stable rendered state, checks every rendered element whether it needs * to be probed for dynamic height. Adjusts scroll height and top if necessary. */ - private _rerender(renderTop: number, renderHeight: number, inSmoothScrolling?: boolean): void { + protected _rerender(renderTop: number, renderHeight: number, inSmoothScrolling?: boolean): void { const previousRenderRange = this.getRenderRange(renderTop, renderHeight); // Let's remember the second element's position, this helps in scrolling up @@ -1352,26 +1451,26 @@ export class ListView<T> implements ISpliceable<T>, IDisposable { const size = item.size; - if (!this.setRowHeight && item.row) { - const newSize = item.row.domNode.offsetHeight; - item.size = newSize; + if (item.row) { + item.row.domNode.style.height = ''; + item.size = item.row.domNode.offsetHeight; item.lastDynamicHeightWidth = this.renderWidth; - return newSize - size; + return item.size - size; } - const row = this.cache.alloc(item.templateId); - + const { row } = this.cache.alloc(item.templateId); row.domNode.style.height = ''; this.rowsContainer.appendChild(row.domNode); const renderer = this.renderers.get(item.templateId); - if (renderer) { - renderer.renderElement(item.element, index, row.templateData, undefined); - renderer.disposeElement?.(item.element, index, row.templateData, undefined); + if (!renderer) { + throw new BugIndicatingError('Missing renderer for templateId: ' + item.templateId); } + renderer.renderElement(item.element, index, row.templateData, undefined); item.size = row.domNode.offsetHeight; + renderer.disposeElement?.(item.element, index, row.templateData, undefined); this.virtualDelegate.setDynamicHeight?.(item.element, item.size); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 0e356c596f..4fe47742d8 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { createStyleSheet } from 'vs/base/browser/dom'; -import { DomEmitter, stopEvent } from 'vs/base/browser/event'; +import { asCssValueWithDefault, createStyleSheet, Dimension, EventHelper } from 'vs/base/browser/dom'; +import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture } from 'vs/base/browser/touch'; -import { alert } from 'vs/base/browser/ui/aria/aria'; -import { IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; +import { alert, AriaRole } from 'vs/base/browser/ui/aria/aria'; import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice'; import { ScrollableElementChangeOptions } from 'vs/base/browser/ui/scrollbar/scrollableElementOptions'; import { binarySearch, firstOrDefault, range } from 'vs/base/common/arrays'; @@ -21,15 +20,13 @@ import { matchesPrefix } from 'vs/base/common/filters'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { clamp } from 'vs/base/common/numbers'; -import { mixin } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; -import { IThemable } from 'vs/base/common/styler'; import { isNumber } from 'vs/base/common/types'; import 'vs/css!./list'; import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate, ListError } from './list'; -import { IListViewAccessibilityProvider, IListViewDragAndDrop, IListViewOptions, IListViewOptionsUpdate, ListView } from './listView'; +import { IListView, IListViewAccessibilityProvider, IListViewDragAndDrop, IListViewOptions, IListViewOptionsUpdate, ListView } from './listView'; interface ITraitChangeEvent { indexes: number[]; @@ -223,7 +220,7 @@ class TraitSpliceable<T> implements ISpliceable<T> { constructor( private trait: Trait<T>, - private view: ListView<T>, + private view: IListView<T>, private identityProvider?: IIdentityProvider<T> ) { } @@ -290,7 +287,7 @@ class KeyboardController<T> implements IDisposable { constructor( private list: List<T>, - private view: ListView<T>, + private view: IListView<T>, options: IListOptions<T> ) { this.onKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(this.onEnter, this, this.disposables); @@ -422,7 +419,7 @@ class TypeNavigationController<T> implements IDisposable { constructor( private list: List<T>, - private view: ListView<T>, + private view: IListView<T>, private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider<T>, private keyboardNavigationEventFilter: IKeyboardNavigationEventFilter, private delegate: IKeyboardNavigationDelegate @@ -457,11 +454,11 @@ class TypeNavigationController<T> implements IDisposable { .map(event => new StandardKeyboardEvent(event)) .filter(e => typing || this.keyboardNavigationEventFilter(e)) .filter(e => this.delegate.mightProducePrintableCharacter(e)) - .forEach(stopEvent) + .forEach(e => EventHelper.stop(e, true)) .map(event => event.browserEvent.key) .event; - const onClear = Event.debounce<string, null>(onChar, () => null, 800, undefined, undefined, this.enabledDisposables); + const onClear = Event.debounce<string, null>(onChar, () => null, 800, undefined, undefined, undefined, this.enabledDisposables); const onInput = Event.reduce<string | null, string | null>(Event.any(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i), undefined, this.enabledDisposables); onInput(this.onInput, this, this.enabledDisposables); @@ -536,7 +533,7 @@ class DOMFocusController<T> implements IDisposable { constructor( private list: List<T>, - private view: ListView<T> + private view: IListView<T> ) { const onKeyDown = this.disposables.add(Event.chain(this.disposables.add(new DomEmitter(view.domNode, 'keydown')).event)) .filter(e => !isInputElement(e.target as HTMLElement)) @@ -625,7 +622,7 @@ export class MouseController<T> implements IDisposable { this.disposables.add(Gesture.addTarget(list.getHTMLElement())); } - Event.any(list.onMouseClick, list.onMouseMiddleClick, list.onTap)(this.onViewPointer, this, this.disposables); + Event.any<IListMouseEvent<any> | IListGestureEvent<any>>(list.onMouseClick, list.onMouseMiddleClick, list.onTap)(this.onViewPointer, this, this.disposables); } updateOptions(optionsUpdate: IListOptionsUpdate): void { @@ -669,7 +666,7 @@ export class MouseController<T> implements IDisposable { } private onContextMenu(e: IListContextMenuEvent<T>): void { - if (isMonacoEditor(e.browserEvent.target as HTMLElement)) { + if (isInputElement(e.browserEvent.target as HTMLElement) || isMonacoEditor(e.browserEvent.target as HTMLElement)) { return; } @@ -686,6 +683,11 @@ export class MouseController<T> implements IDisposable { return; } + if (e.browserEvent.isHandledByList) { + return; + } + + e.browserEvent.isHandledByList = true; const focus = e.index; if (typeof focus === 'undefined') { @@ -722,6 +724,11 @@ export class MouseController<T> implements IDisposable { return; } + if (e.browserEvent.isHandledByList) { + return; + } + + e.browserEvent.isHandledByList = true; const focus = this.list.getFocus(); this.list.setSelection(focus, e.browserEvent); } @@ -783,7 +790,7 @@ export interface IStyleController { export interface IListAccessibilityProvider<T> extends IListViewAccessibilityProvider<T> { getAriaLabel(element: T): string | null; getWidgetAriaLabel(): string; - getWidgetRole?(): string; + getWidgetRole?(): AriaRole; getAriaLevel?(element: T): number | undefined; onDidChangeActiveDescendant?: Event<void>; getActiveDescendantId?(element: T): string | undefined; @@ -798,11 +805,7 @@ export class DefaultStyleController implements IStyleController { const content: string[] = []; if (styles.listBackground) { - if (styles.listBackground.isOpaque()) { - content.push(`.monaco-list${suffix} .monaco-list-rows { background: ${styles.listBackground}; }`); - } else if (!platform.isMacintosh) { // subpixel AA doesn't exist in macOS - console.warn(`List with id '${this.selectorSuffix}' was styled with a non-opaque background color. This will break sub-pixel antialiasing.`); - } + content.push(`.monaco-list${suffix} .monaco-list-rows { background: ${styles.listBackground}; }`); } if (styles.listFocusBackground) { @@ -827,10 +830,6 @@ export class DefaultStyleController implements IStyleController { content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected .codicon { color: ${styles.listActiveSelectionIconForeground}; }`); } - if (styles.listFocusAndSelectionOutline) { - content.push(`.monaco-list${suffix}:focus .monaco-list-row.selected { outline-color: ${styles.listFocusAndSelectionOutline} !important; }`); - } - if (styles.listFocusAndSelectionBackground) { content.push(` .monaco-drag-image, @@ -869,18 +868,22 @@ 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):not(.dragging) .monaco-list-row:hover:not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; }`); } if (styles.listHoverForeground) { - content.push(`.monaco-list${suffix} .monaco-list-row:hover:not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`); + content.push(`.monaco-list${suffix}:not(.drop-target):not(.dragging) .monaco-list-row:hover:not(.selected):not(.focused) { color: ${styles.listHoverForeground}; }`); } - if (styles.listSelectionOutline) { - content.push(`.monaco-list${suffix} .monaco-list-row.selected { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`); + /** + * Outlines + */ + const focusAndSelectionOutline = asCssValueWithDefault(styles.listFocusAndSelectionOutline, asCssValueWithDefault(styles.listSelectionOutline, styles.listFocusOutline ?? '')); + if (focusAndSelectionOutline) { // default: listFocusOutline + content.push(`.monaco-list${suffix}:focus .monaco-list-row.focused.selected { outline: 1px solid ${focusAndSelectionOutline}; outline-offset: -1px;}`); } - if (styles.listFocusOutline) { + if (styles.listFocusOutline) { // default: set content.push(` .monaco-drag-image, .monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } @@ -888,11 +891,20 @@ export class DefaultStyleController implements IStyleController { `); } - if (styles.listInactiveFocusOutline) { + const inactiveFocusAndSelectionOutline = asCssValueWithDefault(styles.listSelectionOutline, styles.listInactiveFocusOutline ?? ''); + if (inactiveFocusAndSelectionOutline) { + content.push(`.monaco-list${suffix} .monaco-list-row.focused.selected { outline: 1px dotted ${inactiveFocusAndSelectionOutline}; outline-offset: -1px; }`); + } + + if (styles.listSelectionOutline) { // default: activeContrastBorder + content.push(`.monaco-list${suffix} .monaco-list-row.selected { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`); + } + + if (styles.listInactiveFocusOutline) { // default: null content.push(`.monaco-list${suffix} .monaco-list-row.focused { outline: 1px dotted ${styles.listInactiveFocusOutline}; outline-offset: -1px; }`); } - if (styles.listHoverOutline) { + if (styles.listHoverOutline) { // default: activeContrastBorder content.push(`.monaco-list${suffix} .monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); } @@ -906,10 +918,18 @@ export class DefaultStyleController implements IStyleController { if (styles.tableColumnsBorder) { content.push(` - .monaco-table:hover > .monaco-split-view2, - .monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before { + .monaco-table > .monaco-split-view2, + .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before, + .monaco-workbench:not(.reduce-motion) .monaco-table:hover > .monaco-split-view2, + .monaco-workbench:not(.reduce-motion) .monaco-table:hover > .monaco-split-view2 .monaco-sash.vertical::before { border-color: ${styles.tableColumnsBorder}; - }`); + } + + .monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2, + .monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { + border-color: transparent; + } + `); } if (styles.tableOddRowsBackgroundColor) { @@ -955,60 +975,70 @@ export interface IListOptions<T> extends IListOptionsUpdate { readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; + readonly scrollByPage?: boolean; readonly additionalScrollHeight?: number; readonly transformOptimization?: boolean; readonly smoothScrolling?: boolean; readonly scrollableElementChangeOptions?: ScrollableElementChangeOptions; readonly alwaysConsumeMouseWheel?: boolean; + readonly initialSize?: Dimension; } -export interface IListStyles extends IFindInputStyles { - listBackground?: Color; - listFocusBackground?: Color; - listFocusForeground?: Color; - listActiveSelectionBackground?: Color; - listActiveSelectionForeground?: Color; - listActiveSelectionIconForeground?: Color; - listFocusAndSelectionOutline?: Color; - listFocusAndSelectionBackground?: Color; - listFocusAndSelectionForeground?: Color; - listInactiveSelectionBackground?: Color; - listInactiveSelectionIconForeground?: Color; - listInactiveSelectionForeground?: Color; - listInactiveFocusForeground?: Color; - listInactiveFocusBackground?: Color; - listHoverBackground?: Color; - listHoverForeground?: Color; - listDropBackground?: Color; - listFocusOutline?: Color; - listInactiveFocusOutline?: Color; - listSelectionOutline?: Color; - listHoverOutline?: Color; - listFilterWidgetBackground?: Color; - listFilterWidgetOutline?: Color; - listFilterWidgetNoMatchesOutline?: Color; - listFilterWidgetShadow?: Color; +export interface IListStyles { + listBackground: string | undefined; + listFocusBackground: string | undefined; + listFocusForeground: string | undefined; + listActiveSelectionBackground: string | undefined; + listActiveSelectionForeground: string | undefined; + listActiveSelectionIconForeground: string | undefined; + listFocusAndSelectionOutline: string | undefined; + listFocusAndSelectionBackground: string | undefined; + listFocusAndSelectionForeground: string | undefined; + listInactiveSelectionBackground: string | undefined; + listInactiveSelectionIconForeground: string | undefined; + listInactiveSelectionForeground: string | undefined; + listInactiveFocusForeground: string | undefined; + listInactiveFocusBackground: string | undefined; + listHoverBackground: string | undefined; + listHoverForeground: string | undefined; + listDropBackground: string | undefined; + listFocusOutline: string | undefined; + listInactiveFocusOutline: string | undefined; + listSelectionOutline: string | undefined; + listHoverOutline: string | undefined; listMatchesShadow?: Color; // {{SQL CARBON EDIT}} - treeIndentGuidesStroke?: Color; - tableColumnsBorder?: Color; - tableOddRowsBackgroundColor?: Color; + treeIndentGuidesStroke: string | undefined; + treeInactiveIndentGuidesStroke: string | undefined; + tableColumnsBorder: string | undefined; + tableOddRowsBackgroundColor: string | undefined; } -const defaultStyles: IListStyles = { - listFocusBackground: Color.fromHex('#7FB0D0'), - listActiveSelectionBackground: Color.fromHex('#0E639C'), - listActiveSelectionForeground: Color.fromHex('#FFFFFF'), - listActiveSelectionIconForeground: Color.fromHex('#FFFFFF'), - listFocusAndSelectionOutline: Color.fromHex('#90C2F9'), - listFocusAndSelectionBackground: Color.fromHex('#094771'), - listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'), - listInactiveSelectionBackground: Color.fromHex('#3F3F46'), - listInactiveSelectionIconForeground: Color.fromHex('#FFFFFF'), - listHoverBackground: Color.fromHex('#2A2D2E'), - listDropBackground: Color.fromHex('#383B3D'), - treeIndentGuidesStroke: Color.fromHex('#a9a9a9'), - tableColumnsBorder: Color.fromHex('#cccccc').transparent(0.2), - tableOddRowsBackgroundColor: Color.fromHex('#cccccc').transparent(0.04) +export const unthemedListStyles: IListStyles = { + listFocusBackground: '#7FB0D0', + listActiveSelectionBackground: '#0E639C', + listActiveSelectionForeground: '#FFFFFF', + listActiveSelectionIconForeground: '#FFFFFF', + listFocusAndSelectionOutline: '#90C2F9', + listFocusAndSelectionBackground: '#094771', + listFocusAndSelectionForeground: '#FFFFFF', + listInactiveSelectionBackground: '#3F3F46', + listInactiveSelectionIconForeground: '#FFFFFF', + listHoverBackground: '#2A2D2E', + listDropBackground: '#383B3D', + treeIndentGuidesStroke: '#a9a9a9', + treeInactiveIndentGuidesStroke: Color.fromHex('#a9a9a9').transparent(0.4).toString(), + tableColumnsBorder: Color.fromHex('#cccccc').transparent(0.2).toString(), + tableOddRowsBackgroundColor: Color.fromHex('#cccccc').transparent(0.04).toString(), + listBackground: undefined, + listFocusForeground: undefined, + listInactiveSelectionForeground: undefined, + listInactiveFocusForeground: undefined, + listInactiveFocusBackground: undefined, + listHoverForeground: undefined, + listFocusOutline: undefined, + listInactiveFocusOutline: undefined, + listSelectionOutline: undefined, + listHoverOutline: undefined }; const DefaultOptions: IListOptions<any> = { @@ -1237,13 +1267,13 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> { * - Dynamic element height support * - Drag-and-drop support */ -export class List<T> implements ISpliceable<T>, IThemable, IDisposable { +export class List<T> implements ISpliceable<T>, IDisposable { private focus = new Trait<T>('focused'); private selection: Trait<T>; private anchor = new Trait<T>('anchor'); private eventBufferer = new EventBufferer(); - protected view: ListView<T>; + protected view: IListView<T>; private spliceable: ISpliceable<T>; private styleController: IStyleController; private typeNavigationController?: TypeNavigationController<T>; @@ -1289,7 +1319,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable { const fromKeyDown = this.disposables.add(Event.chain(this.disposables.add(new DomEmitter(this.view.domNode, 'keydown')).event)) .map(e => new StandardKeyboardEvent(e)) .filter(e => didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) - .map(stopEvent) + .map(e => EventHelper.stop(e, true)) .filter(() => false) .event as Event<any>; @@ -1297,7 +1327,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable { .forEach(() => didJustPressContextMenuKey = false) .map(e => new StandardKeyboardEvent(e)) .filter(e => e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) - .map(stopEvent) + .map(e => EventHelper.stop(e, true)) .map(({ browserEvent }) => { const focus = this.getFocus(); const index = focus.length ? focus[0] : undefined; @@ -1336,8 +1366,6 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable { // {{SQL CARBON EDIT}} - Change the parameter from 'role !== listbox' to 'role !== list', according to the doc https://www.w3.org/WAI/PF/HTML/wiki/RoleAttribute, list role is non-interactive. this.selection = new SelectionTrait(role !== 'list'); - mixin(_options, defaultStyles, false); - const baseRenderers: IListRenderer<T, ITraitTemplateData>[] = [this.focus.renderer, this.selection.renderer]; this.accessibilityProvider = _options.accessibilityProvider; @@ -1355,7 +1383,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable { dnd: _options.dnd && new ListViewDragAndDrop(this, _options.dnd) }; - this.view = new ListView(container, virtualDelegate, renderers, viewOptions); + this.view = this.createListView(container, virtualDelegate, renderers, viewOptions); this.view.domNode.setAttribute('role', role); if (_options.styleController) { @@ -1406,6 +1434,10 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable { } } + protected createListView(container: HTMLElement, virtualDelegate: IListVirtualDelegate<T>, renderers: IListRenderer<any, any>[], viewOptions: IListViewOptions<T>): IListView<T> { + return new ListView(container, virtualDelegate, renderers, viewOptions); + } + protected createMouseController(options: IListOptions<T>): MouseController<T> { return new MouseController(this); } @@ -1432,7 +1464,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable { return this._options; } - splice(start: number, deleteCount: number, elements: T[] = []): void { + splice(start: number, deleteCount: number, elements: readonly T[] = []): void { if (start < 0 || start > this.view.length) { throw new ListError(this.user, `Invalid start index: ${start}`); } @@ -1476,10 +1508,18 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable { return this.view.contentHeight; } + get contentWidth(): number { + return this.view.contentWidth; + } + get onDidChangeContentHeight(): Event<number> { return this.view.onDidChangeContentHeight; } + get onDidChangeContentWidth(): Event<number> { + return this.view.onDidChangeContentWidth; + } + get scrollTop(): number { return this.view.getScrollTop(); } @@ -1530,9 +1570,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable { } triggerTypeNavigation(): void { - if (this.typeNavigationController) { - this.typeNavigationController.trigger(); - } + this.typeNavigationController?.trigger(); } setSelection(indexes: number[], browserEvent?: UIEvent): void { diff --git a/src/vs/base/browser/ui/list/rowCache.ts b/src/vs/base/browser/ui/list/rowCache.ts index f6f4f39ce3..ec3b76982a 100644 --- a/src/vs/base/browser/ui/list/rowCache.ts +++ b/src/vs/base/browser/ui/list/rowCache.ts @@ -25,23 +25,34 @@ export class RowCache<T> implements IDisposable { private cache = new Map<string, IRow[]>(); + private readonly transactionNodesPendingRemoval = new Set<HTMLElement>(); + private inTransaction = false; + constructor(private renderers: Map<string, IListRenderer<T, any>>) { } /** * Returns a row either by creating a new one or reusing * a previously released row which shares the same templateId. + * + * @returns A row and `isReusingConnectedDomNode` if the row's node is already in the dom in a stale position. */ - alloc(templateId: string): IRow { + alloc(templateId: string): { row: IRow; isReusingConnectedDomNode: boolean } { let result = this.getTemplateCache(templateId).pop(); - if (!result) { + let isStale = false; + if (result) { + isStale = this.transactionNodesPendingRemoval.has(result.domNode); + if (isStale) { + this.transactionNodesPendingRemoval.delete(result.domNode); + } + } else { const domNode = $('.monaco-list-row'); const renderer = this.getRenderer(templateId); const templateData = renderer.renderTemplate(domNode); result = { domNode, templateId, templateData }; } - return result; + return { row: result, isReusingConnectedDomNode: isStale }; } /** @@ -55,17 +66,47 @@ export class RowCache<T> implements IDisposable { this.releaseRow(row); } + /** + * Begin a set of changes that use the cache. This lets us skip work when a row is removed and then inserted again. + */ + transact(makeChanges: () => void) { + if (this.inTransaction) { + throw new Error('Already in transaction'); + } + + this.inTransaction = true; + + try { + makeChanges(); + } finally { + for (const domNode of this.transactionNodesPendingRemoval) { + this.doRemoveNode(domNode); + } + + this.transactionNodesPendingRemoval.clear(); + this.inTransaction = false; + } + } + private releaseRow(row: IRow): void { const { domNode, templateId } = row; if (domNode) { - domNode.classList.remove('scrolling'); - removeFromParent(domNode); + if (this.inTransaction) { + this.transactionNodesPendingRemoval.add(domNode); + } else { + this.doRemoveNode(domNode); + } } const cache = this.getTemplateCache(templateId); cache.push(row); } + private doRemoveNode(domNode: HTMLElement) { + domNode.classList.remove('scrolling'); + removeFromParent(domNode); + } + private getTemplateCache(templateId: string): IRow[] { let result = this.cache.get(templateId); @@ -87,6 +128,7 @@ export class RowCache<T> implements IDisposable { }); this.cache.clear(); + this.transactionNodesPendingRemoval.clear(); } private getRenderer(templateId: string): IListRenderer<T, any> { diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index f8879a6c05..476e5d33c3 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -10,13 +10,12 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionViewItem, BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles'; import { AnchorAlignment, layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { EmptySubmenuAction, IAction, IActionRunner, Separator, SubmenuAction } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; +import { Codicon, getCodiconFontCharacters } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import { Event } from 'vs/base/common/event'; import { stripIcons } from 'vs/base/common/iconLabels'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -50,20 +49,35 @@ export interface IMenuOptions { } export interface IMenuStyles { - shadowColor?: Color; - borderColor?: Color; - foregroundColor?: Color; - backgroundColor?: Color; - selectionForegroundColor?: Color; - selectionBackgroundColor?: Color; - selectionBorderColor?: Color; - separatorColor?: Color; - scrollbarShadow?: Color; - scrollbarSliderBackground?: Color; - scrollbarSliderHoverBackground?: Color; - scrollbarSliderActiveBackground?: Color; + shadowColor: string | undefined; + borderColor: string | undefined; + foregroundColor: string | undefined; + backgroundColor: string | undefined; + selectionForegroundColor: string | undefined; + selectionBackgroundColor: string | undefined; + selectionBorderColor: string | undefined; + separatorColor: string | undefined; + scrollbarShadow: string | undefined; + scrollbarSliderBackground: string | undefined; + scrollbarSliderHoverBackground: string | undefined; + scrollbarSliderActiveBackground: string | undefined; } +export const unthemedMenuStyles: IMenuStyles = { + shadowColor: undefined, + borderColor: undefined, + foregroundColor: undefined, + backgroundColor: undefined, + selectionForegroundColor: undefined, + selectionBackgroundColor: undefined, + selectionBorderColor: undefined, + separatorColor: undefined, + scrollbarShadow: undefined, + scrollbarSliderBackground: undefined, + scrollbarSliderHoverBackground: undefined, + scrollbarSliderActiveBackground: undefined +}; + interface ISubMenuData { parent: Menu; submenu?: Menu; @@ -77,7 +91,7 @@ export class Menu extends ActionBar { static globalStyleSheet: HTMLStyleElement; protected styleSheet: HTMLStyleElement | undefined; - constructor(container: HTMLElement, actions: ReadonlyArray<IAction>, options: IMenuOptions = {}) { + constructor(container: HTMLElement, actions: ReadonlyArray<IAction>, options: IMenuOptions, private readonly menuStyles: IMenuStyles) { container.classList.add('monaco-menu-container'); container.setAttribute('role', 'presentation'); const menuElement = document.createElement('div'); @@ -101,7 +115,7 @@ export class Menu extends ActionBar { this.menuDisposables = this._register(new DisposableStore()); - this.initializeOrUpdateStyleSheet(container, {}); + this.initializeOrUpdateStyleSheet(container, menuStyles); this._register(Gesture.addTarget(menuElement)); @@ -229,6 +243,8 @@ export class Menu extends ActionBar { const scrollElement = this.scrollableElement.getDomNode(); scrollElement.style.position = ''; + this.styleScrollElement(scrollElement, menuStyles); + // Support scroll on menu drag this._register(addDisposableListener(menuElement, TouchEventType.Change, e => { EventHelper.stop(e, true); @@ -278,30 +294,19 @@ export class Menu extends ActionBar { this.styleSheet.textContent = getMenuWidgetCSS(style, isInShadowDOM(container)); } - style(style: IMenuStyles): void { - const container = this.getContainer(); + private styleScrollElement(scrollElement: HTMLElement, style: IMenuStyles): void { - this.initializeOrUpdateStyleSheet(container, style); - - const fgColor = style.foregroundColor ? `${style.foregroundColor}` : ''; - const bgColor = style.backgroundColor ? `${style.backgroundColor}` : ''; + const fgColor = style.foregroundColor ?? ''; + const bgColor = style.backgroundColor ?? ''; const border = style.borderColor ? `1px solid ${style.borderColor}` : ''; const borderRadius = '5px'; const shadow = style.shadowColor ? `0 2px 8px ${style.shadowColor}` : ''; - container.style.outline = border; - container.style.borderRadius = borderRadius; - container.style.color = fgColor; - container.style.backgroundColor = bgColor; - container.style.boxShadow = shadow; - - if (this.viewItems) { - this.viewItems.forEach(item => { - if (item instanceof BaseMenuActionViewItem || item instanceof MenuSeparatorActionViewItem) { - item.style(style); - } - }); - } + scrollElement.style.outline = border; + scrollElement.style.borderRadius = borderRadius; + scrollElement.style.color = fgColor; + scrollElement.style.backgroundColor = bgColor; + scrollElement.style.boxShadow = shadow; } override getContainer(): HTMLElement { @@ -364,9 +369,9 @@ export class Menu extends ActionBar { private doGetActionViewItem(action: IAction, options: IMenuOptions, parentData: ISubMenuData): BaseActionViewItem { if (action instanceof Separator) { - return new MenuSeparatorActionViewItem(options.context, action, { icon: true }); + return new MenuSeparatorActionViewItem(options.context, action, { icon: true }, this.menuStyles); } else if (action instanceof SubmenuAction) { - const menuActionViewItem = new SubmenuMenuActionViewItem(action, action.actions, parentData, { ...options, submenuIds: new Set([...(options.submenuIds || []), action.id]) }); + const menuActionViewItem = new SubmenuMenuActionViewItem(action, action.actions, parentData, { ...options, submenuIds: new Set([...(options.submenuIds || []), action.id]) }, this.menuStyles); if (options.enableMnemonics) { const mnemonic = menuActionViewItem.getMnemonic(); @@ -396,7 +401,7 @@ export class Menu extends ActionBar { } } - const menuActionViewItem = new BaseMenuActionViewItem(options.context, action, menuItemOptions); + const menuActionViewItem = new BaseMenuActionViewItem(options.context, action, menuItemOptions, this.menuStyles); if (options.enableMnemonics) { const mnemonic = menuActionViewItem.getMnemonic(); @@ -433,9 +438,8 @@ class BaseMenuActionViewItem extends BaseActionViewItem { private check: HTMLElement | undefined; private mnemonic: string | undefined; private cssClass: string; - protected menuStyle: IMenuStyles | undefined; - constructor(ctx: unknown, action: IAction, options: IMenuItemOptions = {}) { + constructor(ctx: unknown, action: IAction, options: IMenuItemOptions, protected readonly menuStyle: IMenuStyles) { options.isMenu = true; super(action, action, options); @@ -446,7 +450,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { // Set mnemonic if (this.options.label && options.enableMnemonics) { - const label = this.getAction().label; + const label = this.action.label; if (label) { const matches = MENU_MNEMONIC_REGEX.exec(label); if (matches) { @@ -525,7 +529,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - this.check = append(this.item, $('span.menu-item-check' + Codicon.menuSelection.cssSelector)); + this.check = append(this.item, $('span.menu-item-check' + ThemeIcon.asCSSSelector(Codicon.menuSelection))); this.check.setAttribute('role', 'none'); this.label = append(this.item, $('span.action-label')); @@ -542,6 +546,8 @@ class BaseMenuActionViewItem extends BaseActionViewItem { this.updateTooltip(); this.updateEnabled(); this.updateChecked(); + + this.applyStyle(); } override blur(): void { @@ -552,9 +558,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { override focus(): void { super.focus(); - if (this.item) { - this.item.focus(); - } + this.item?.focus(); this.applyStyle(); } @@ -566,7 +570,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - override updateLabel(): void { + protected override updateLabel(): void { if (!this.label) { return; } @@ -574,7 +578,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { if (this.options.label) { clearNode(this.label); - let label = stripIcons(this.getAction().label); + let label = stripIcons(this.action.label); if (label) { const cleanLabel = cleanMnemonic(label); if (!this.options.enableMnemonics) { @@ -617,16 +621,16 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - override updateTooltip(): void { + protected override updateTooltip(): void { // menus should function like native menus and they do not have tooltips } - override updateClass(): void { + protected override updateClass(): void { if (this.cssClass && this.item) { this.item.classList.remove(...this.cssClass.split(' ')); } if (this.options.icon && this.label) { - this.cssClass = this.getAction().class || ''; + this.cssClass = this.action.class || ''; this.label.classList.add('icon'); if (this.cssClass) { this.label.classList.add(...this.cssClass.split(' ')); @@ -637,8 +641,8 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - override updateEnabled(): void { - if (this.getAction().enabled) { + protected override updateEnabled(): void { + if (this.action.enabled) { if (this.element) { this.element.classList.remove('disabled'); this.element.removeAttribute('aria-disabled'); @@ -662,12 +666,12 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } } - override updateChecked(): void { + protected override updateChecked(): void { if (!this.item) { return; } - const checked = this.getAction().checked; + const checked = this.action.checked; this.item.classList.toggle('checked', !!checked); if (checked !== undefined) { this.item.setAttribute('role', 'menuitemcheckbox'); @@ -683,10 +687,6 @@ class BaseMenuActionViewItem extends BaseActionViewItem { } protected applyStyle(): void { - if (!this.menuStyle) { - return; - } - const isSelected = this.element && this.element.classList.contains('focused'); const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : undefined; @@ -694,21 +694,16 @@ class BaseMenuActionViewItem extends BaseActionViewItem { const outlineOffset = isSelected && this.menuStyle.selectionBorderColor ? `-1px` : ''; if (this.item) { - this.item.style.color = fgColor ? fgColor.toString() : ''; - this.item.style.backgroundColor = bgColor ? bgColor.toString() : ''; + this.item.style.color = fgColor ?? ''; + this.item.style.backgroundColor = bgColor ?? ''; this.item.style.outline = outline; this.item.style.outlineOffset = outlineOffset; } if (this.check) { - this.check.style.color = fgColor ? fgColor.toString() : ''; + this.check.style.color = fgColor ?? ''; } } - - style(style: IMenuStyles): void { - this.menuStyle = style; - this.applyStyle(); - } } class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { @@ -725,9 +720,10 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { action: IAction, private submenuActions: ReadonlyArray<IAction>, private parentData: ISubMenuData, - private submenuOptions?: IMenuOptions + private submenuOptions: IMenuOptions, + menuStyles: IMenuStyles ) { - super(action, action, submenuOptions); + super(action, action, submenuOptions, menuStyles); this.expandDirection = submenuOptions && submenuOptions.expandDirection !== undefined ? submenuOptions.expandDirection : Direction.Right; @@ -758,7 +754,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.item.tabIndex = 0; this.item.setAttribute('aria-haspopup', 'true'); this.updateAriaExpanded('false'); - this.submenuIndicator = append(this.item, $('span.submenu-indicator' + Codicon.menuSubmenu.cssSelector)); + this.submenuIndicator = append(this.item, $('span.submenu-indicator' + ThemeIcon.asCSSSelector(Codicon.menuSubmenu))); this.submenuIndicator.setAttribute('aria-hidden', 'true'); } @@ -807,7 +803,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { })); } - override updateEnabled(): void { + protected override updateEnabled(): void { // override on submenu entry // native menus do not observe enablement on sumbenus // we mimic that behavior @@ -890,10 +886,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { this.submenuContainer.style.top = '0'; this.submenuContainer.style.left = '0'; - this.parentData.submenu = new Menu(this.submenuContainer, this.submenuActions.length ? this.submenuActions : [new EmptySubmenuAction()], this.submenuOptions); - if (this.menuStyle) { - this.parentData.submenu.style(this.menuStyle); - } + this.parentData.submenu = new Menu(this.submenuContainer, this.submenuActions.length ? this.submenuActions : [new EmptySubmenuAction()], this.submenuOptions, this.menuStyle); // layout submenu const entryBox = this.element.getBoundingClientRect(); @@ -953,18 +946,12 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { protected override applyStyle(): void { super.applyStyle(); - if (!this.menuStyle) { - return; - } - const isSelected = this.element && this.element.classList.contains('focused'); const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor; if (this.submenuIndicator) { - this.submenuIndicator.style.color = fgColor ? `${fgColor}` : ''; + this.submenuIndicator.style.color = fgColor ?? ''; } - - this.parentData.submenu?.style(this.menuStyle); } override dispose(): void { @@ -984,9 +971,14 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { } class MenuSeparatorActionViewItem extends ActionViewItem { - style(style: IMenuStyles): void { + constructor(context: unknown, action: IAction, options: IActionViewItemOptions, private readonly menuStyles: IMenuStyles) { + super(context, action, options); + } + + override render(container: HTMLElement): void { + super.render(container); if (this.label) { - this.label.style.borderBottomColor = style.separatorColor ? `${style.separatorColor}` : ''; + this.label.style.borderBottomColor = this.menuStyles.separatorColor ? `${this.menuStyles.separatorColor}` : ''; } } } @@ -1004,6 +996,11 @@ export function cleanMnemonic(label: string): string { return label.replace(regex, mnemonicInText ? '$2$3' : '').trim(); } +export function formatRule(c: ThemeIcon) { + const fontCharacter = getCodiconFontCharacters()[c.id]; + return `.codicon-${c.id}:before { content: '\\${fontCharacter.toString(16)}'; }`; +} + function getMenuWidgetCSS(style: IMenuStyles, isForShadowDom: boolean): string { let result = /* css */` .monaco-menu { @@ -1130,6 +1127,8 @@ ${formatRule(Codicon.menuSubmenu)} height: 2em; align-items: center; position: relative; + margin: 0 4px; + border-radius: 4px; } .monaco-menu .monaco-action-bar.vertical .action-menu-item:hover .keybinding, @@ -1253,7 +1252,7 @@ ${formatRule(Codicon.menuSubmenu)} /* Vertical Action Bar Styles */ .monaco-menu .monaco-action-bar.vertical { - padding: .6em 0; + padding: 4px 0; } .monaco-menu .monaco-action-bar.vertical .action-menu-item { diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index c8719d01cc..13f9dc4840 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -13,6 +13,7 @@ import { ActionRunner, IAction, IActionRunner, Separator, SubmenuAction } from ' import { asArray } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod, ScanCode, ScanCodeUtils } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; @@ -85,10 +86,9 @@ export class MenuBar extends Disposable { private readonly _onFocusStateChange: Emitter<boolean>; private numMenusShown: number = 0; - private menuStyle: IMenuStyles | undefined; private overflowLayoutScheduled: IDisposable | undefined = undefined; - constructor(private container: HTMLElement, private options: IMenuBarOptions = {}) { + constructor(private container: HTMLElement, private options: IMenuBarOptions, private menuStyle: IMenuStyles) { super(); this.container.setAttribute('role', 'menubar'); @@ -109,7 +109,7 @@ export class MenuBar extends Disposable { this.menuUpdater = this._register(new RunOnceScheduler(() => this.update(), 200)); this.actionRunner = this.options.actionRunner ?? this._register(new ActionRunner()); - this._register(this.actionRunner.onBeforeRun(() => { + this._register(this.actionRunner.onWillRun(() => { this.setUnfocusedState(); })); @@ -314,7 +314,7 @@ export class MenuBar extends Disposable { createOverflowMenu(): void { const label = this.isCompact ? nls.localize('mAppMenu', 'Application Menu') : nls.localize('mMore', 'More'); const buttonElement = $('div.menubar-menu-button', { 'role': 'menuitem', 'tabindex': this.isCompact ? 0 : -1, 'aria-label': label, 'aria-haspopup': true }); - const titleElement = $('div.menubar-menu-title.toolbar-toggle-more' + Codicon.menuBarMore.cssSelector, { 'role': 'none', 'aria-hidden': true }); + const titleElement = $('div.menubar-menu-title.toolbar-toggle-more' + ThemeIcon.asCSSSelector(Codicon.menuBarMore), { 'role': 'none', 'aria-hidden': true }); buttonElement.appendChild(titleElement); this.container.appendChild(buttonElement); @@ -607,10 +607,6 @@ export class MenuBar extends Disposable { } } - style(style: IMenuStyles): void { - this.menuStyle = style; - } - update(options?: IMenuBarOptions): void { if (options) { this.options = options; @@ -985,9 +981,7 @@ export class MenuBar extends Disposable { this.focusedMenu.holder.remove(); } - if (this.focusedMenu.widget) { - this.focusedMenu.widget.dispose(); - } + this.focusedMenu.widget?.dispose(); this.focusedMenu = { index: this.focusedMenu.index }; } @@ -1031,10 +1025,7 @@ export class MenuBar extends Disposable { useEventAsContext: true }; - const menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions)); - if (this.menuStyle) { - menuWidget.style(this.menuStyle); - } + const menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions, this.menuStyle)); this._register(menuWidget.onDidCancel(() => { this.focusState = MenubarState.FOCUSED; diff --git a/src/vs/base/browser/ui/progressbar/progressbar.ts b/src/vs/base/browser/ui/progressbar/progressbar.ts index 1ff9533eca..8f63ab97fa 100644 --- a/src/vs/base/browser/ui/progressbar/progressbar.ts +++ b/src/vs/base/browser/ui/progressbar/progressbar.ts @@ -5,9 +5,7 @@ import { hide, show } from 'vs/base/browser/dom'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { Color } from 'vs/base/common/color'; import { Disposable } from 'vs/base/common/lifecycle'; -import { mixin } from 'vs/base/common/objects'; import { isNumber } from 'vs/base/common/types'; import 'vs/css!./progressbar'; @@ -21,11 +19,11 @@ export interface IProgressBarOptions extends IProgressBarStyles { } export interface IProgressBarStyles { - progressBarBackground?: Color; + progressBarBackground: string | undefined; } -const defaultOpts = { - progressBarBackground: Color.fromHex('#0E70C0') +export const unthemedProgressBarOptions: IProgressBarOptions = { + progressBarBackground: undefined }; /** @@ -43,32 +41,25 @@ export class ProgressBar extends Disposable { */ private static readonly LONG_RUNNING_INFINITE_THRESHOLD = 10000; - private options: IProgressBarOptions; private workedVal: number; private element!: HTMLElement; private bit!: HTMLElement; private totalWork: number | undefined; - private progressBarBackground: Color | undefined; private showDelayedScheduler: RunOnceScheduler; private longRunningScheduler: RunOnceScheduler; constructor(container: HTMLElement, options?: IProgressBarOptions) { super(); - this.options = options || Object.create(null); - mixin(this.options, defaultOpts, false); - this.workedVal = 0; - this.progressBarBackground = this.options.progressBarBackground; - this.showDelayedScheduler = this._register(new RunOnceScheduler(() => show(this.element), 0)); this.longRunningScheduler = this._register(new RunOnceScheduler(() => this.infiniteLongRunning(), ProgressBar.LONG_RUNNING_INFINITE_THRESHOLD)); - this.create(container); + this.create(container, options); } - private create(container: HTMLElement): void { + private create(container: HTMLElement, options?: IProgressBarOptions): void { this.element = document.createElement('div'); this.element.classList.add('monaco-progress-container'); this.element.setAttribute('role', 'progressbar'); @@ -77,9 +68,8 @@ export class ProgressBar extends Disposable { this.bit = document.createElement('div'); this.bit.classList.add('progress-bit'); + this.bit.style.backgroundColor = options?.progressBarBackground || '#0E70C0'; this.element.appendChild(this.bit); - - this.applyStyles(); } private off(): void { @@ -223,18 +213,4 @@ export class ProgressBar extends Disposable { hide(this.element); this.showDelayedScheduler.cancel(); } - - style(styles: IProgressBarStyles): void { - this.progressBarBackground = styles.progressBarBackground; - - this.applyStyles(); - } - - protected applyStyles(): void { - if (this.bit) { - const background = this.progressBarBackground ? this.progressBarBackground.toString() : ''; - - this.bit.style.backgroundColor = background; - } - } } diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index c8773a44ad..cd67a692c4 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ :root { - --sash-size: 4px; + --vscode-sash-size: 4px; } .monaco-sash { @@ -49,7 +49,7 @@ .monaco-sash.vertical { cursor: ew-resize; top: 0; - width: var(--sash-size); + width: var(--vscode-sash-size); height: 100%; } @@ -57,13 +57,13 @@ cursor: ns-resize; left: 0; width: 100%; - height: var(--sash-size); + height: var(--vscode-sash-size); } .monaco-sash:not(.disabled) > .orthogonal-drag-handle { content: " "; - height: calc(var(--sash-size) * 2); - width: calc(var(--sash-size) * 2); + height: calc(var(--vscode-sash-size) * 2); + width: calc(var(--vscode-sash-size) * 2); z-index: 100; display: block; cursor: all-scroll; @@ -85,20 +85,20 @@ } .monaco-sash.vertical > .orthogonal-drag-handle.start { - left: calc(var(--sash-size) * -0.5); - top: calc(var(--sash-size) * -1); + left: calc(var(--vscode-sash-size) * -0.5); + top: calc(var(--vscode-sash-size) * -1); } .monaco-sash.vertical > .orthogonal-drag-handle.end { - left: calc(var(--sash-size) * -0.5); - bottom: calc(var(--sash-size) * -1); + left: calc(var(--vscode-sash-size) * -0.5); + bottom: calc(var(--vscode-sash-size) * -1); } .monaco-sash.horizontal > .orthogonal-drag-handle.start { - top: calc(var(--sash-size) * -0.5); - left: calc(var(--sash-size) * -1); + top: calc(var(--vscode-sash-size) * -0.5); + left: calc(var(--vscode-sash-size) * -1); } .monaco-sash.horizontal > .orthogonal-drag-handle.end { - top: calc(var(--sash-size) * -0.5); - right: calc(var(--sash-size) * -1); + top: calc(var(--vscode-sash-size) * -0.5); + right: calc(var(--vscode-sash-size) * -1); } .monaco-sash:before { @@ -107,18 +107,26 @@ position: absolute; width: 100%; height: 100%; - transition: background-color 0.1s ease-out; background: transparent; } +.monaco-workbench:not(.reduce-motion) .monaco-sash:before { + transition: background-color 0.1s ease-out; +} + +.monaco-sash.hover:before, +.monaco-sash.active:before { + background: var(--vscode-sash-hoverBorder); +} + .monaco-sash.vertical:before { - width: var(--sash-hover-size); - left: calc(50% - (var(--sash-hover-size) / 2)); + width: var(--vscode-sash-hover-size); + left: calc(50% - (var(--vscode-sash-hover-size) / 2)); } .monaco-sash.horizontal:before { - height: var(--sash-hover-size); - top: calc(50% - (var(--sash-hover-size) / 2)); + height: var(--vscode-sash-hover-size); + top: calc(50% - (var(--vscode-sash-hover-size) / 2)); } .pointer-events-disabled { diff --git a/src/vs/base/browser/ui/sash/sash.ts b/src/vs/base/browser/ui/sash/sash.ts index d194e90990..381ae601b1 100644 --- a/src/vs/base/browser/ui/sash/sash.ts +++ b/src/vs/base/browser/ui/sash/sash.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { $, append, createStyleSheet, EventHelper, EventLike, getElementsByTagName } from 'vs/base/browser/dom'; +import { $, append, createStyleSheet, EventHelper, EventLike } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; -import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch'; +import { EventType, Gesture } from 'vs/base/browser/touch'; import { Delayer } from 'vs/base/common/async'; import { memoize } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; @@ -55,6 +55,13 @@ export enum OrthogonalEdge { West = 'west' } +export interface IBoundarySashes { + readonly top?: Sash; + readonly right?: Sash; + readonly bottom?: Sash; + readonly left?: Sash; +} + export interface ISashOptions { /** @@ -155,6 +162,7 @@ interface PointerEvent extends EventLike { readonly pageY: number; readonly altKey: boolean; readonly target: EventTarget | null; + readonly initialTarget?: EventTarget | undefined; } interface IPointerEventFactory { @@ -420,17 +428,22 @@ export class Sash extends Disposable { this._register(Gesture.addTarget(this.el)); - const onTouchStart = Event.map(this._register(new DomEmitter(this.el, EventType.Start)).event, e => ({ ...e, target: e.initialTarget ?? null })); + const onTouchStart = this._register(new DomEmitter(this.el, EventType.Start)).event; this._register(onTouchStart(e => this.onPointerStart(e, new GestureEventFactory(this.el)), this)); const onTap = this._register(new DomEmitter(this.el, EventType.Tap)).event; - const onDoubleTap = Event.map( - Event.filter( - Event.debounce<GestureEvent, { event: GestureEvent; count: number }>(onTap, (res, event) => ({ event, count: (res?.count ?? 0) + 1 }), 250), - ({ count }) => count === 2 - ), - ({ event }) => ({ ...event, target: event.initialTarget ?? null }) - ); - this._register(onDoubleTap(this.onPointerDoublePress, this)); + + let doubleTapTimeout: any = undefined; + this._register(onTap(event => { + if (doubleTapTimeout) { + clearTimeout(doubleTapTimeout); + doubleTapTimeout = undefined; + this.onPointerDoublePress(event); + return; + } + + clearTimeout(doubleTapTimeout); + doubleTapTimeout = setTimeout(() => doubleTapTimeout = undefined, 250); + }, this)); if (typeof options.size === 'number') { this.size = options.size; @@ -494,7 +507,7 @@ export class Sash extends Disposable { return; } - const iframes = getElementsByTagName('iframe'); + const iframes = document.getElementsByTagName('iframe'); for (const iframe of iframes) { iframe.classList.add(PointerEventsDisabledCssClass); // disable mouse events on iframes as long as we drag the sash } @@ -663,12 +676,14 @@ export class Sash extends Disposable { } private getOrthogonalSash(e: PointerEvent): Sash | undefined { - if (!e.target || !(e.target instanceof HTMLElement)) { + const target = e.initialTarget ?? e.target; + + if (!target || !(target instanceof HTMLElement)) { return undefined; } - if (e.target.classList.contains('orthogonal-drag-handle')) { - return e.target.classList.contains('start') ? this.orthogonalStartSash : this.orthogonalEndSash; + if (target.classList.contains('orthogonal-drag-handle')) { + return target.classList.contains('start') ? this.orthogonalStartSash : this.orthogonalEndSash; } return undefined; diff --git a/src/vs/base/browser/ui/scrollbar/media/scrollbars.css b/src/vs/base/browser/ui/scrollbar/media/scrollbars.css index 27acf225a4..e165eeda97 100644 --- a/src/vs/base/browser/ui/scrollbar/media/scrollbars.css +++ b/src/vs/base/browser/ui/scrollbar/media/scrollbars.css @@ -16,6 +16,8 @@ background:rgba(0,0,0,0); transition: opacity 100ms linear; + /* In front of peek view */ + z-index: 11; } .monaco-scrollable-element > .invisible { opacity: 0; @@ -36,6 +38,7 @@ left: 3px; height: 3px; width: 100%; + box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset; } .monaco-scrollable-element > .shadow.left { display: block; @@ -43,6 +46,7 @@ left: 0; height: 100%; width: 3px; + box-shadow: var(--vscode-scrollbar-shadow) 6px 0 6px -6px inset; } .monaco-scrollable-element > .shadow.top-left-corner { display: block; @@ -51,3 +55,18 @@ height: 3px; width: 3px; } +.monaco-scrollable-element > .shadow.top.left { + box-shadow: var(--vscode-scrollbar-shadow) 6px 0 6px -6px inset; +} + +.monaco-scrollable-element > .scrollbar > .slider { + background: var(--vscode-scrollbarSlider-background); +} + +.monaco-scrollable-element > .scrollbar > .slider:hover { + background: var(--vscode-scrollbarSlider-hoverBackground); +} + +.monaco-scrollable-element > .scrollbar > .slider.active { + background: var(--vscode-scrollbarSlider-activeBackground); +} diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 0cbf432d7b..3ac18fad43 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -86,6 +86,17 @@ export class MouseWheelClassifier { return (score <= 0.5); } + public acceptStandardWheelEvent(e: StandardWheelEvent): void { + const osZoomFactor = window.devicePixelRatio / getZoomFactor(); + if (platform.isWindows || platform.isLinux) { + // On Windows and Linux, the incoming delta events are multiplied with the OS zoom factor. + // The OS zoom factor can be reverse engineered by using the device pixel ratio and the configured zoom factor into account. + this.accept(Date.now(), e.deltaX / osZoomFactor, e.deltaY / osZoomFactor); + } else { + this.accept(Date.now(), e.deltaX, e.deltaY); + } + } + public accept(timestamp: number, deltaX: number, deltaY: number): void { const item = new MouseWheelClassifierItem(timestamp, deltaX, deltaY); item.score = this._computeScore(item); @@ -334,7 +345,7 @@ export abstract class AbstractScrollableElement extends Widget { this._revealOnScroll = value; } - public triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { + public delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { this._onMouseWheel(new StandardWheelEvent(browserEvent)); } @@ -365,14 +376,7 @@ export abstract class AbstractScrollableElement extends Widget { const classifier = MouseWheelClassifier.INSTANCE; if (SCROLL_WHEEL_SMOOTH_SCROLL_ENABLED) { - const osZoomFactor = window.devicePixelRatio / getZoomFactor(); - if (platform.isWindows || platform.isLinux) { - // On Windows and Linux, the incoming delta events are multiplied with the OS zoom factor. - // The OS zoom factor can be reverse engineered by using the device pixel ratio and the configured zoom factor into account. - classifier.accept(Date.now(), e.deltaX / osZoomFactor, e.deltaY / osZoomFactor); - } else { - classifier.accept(Date.now(), e.deltaX, e.deltaY); - } + classifier.acceptStandardWheelEvent(e); } // console.log(`${Date.now()}, ${e.deltaY}, ${e.deltaX}`); diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts b/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts index bbcd1c27fa..ebeb6c3601 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts @@ -6,7 +6,7 @@ import { GlobalPointerMoveMonitor } from 'vs/base/browser/globalPointerMoveMonitor'; import { Widget } from 'vs/base/browser/ui/widget'; import { IntervalTimer, TimeoutTimer } from 'vs/base/common/async'; -import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import * as dom from 'vs/base/browser/dom'; /** @@ -17,7 +17,7 @@ export const ARROW_IMG_SIZE = 11; export interface ScrollbarArrowOptions { onActivate: () => void; className: string; - icon: Codicon; + icon: ThemeIcon; bgWidth: number; bgHeight: number; @@ -61,7 +61,7 @@ export class ScrollbarArrow extends Widget { this.domNode = document.createElement('div'); this.domNode.className = opts.className; - this.domNode.classList.add(...opts.icon.classNamesArray); + this.domNode.classList.add(...ThemeIcon.asClassNameArray(opts.icon)); this.domNode.style.position = 'absolute'; this.domNode.style.width = ARROW_IMG_SIZE + 'px'; diff --git a/src/vs/base/browser/ui/selectBox/selectBox.css b/src/vs/base/browser/ui/selectBox/selectBox.css index 0a20136f6e..76b9ec5323 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.css +++ b/src/vs/base/browser/ui/selectBox/selectBox.css @@ -5,6 +5,8 @@ .monaco-select-box { width: 100%; + cursor: pointer; + border-radius: 2px; } .monaco-select-box-dropdown-container { @@ -21,7 +23,7 @@ .monaco-action-bar .action-item .monaco-select-box { cursor: pointer; - min-width: 110px; + min-width: 100px; min-height: 18px; padding: 2px 23px 2px 8px; } diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 2e6d27f259..fe4b6821e9 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -5,14 +5,12 @@ import { IContentActionHandler } from 'vs/base/browser/formattedTextRenderer'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { IListStyles, unthemedListStyles } from 'vs/base/browser/ui/list/listWidget'; import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom'; import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative'; import { Widget } from 'vs/base/browser/ui/widget'; -import { Color } from 'vs/base/common/color'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { deepClone } from 'vs/base/common/objects'; import { isMacintosh } from 'vs/base/common/platform'; import 'vs/css!./selectBox'; @@ -33,8 +31,6 @@ export interface ISelectBoxDelegate extends IDisposable { // Delegated Widget interface render(container: HTMLElement): void; - style(styles: ISelectBoxStyles): void; - applyStyles(): void; } export interface ISelectBoxOptions { @@ -57,19 +53,24 @@ export interface ISelectOptionItem { } export interface ISelectBoxStyles extends IListStyles { - selectBackground?: Color; - selectListBackground?: Color; - selectForeground?: Color; - decoratorRightForeground?: Color; - selectBorder?: Color; - selectListBorder?: Color; - focusBorder?: Color; + readonly selectBackground: string | undefined; + readonly selectListBackground: string | undefined; + readonly selectForeground: string | undefined; + readonly decoratorRightForeground: string | undefined; + readonly selectBorder: string | undefined; + readonly selectListBorder: string | undefined; + readonly focusBorder: string | undefined; } -export const defaultStyles = { - selectBackground: Color.fromHex('#3C3C3C'), - selectForeground: Color.fromHex('#F0F0F0'), - selectBorder: Color.fromHex('#3C3C3C') +export const unthemedSelectBoxStyles: ISelectBoxStyles = { + ...unthemedListStyles, + selectBackground: '#3C3C3C', + selectForeground: '#F0F0F0', + selectBorder: '#3C3C3C', + decoratorRightForeground: undefined, + selectListBackground: undefined, + selectListBorder: undefined, + focusBorder: undefined, }; export interface ISelectData { @@ -79,12 +80,9 @@ export interface ISelectData { export class SelectBox extends Widget implements ISelectBoxDelegate { protected selectElement: HTMLSelectElement; // {{SQL CARBON EDIT}} - protected selectBackground?: Color; - protected selectForeground?: Color; - protected selectBorder?: Color; protected selectBoxDelegate: ISelectBoxDelegate; // {{SQL CARBON EDIT}} Make protected so we can hook into keyboard events - constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles), selectBoxOptions?: ISelectBoxOptions) { + constructor(options: ISelectOptionItem[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) { super(); // Default to native SelectBox for OSX unless overridden @@ -134,14 +132,6 @@ export class SelectBox extends Widget implements ISelectBoxDelegate { this.selectBoxDelegate.render(container); } - style(styles: ISelectBoxStyles): void { - this.selectBoxDelegate.style(styles); - } - - applyStyles(): void { - this.selectBoxDelegate.applyStyles(); - } - // {{SQL CARBON EDIT}} protected createOption(value: string, disabled?: boolean): HTMLOptionElement { let option = document.createElement('option'); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css index 889a70b4d2..b34fbb27b6 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css @@ -41,6 +41,8 @@ text-align: left; width: 1px; overflow: hidden; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; } .monaco-select-box-dropdown-container > .select-box-dropdown-list-container { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 4cf69a9606..0cbd536dfa 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -15,7 +15,7 @@ import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData, I import * as arrays from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyCodeUtils } from 'vs/base/common/keyCodes'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import 'vs/css!./selectBoxCustom'; @@ -31,7 +31,6 @@ interface ISelectListTemplateData { text: HTMLElement; detail: HTMLElement; decoratorRight: HTMLElement; - disposables: IDisposable[]; } class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectListTemplateData> { @@ -40,7 +39,6 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList renderTemplate(container: HTMLElement): ISelectListTemplateData { const data: ISelectListTemplateData = Object.create(null); - data.disposables = []; data.root = container; data.text = dom.append(container, $('.option-text')); data.detail = dom.append(container, $('.option-detail')); @@ -72,8 +70,8 @@ class SelectListRenderer implements IListRenderer<ISelectOptionItem, ISelectList } } - disposeTemplate(templateData: ISelectListTemplateData): void { - templateData.disposables = dispose(templateData.disposables); + disposeTemplate(_templateData: ISelectListTemplateData): void { + // noop } } @@ -90,7 +88,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private options: ISelectOptionItem[] = []; private selected: number; private readonly _onDidSelect: Emitter<ISelectData>; - private styles: ISelectBoxStyles; + private readonly styles: ISelectBoxStyles; private listRenderer!: SelectListRenderer; private contextViewProvider!: IContextViewProvider; public selectDropDownContainer!: HTMLElement; // {{SQL CARBON EDIT}} Make public so we can hook into keyboard events @@ -103,6 +101,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi private _hasDetails: boolean = false; private selectionDetailsPane!: HTMLElement; private _skipLayout: boolean = false; + private _cachedMaxDetailsHeight?: number; private _sticky: boolean = false; // for dev purposes only @@ -110,6 +109,8 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi super(); this._isVisible = false; + this.styles = styles; + this.selectBoxOptions = selectBoxOptions || Object.create(null); if (typeof this.selectBoxOptions.minBottomMargin !== 'number') { @@ -138,8 +139,6 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this._onDidSelect = new Emitter<ISelectData>(); this._register(this._onDidSelect); - this.styles = styles; - this.registerListeners(); this.constructSelectDropDown(contextViewProvider); @@ -149,12 +148,14 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.setOptions(options, selected); } + this.initStyleSheet(); + } // IDelegate - List renderer getHeight(): number { - return 18; + return 22; } getTemplateId(): string { @@ -274,6 +275,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.options = options; this.selectElement.options.length = 0; this._hasDetails = false; + this._cachedMaxDetailsHeight = undefined; this.options.forEach((option, index) => { this.selectElement.add(this.createOption(option.text, index, option.isDisabled)); @@ -344,15 +346,13 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.container = container; container.classList.add('select-container'); container.appendChild(this.selectElement); - this.applyStyles(); + this.styleSelectElement(); } - public style(styles: ISelectBoxStyles): void { + private initStyleSheet(): void { const content: string[] = []; - this.styles = styles; - // Style non-native select mode if (this.styles.listFocusBackground) { @@ -367,7 +367,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:not(.focused) .option-decorator-right { color: ${this.styles.decoratorRightForeground}; }`); } - if (this.styles.selectBackground && this.styles.selectBorder && !this.styles.selectBorder.equals(this.styles.selectBackground)) { + if (this.styles.selectBackground && this.styles.selectBorder && this.styles.selectBorder !== this.styles.selectBackground) { content.push(`.monaco-select-box-dropdown-container { border: 1px solid ${this.styles.selectBorder} } `); content.push(`.monaco-select-box-dropdown-container > .select-box-details-pane.border-top { border-top: 1px solid ${this.styles.selectBorder} } `); content.push(`.monaco-select-box-dropdown-container > .select-box-details-pane.border-bottom { border-bottom: 1px solid ${this.styles.selectBorder} } `); @@ -402,52 +402,29 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi content.push(`.monaco-select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: transparent !important; color: inherit !important; outline: none !important; }`); this.styleElement.textContent = content.join('\n'); - - this.applyStyles(); } - public applyStyles(): void { + private styleSelectElement(): void { + const background = this.styles.selectBackground ?? ''; + const foreground = this.styles.selectForeground ?? ''; + const border = this.styles.selectBorder ?? ''; - // Style parent select - - // {{SQL CARBON EDIT}} - let background = ''; - let foreground = ''; - let border = ''; - - if (this.selectElement) { - if (this.selectElement.disabled) { - background = (<any>this.styles).disabledSelectBackground ? (<any>this.styles).disabledSelectBackground.toString() : ''; - foreground = (<any>this.styles).disabledSelectForeground ? (<any>this.styles).disabledSelectForeground.toString() : ''; - } else { - background = this.styles.selectBackground ? this.styles.selectBackground.toString() : ''; - foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : ''; - border = this.styles.selectBorder ? this.styles.selectBorder.toString() : ''; - } - this.selectElement.style.backgroundColor = background; - this.selectElement.style.color = foreground; - this.selectElement.style.borderColor = border; - } - - // Style drop down select list (non-native mode only) - - if (this.selectList) { - this.styleList(); - } + this.selectElement.style.backgroundColor = background; + this.selectElement.style.color = foreground; + this.selectElement.style.borderColor = border; } private styleList() { - if (this.selectList) { - const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : ''; - this.selectList.style({}); + const background = this.styles.selectBackground ?? ''; - const listBackground = this.styles.selectListBackground ? this.styles.selectListBackground.toString() : background; - this.selectDropDownListContainer.style.backgroundColor = listBackground; - this.selectionDetailsPane.style.backgroundColor = listBackground; - const optionsBorder = this.styles.focusBorder ? this.styles.focusBorder.toString() : ''; - this.selectDropDownContainer.style.outlineColor = optionsBorder; - this.selectDropDownContainer.style.outlineOffset = '-1px'; - } + const listBackground = dom.asCssValueWithDefault(this.styles.selectListBackground, background); + this.selectDropDownListContainer.style.backgroundColor = listBackground; + this.selectionDetailsPane.style.backgroundColor = listBackground; + const optionsBorder = this.styles.focusBorder ?? ''; + this.selectDropDownContainer.style.outlineColor = optionsBorder; + this.selectDropDownContainer.style.outlineOffset = '-1px'; + + this.selectList.style(this.styles); } private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement { @@ -592,7 +569,10 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi this.selectList.layout(); let listHeight = this.selectList.contentHeight; - const maxDetailsPaneHeight = this._hasDetails ? this.measureMaxDetailsHeight() : 0; + if (this._hasDetails && this._cachedMaxDetailsHeight === undefined) { + this._cachedMaxDetailsHeight = this.measureMaxDetailsHeight(); + } + const maxDetailsPaneHeight = this._hasDetails ? this._cachedMaxDetailsHeight! : 0; const minRequiredDropDownHeight = listHeight + verticalPadding + maxDetailsPaneHeight; const maxVisibleOptionsBelow = ((Math.floor((maxSelectDropDownHeightBelow - verticalPadding - maxDetailsPaneHeight) / this.getHeight()))); @@ -768,7 +748,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi return label; }, getWidgetAriaLabel: () => localize({ key: 'selectBox', comment: ['Behave like native select dropdown element.'] }, "Select Box"), - getRole: () => 'option', + getRole: () => isMacintosh ? '' : 'option', getWidgetRole: () => 'listbox' } }); @@ -897,7 +877,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi } }; - const rendered = renderMarkdown({ value: text }, { actionHandler }); + const rendered = renderMarkdown({ value: text, supportThemeIcons: true }, { actionHandler }); rendered.element.classList.add('select-box-description-markdown'); cleanRenderedMarkdown(rendered.element); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts index 986175c885..818ac008b4 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -169,13 +169,9 @@ export class SelectBoxNative extends Disposable implements ISelectBoxDelegate { // Style native select if (this.selectElement) { - const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : ''; - const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : ''; - const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : ''; - - this.selectElement.style.backgroundColor = background; - this.selectElement.style.color = foreground; - this.selectElement.style.borderColor = border; + this.selectElement.style.backgroundColor = this.styles.selectBackground ?? ''; + this.selectElement.style.color = this.styles.selectForeground ?? ''; + this.selectElement.style.borderColor = this.styles.selectBorder ?? ''; } } diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index a9a56b0975..bccfb91d8f 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -9,7 +9,7 @@ import { $, addDisposableListener, append, clearNode, EventHelper, EventType, tr import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; -import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { IBoundarySashes, Orientation } from 'vs/base/browser/ui/sash/sash'; import { Color, RGBA } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -29,11 +29,11 @@ export interface IPaneOptions { } export interface IPaneStyles { - dropBackground?: Color; - headerForeground?: Color; - headerBackground?: Color; - headerBorder?: Color; - leftBorder?: Color; + readonly dropBackground: string | undefined; + readonly headerForeground: string | undefined; + readonly headerBackground: string | undefined; + readonly headerBorder: string | undefined; + readonly leftBorder: string | undefined; } /** @@ -58,10 +58,17 @@ export abstract class Pane extends Disposable implements IView { private expandedSize: number | undefined = undefined; private _headerVisible = true; + private _bodyRendered = false; private _minimumBodySize: number; private _maximumBodySize: number; - private ariaHeaderLabel: string; - private styles: IPaneStyles = {}; + private _ariaHeaderLabel: string; + private styles: IPaneStyles = { + dropBackground: undefined, + headerBackground: undefined, + headerBorder: undefined, + headerForeground: undefined, + leftBorder: undefined + }; private animationTimer: number | undefined = undefined; private readonly _onDidChange = this._register(new Emitter<number | undefined>()); @@ -70,6 +77,15 @@ export abstract class Pane extends Disposable implements IView { private readonly _onDidChangeExpansionState = this._register(new Emitter<boolean>()); readonly onDidChangeExpansionState: Event<boolean> = this._onDidChangeExpansionState.event; + get ariaHeaderLabel(): string { + return this._ariaHeaderLabel; + } + + set ariaHeaderLabel(newLabel: string) { + this._ariaHeaderLabel = newLabel; + this.header.setAttribute('aria-label', this.ariaHeaderLabel); + } + get draggableElement(): HTMLElement { return this.header; } @@ -78,9 +94,8 @@ export abstract class Pane extends Disposable implements IView { return this.element; } - private _dropBackground: Color | undefined; - get dropBackground(): Color | undefined { - return this._dropBackground; + get dropBackground(): string | undefined { + return this.styles.dropBackground; } get minimumBodySize(): number { @@ -127,7 +142,7 @@ export abstract class Pane extends Disposable implements IView { super(); this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded; this._orientation = typeof options.orientation === 'undefined' ? Orientation.VERTICAL : options.orientation; - this.ariaHeaderLabel = localize('viewSection', "{0} Section", options.title); + this._ariaHeaderLabel = localize('viewSection', "{0} Section", options.title); this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : this._orientation === Orientation.HORIZONTAL ? 200 : 120; this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY; @@ -149,6 +164,11 @@ export abstract class Pane extends Disposable implements IView { this.updateHeader(); if (expanded) { + if (!this._bodyRendered) { + this.renderBody(this.body); + this._bodyRendered = true; + } + if (typeof this.animationTimer === 'number') { clearTimeout(this.animationTimer); } @@ -240,7 +260,13 @@ export abstract class Pane extends Disposable implements IView { }); this.body = append(this.element, $('.pane-body')); - this.renderBody(this.body); + + // Only render the body if it will be visible + // Otherwise, render it when the pane is expanded + if (!this._bodyRendered && this.isExpanded()) { + this.renderBody(this.body); + this._bodyRendered = true; + } if (!this.isExpanded()) { this.body.remove(); @@ -278,10 +304,9 @@ export abstract class Pane extends Disposable implements IView { this.header.classList.toggle('expanded', expanded); this.header.setAttribute('aria-expanded', String(expanded)); - this.header.style.color = this.styles.headerForeground ? this.styles.headerForeground.toString() : ''; - this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : ''; + this.header.style.color = this.styles.headerForeground ?? ''; + this.header.style.backgroundColor = this.styles.headerBackground ?? ''; this.header.style.borderTop = this.styles.headerBorder && this.orientation === Orientation.VERTICAL ? `1px solid ${this.styles.headerBorder}` : ''; - this._dropBackground = this.styles.dropBackground; this.element.style.borderLeft = this.styles.leftBorder && this.orientation === Orientation.HORIZONTAL ? `1px solid ${this.styles.leftBorder}` : ''; } @@ -395,7 +420,7 @@ class PaneDraggable extends Disposable { let backgroundColor: string | null = null; if (this.dragOverCounter > 0) { - backgroundColor = (this.pane.dropBackground || PaneDraggable.DefaultDragOverBackgroundColor).toString(); + backgroundColor = this.pane.dropBackground ?? PaneDraggable.DefaultDragOverBackgroundColor.toString(); } this.pane.dropTargetElement.style.backgroundColor = backgroundColor || ''; @@ -443,6 +468,7 @@ export class PaneView extends Disposable { readonly onDidDrop: Event<{ from: Pane; to: Pane }> = this._onDidDrop.event; orientation: Orientation; + private boundarySashes: IBoundarySashes | undefined; readonly onDidSashChange: Event<number>; readonly onDidSashReset: Event<number>; readonly onDidScroll: Event<ScrollEvent>; @@ -540,6 +566,20 @@ export class PaneView extends Disposable { this.splitview.layout(this.size); } + setBoundarySashes(sashes: IBoundarySashes) { + this.boundarySashes = sashes; + this.updateSplitviewOrthogonalSashes(sashes); + } + + private updateSplitviewOrthogonalSashes(sashes: IBoundarySashes | undefined) { + if (this.orientation === Orientation.VERTICAL) { + this.splitview.orthogonalStartSash = sashes?.left; + this.splitview.orthogonalEndSash = sashes?.right; + } else { + this.splitview.orthogonalEndSash = sashes?.bottom; + } + } + flipOrientation(height: number, width: number): void { this.orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; const paneSizes = this.paneItems.map(pane => this.getPaneSize(pane.pane)); @@ -548,6 +588,7 @@ export class PaneView extends Disposable { clearNode(this.element); this.splitview = this._register(new SplitView(this.element, { orientation: this.orientation })); + this.updateSplitviewOrthogonalSashes(this.boundarySashes); const newOrthogonalSize = this.orientation === Orientation.VERTICAL ? width : height; const newSize = this.orientation === Orientation.HORIZONTAL ? width : height; diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 55ec3417a0..a427e840c0 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { $, addDisposableListener, append, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { DomEmitter } from 'vs/base/browser/event'; import { ISashEvent as IBaseSashEvent, Orientation, Sash, SashState } from 'vs/base/browser/ui/sash/sash'; import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { pushToEnd, pushToStart, range } from 'vs/base/common/arrays'; @@ -52,7 +53,7 @@ export interface IView<TLayoutContext = undefined> { readonly minimumSize: number; /** - * A minimum size for this view. + * A maximum size for this view. * * @remarks If none, set it to `Number.POSITIVE_INFINITY`. */ @@ -66,6 +67,14 @@ export interface IView<TLayoutContext = undefined> { */ readonly priority?: LayoutPriority; + /** + * If the {@link SplitView} supports {@link ISplitViewOptions.proportionalLayout proportional layout}, + * this property allows for finer control over the proportional layout algorithm, per view. + * + * @defaultValue `true` + */ + readonly proportionalLayout?: boolean; + /** * Whether the view will snap whenever the user reaches its minimum size or * attempts to grow it beyond the minimum size. @@ -241,6 +250,7 @@ abstract class ViewItem<TLayoutContext> { get viewMaximumSize(): number { return this.view.maximumSize; } get priority(): LayoutPriority | undefined { return this.view.priority; } + get proportionalLayout(): boolean { return this.view.proportionalLayout ?? true; } get snap(): boolean { return !!this.view.snap; } set enabled(enabled: boolean) { @@ -411,9 +421,9 @@ export class SplitView<TLayoutContext = undefined> extends Disposable { private size = 0; private layoutContext: TLayoutContext | undefined; private contentSize = 0; - private proportions: undefined | number[] = undefined; + private proportions: (number | undefined)[] | undefined = undefined; private viewItems: ViewItem<TLayoutContext>[] = []; - private sashItems: ISashItem[] = []; + sashItems: ISashItem[] = []; // used in tests private sashDragState: ISashDragState | undefined; private state: State = State.Idle; private inverseAltBehavior: boolean; @@ -554,10 +564,27 @@ export class SplitView<TLayoutContext = undefined> extends Disposable { horizontal: this.orientation === Orientation.HORIZONTAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden }, this.scrollable)); + // https://github.com/microsoft/vscode/issues/157737 + const onDidScrollViewContainer = this._register(new DomEmitter(this.viewContainer, 'scroll')).event; + this._register(onDidScrollViewContainer(_ => { + const position = this.scrollableElement.getScrollPosition(); + const scrollLeft = Math.abs(this.viewContainer.scrollLeft - position.scrollLeft) <= 1 ? undefined : this.viewContainer.scrollLeft; + const scrollTop = Math.abs(this.viewContainer.scrollTop - position.scrollTop) <= 1 ? undefined : this.viewContainer.scrollTop; + + if (scrollLeft !== undefined || scrollTop !== undefined) { + this.scrollableElement.setScrollPosition({ scrollLeft, scrollTop }); + } + })); + this.onDidScroll = this.scrollableElement.onScroll; this._register(this.onDidScroll(e => { - this.viewContainer.scrollTop = e.scrollTop; - this.viewContainer.scrollLeft = e.scrollLeft; + if (e.scrollTopChanged) { + this.viewContainer.scrollTop = e.scrollTop; + } + + if (e.scrollLeftChanged) { + this.viewContainer.scrollLeft = e.scrollLeft; + } })); append(this.el, this.scrollableElement.getDomNode()); @@ -747,9 +774,26 @@ export class SplitView<TLayoutContext = undefined> extends Disposable { this.resize(this.viewItems.length - 1, size - previousSize, undefined, lowPriorityIndexes, highPriorityIndexes); } else { + let total = 0; + for (let i = 0; i < this.viewItems.length; i++) { const item = this.viewItems[i]; - item.size = clamp(Math.round(this.proportions[i] * size), item.minimumSize, item.maximumSize); + const proportion = this.proportions[i]; + + if (typeof proportion === 'number') { + total += proportion; + } else { + size -= item.size; + } + } + + for (let i = 0; i < this.viewItems.length; i++) { + const item = this.viewItems[i]; + const proportion = this.proportions[i]; + + if (typeof proportion === 'number') { + item.size = clamp(Math.round(proportion * size / total), item.minimumSize, item.maximumSize); + } } } @@ -759,7 +803,7 @@ export class SplitView<TLayoutContext = undefined> extends Disposable { private saveProportions(): void { if (this.proportionalLayout && this.contentSize > 0) { - this.proportions = this.viewItems.map(i => i.size / this.contentSize); + this.proportions = this.viewItems.map(i => i.proportionalLayout ? i.size / this.contentSize : undefined); } } @@ -1339,12 +1383,14 @@ export class SplitView<TLayoutContext = undefined> extends Disposable { } override dispose(): void { - super.dispose(); + this.sashDragState?.disposable.dispose(); dispose(this.viewItems); this.viewItems = []; this.sashItems.forEach(i => i.disposable.dispose()); this.sashItems = []; + + super.dispose(); } } diff --git a/src/vs/base/browser/ui/table/table.css b/src/vs/base/browser/ui/table/table.css index 615eca2061..9d1fffdc27 100644 --- a/src/vs/base/browser/ui/table/table.css +++ b/src/vs/base/browser/ui/table/table.css @@ -10,6 +10,7 @@ height: 100%; width: 100%; white-space: nowrap; + overflow: hidden; } .monaco-table > .monaco-split-view2 { @@ -45,17 +46,12 @@ .monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { content: ""; position: absolute; - left: calc(var(--sash-size) / 2); + left: calc(var(--vscode-sash-size) / 2); width: 0; border-left: 1px solid transparent; } -.monaco-table > .monaco-split-view2, -.monaco-table > .monaco-split-view2 .monaco-sash.vertical::before { +.monaco-workbench:not(.reduce-motion) .monaco-table > .monaco-split-view2, +.monaco-workbench:not(.reduce-motion) .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/tableWidget.ts b/src/vs/base/browser/ui/table/tableWidget.ts index 1d08ab0f25..c2a2d4e6d8 100644 --- a/src/vs/base/browser/ui/table/tableWidget.ts +++ b/src/vs/base/browser/ui/table/tableWidget.ts @@ -5,14 +5,13 @@ import { $, append, clearNode, createStyleSheet, getContentHeight, getContentWidth } from 'vs/base/browser/dom'; import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IListOptions, IListOptionsUpdate, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; +import { IListOptions, IListOptionsUpdate, IListStyles, List, unthemedListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ISplitViewDescriptor, IView, Orientation, SplitView } from 'vs/base/browser/ui/splitview/splitview'; import { ITableColumn, ITableContextMenuEvent, ITableEvent, ITableGestureEvent, ITableMouseEvent, ITableRenderer, ITableTouchEvent, ITableVirtualDelegate } from 'vs/base/browser/ui/table/table'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; -import { IThemable } from 'vs/base/common/styler'; import 'vs/css!./table'; // TODO@joao @@ -140,7 +139,7 @@ export interface ITableOptions<TRow> extends IListOptions<TRow> { } export interface ITableOptionsUpdate extends IListOptionsUpdate { } export interface ITableStyles extends IListStyles { } -export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable { +export class Table<TRow> implements ISpliceable<TRow>, IDisposable { private static InstanceCount = 0; readonly domId = `table_id_${++Table.InstanceCount}`; @@ -221,14 +220,14 @@ export class Table<TRow> implements ISpliceable<TRow>, IThemable, IDisposable { }, null, this.disposables); this.styleElement = createStyleSheet(this.domNode); - this.style({}); + this.style(unthemedListStyles); } updateOptions(options: ITableOptionsUpdate): void { this.list.updateOptions(options); } - splice(start: number, deleteCount: number, elements: TRow[] = []): void { + splice(start: number, deleteCount: number, elements: readonly TRow[] = []): void { this.list.splice(start, deleteCount, elements); } diff --git a/src/vs/base/browser/ui/toggle/toggle.css b/src/vs/base/browser/ui/toggle/toggle.css index cade95931d..e9dd1aebc9 100644 --- a/src/vs/base/browser/ui/toggle/toggle.css +++ b/src/vs/base/browser/ui/toggle/toggle.css @@ -16,7 +16,6 @@ box-sizing: border-box; user-select: none; -webkit-user-select: none; - -ms-user-select: none; } .monaco-custom-toggle:hover { diff --git a/src/vs/base/browser/ui/toggle/toggle.ts b/src/vs/base/browser/ui/toggle/toggle.ts index 64a8303d7c..4db88354e8 100644 --- a/src/vs/base/browser/ui/toggle/toggle.ts +++ b/src/vs/base/browser/ui/toggle/toggle.ts @@ -7,49 +7,53 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { BaseActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Widget } from 'vs/base/browser/ui/widget'; import { IAction } from 'vs/base/common/actions'; -import { Codicon, CSSIcon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; +import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import 'vs/css!./toggle'; export interface IToggleOpts extends IToggleStyles { readonly actionClassName?: string; - readonly icon?: CSSIcon; + readonly icon?: ThemeIcon; readonly title: string; readonly isChecked: boolean; readonly notFocusable?: boolean; } export interface IToggleStyles { - inputActiveOptionBorder?: Color; - inputActiveOptionForeground?: Color; - inputActiveOptionBackground?: Color; + readonly inputActiveOptionBorder: string | undefined; + readonly inputActiveOptionForeground: string | undefined; + readonly inputActiveOptionBackground: string | undefined; } export interface ICheckboxStyles { - checkboxBackground?: Color; - checkboxBorder?: Color; - checkboxForeground?: Color; + readonly checkboxBackground: string | undefined; + readonly checkboxBorder: string | undefined; + readonly checkboxForeground: string | undefined; } -const defaultOpts = { - inputActiveOptionBorder: Color.fromHex('#007ACC00'), - inputActiveOptionForeground: Color.fromHex('#FFFFFF'), - inputActiveOptionBackground: Color.fromHex('#0E639C50') +export const unthemedToggleStyles = { + inputActiveOptionBorder: '#007ACC00', + inputActiveOptionForeground: '#FFFFFF', + inputActiveOptionBackground: '#0E639C50' }; export class ToggleActionViewItem extends BaseActionViewItem { protected readonly toggle: Toggle; - constructor(context: any, action: IAction, options: IActionViewItemOptions | undefined) { + constructor(context: any, action: IAction, options: IActionViewItemOptions) { super(context, action, options); + this.toggle = this._register(new Toggle({ actionClassName: this._action.class, isChecked: !!this._action.checked, title: (<IActionViewItemOptions>this.options).keybinding ? `${this._action.label} (${(<IActionViewItemOptions>this.options).keybinding})` : this._action.label, - notFocusable: true + notFocusable: true, + inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground, + inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder, + inputActiveOptionForeground: options.toggleStyles?.inputActiveOptionForeground, })); this._register(this.toggle.onChange(() => this._action.checked = !!this.toggle && this.toggle.checked)); } @@ -59,7 +63,7 @@ export class ToggleActionViewItem extends BaseActionViewItem { this.element.appendChild(this.toggle.domNode); } - override updateEnabled(): void { + protected override updateEnabled(): void { if (this.toggle) { if (this.isEnabled()) { this.toggle.enable(); @@ -69,7 +73,7 @@ export class ToggleActionViewItem extends BaseActionViewItem { } } - override updateChecked(): void { + protected override updateChecked(): void { this.toggle.checked = !!this._action.checked; } @@ -98,7 +102,7 @@ export class Toggle extends Widget { readonly onKeyDown: Event<IKeyboardEvent> = this._onKeyDown.event; private readonly _opts: IToggleOpts; - private _icon: CSSIcon | undefined; + private _icon: ThemeIcon | undefined; readonly domNode: HTMLElement; private _checked: boolean; @@ -106,13 +110,13 @@ export class Toggle extends Widget { constructor(opts: IToggleOpts) { super(); - this._opts = { ...defaultOpts, ...opts }; + this._opts = opts; this._checked = this._opts.isChecked; const classes = ['monaco-custom-toggle']; if (this._opts.icon) { this._icon = this._opts.icon; - classes.push(...CSSIcon.asClassNameArray(this._icon)); + classes.push(...ThemeIcon.asClassNameArray(this._icon)); } if (this._opts.actionClassName) { classes.push(...this._opts.actionClassName.split(' ')); @@ -141,7 +145,7 @@ export class Toggle extends Widget { } }); - this.ignoreGesture(this.domNode); + this._register(this.ignoreGesture(this.domNode)); this.onkeydown(this.domNode, (keyboardEvent) => { if (keyboardEvent.keyCode === KeyCode.Space || keyboardEvent.keyCode === KeyCode.Enter) { @@ -177,13 +181,13 @@ export class Toggle extends Widget { this.applyStyles(); } - setIcon(icon: CSSIcon | undefined): void { + setIcon(icon: ThemeIcon | undefined): void { if (this._icon) { - this.domNode.classList.remove(...CSSIcon.asClassNameArray(this._icon)); + this.domNode.classList.remove(...ThemeIcon.asClassNameArray(this._icon)); } this._icon = icon; if (this._icon) { - this.domNode.classList.add(...CSSIcon.asClassNameArray(this._icon)); + this.domNode.classList.add(...ThemeIcon.asClassNameArray(this._icon)); } } @@ -191,24 +195,11 @@ export class Toggle extends Widget { return 2 /*margin left*/ + 2 /*border*/ + 2 /*padding*/ + 16 /* icon width */; } - style(styles: IToggleStyles): void { - if (styles.inputActiveOptionBorder) { - this._opts.inputActiveOptionBorder = styles.inputActiveOptionBorder; - } - if (styles.inputActiveOptionForeground) { - this._opts.inputActiveOptionForeground = styles.inputActiveOptionForeground; - } - if (styles.inputActiveOptionBackground) { - this._opts.inputActiveOptionBackground = styles.inputActiveOptionBackground; - } - this.applyStyles(); - } - protected applyStyles(): void { if (this.domNode) { - this.domNode.style.borderColor = this._checked && this._opts.inputActiveOptionBorder ? this._opts.inputActiveOptionBorder.toString() : ''; - this.domNode.style.color = this._checked && this._opts.inputActiveOptionForeground ? this._opts.inputActiveOptionForeground.toString() : 'inherit'; - this.domNode.style.backgroundColor = this._checked && this._opts.inputActiveOptionBackground ? this._opts.inputActiveOptionBackground.toString() : ''; + this.domNode.style.borderColor = (this._checked && this._opts.inputActiveOptionBorder) || ''; + this.domNode.style.color = (this._checked && this._opts.inputActiveOptionForeground) || 'inherit'; + this.domNode.style.backgroundColor = (this._checked && this._opts.inputActiveOptionBackground) || ''; } } @@ -232,18 +223,18 @@ export class Checkbox extends Widget { readonly domNode: HTMLElement; - constructor(private title: string, private isChecked: boolean) { + constructor(private title: string, private isChecked: boolean, styles: ICheckboxStyles) { super(); - this.checkbox = new Toggle({ title: this.title, isChecked: this.isChecked, icon: Codicon.check, actionClassName: 'monaco-checkbox' }); + this.checkbox = new Toggle({ title: this.title, isChecked: this.isChecked, icon: Codicon.check, actionClassName: 'monaco-checkbox', ...unthemedToggleStyles }); this.domNode = this.checkbox.domNode; - this.styles = {}; + this.styles = styles; - this.checkbox.onChange(() => { - this.applyStyles(); - }); + this.applyStyles(); + + this._register(this.checkbox.onChange(() => this.applyStyles())); } get checked(): boolean { @@ -264,15 +255,9 @@ export class Checkbox extends Widget { return this.domNode === document.activeElement; } - style(styles: ICheckboxStyles): void { - this.styles = styles; - - this.applyStyles(); - } - protected applyStyles(): void { - this.domNode.style.color = this.styles.checkboxForeground ? this.styles.checkboxForeground.toString() : ''; - this.domNode.style.backgroundColor = this.styles.checkboxBackground ? this.styles.checkboxBackground.toString() : ''; - this.domNode.style.borderColor = this.styles.checkboxBorder ? this.styles.checkboxBorder.toString() : ''; + this.domNode.style.color = this.styles.checkboxForeground || ''; + this.domNode.style.backgroundColor = this.styles.checkboxBackground || ''; + this.domNode.style.borderColor = this.styles.checkboxBorder || ''; } } diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 9967f74100..dd37bdca88 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -8,7 +8,8 @@ import { ActionBar, ActionsOrientation, IActionViewItemProvider } from 'vs/base/ import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem'; import { Action, IAction, IActionRunner, SubmenuAction } from 'vs/base/common/actions'; -import { Codicon, CSSIcon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import { EventMultiplexer } from 'vs/base/common/event'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -27,7 +28,7 @@ export interface IToolBarOptions { toggleMenuTitle?: string; anchorAlignmentProvider?: () => AnchorAlignment; renderDropdownAsChildElement?: boolean; - moreIcon?: CSSIcon; + moreIcon?: ThemeIcon; allowContextMenu?: boolean; } @@ -36,13 +37,13 @@ export interface IToolBarOptions { */ export class ToolBar extends Disposable { private options: IToolBarOptions; - private actionBar: ActionBar; + protected readonly actionBar: ActionBar; private toggleMenuAction: ToggleMenuAction; private toggleMenuActionViewItem: DropdownMenuActionViewItem | undefined; private submenuActionViewItems: DropdownMenuActionViewItem[] = []; private hasSecondaryActions: boolean = false; - private lookupKeybindings: boolean; - private element: HTMLElement; + private readonly lookupKeybindings: boolean; + private readonly element: HTMLElement; private _onDidChangeDropdownVisibility = this._register(new EventMultiplexer<boolean>()); readonly onDidChangeDropdownVisibility = this._onDidChangeDropdownVisibility.event; @@ -65,7 +66,7 @@ export class ToolBar extends Disposable { ariaLabel: options.ariaLabel, actionRunner: options.actionRunner, allowContextMenu: options.allowContextMenu, - actionViewItemProvider: (action: IAction) => { + actionViewItemProvider: (action, viewItemOptions) => { if (action.id === ToggleMenuAction.ID) { this.toggleMenuActionViewItem = new DropdownMenuActionViewItem( action, @@ -75,7 +76,7 @@ export class ToolBar extends Disposable { actionViewItemProvider: this.options.actionViewItemProvider, actionRunner: this.actionRunner, keybindingProvider: this.options.getKeyBinding, - classNames: CSSIcon.asClassNameArray(options.moreIcon ?? Codicon.toolBarMore), + classNames: ThemeIcon.asClassNameArray(options.moreIcon ?? Codicon.toolBarMore), anchorAlignmentProvider: this.options.anchorAlignmentProvider, menuAsChild: !!this.options.renderDropdownAsChildElement } @@ -87,7 +88,7 @@ export class ToolBar extends Disposable { } if (options.actionViewItemProvider) { - const result = options.actionViewItemProvider(action); + const result = options.actionViewItemProvider(action, viewItemOptions); if (result) { return result; @@ -140,6 +141,10 @@ export class ToolBar extends Disposable { return this.element; } + focus(): void { + this.actionBar.focus(); + } + getItemsWidth(): number { let itemsWidth = 0; for (let i = 0; i < this.actionBar.length(); i++) { @@ -148,8 +153,8 @@ export class ToolBar extends Disposable { return itemsWidth; } - getItemAction(index: number) { - return this.actionBar.getAction(index); + getItemAction(indexOrElement: number | HTMLElement) { + return this.actionBar.getAction(indexOrElement); } getItemWidth(index: number): number { @@ -181,6 +186,10 @@ export class ToolBar extends Disposable { }); } + isEmpty(): boolean { + return this.actionBar.isEmpty(); + } + private getKeybindingLabel(action: IAction): string | undefined { const key = this.lookupKeybindings ? this.options.getKeyBinding?.(action) : undefined; diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index f858d8a369..ec1e55fb97 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -9,20 +9,20 @@ import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; -import { IMessage, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; +import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; +import { IInputBoxStyles, IMessage, MessageType, unthemedInboxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListMouseEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { IListOptions, IListStyles, isButton, isInputElement, isMonacoEditor, List, MouseController, TypeNavigationMode } from 'vs/base/browser/ui/list/listWidget'; -import { Toggle } from 'vs/base/browser/ui/toggle/toggle'; +import { IToggleStyles, Toggle, unthemedToggleStyles } from 'vs/base/browser/ui/toggle/toggle'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeModel, ITreeModelSpliceEvent, ITreeMouseEvent, ITreeNavigator, ITreeNode, ITreeRenderer, TreeDragOverBubble, TreeError, TreeFilterResult, TreeMouseEventTarget, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; import { distinct, equals, firstOrDefault, range } from 'vs/base/common/arrays'; import { disposableTimeout, timeout } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import { SetMap } from 'vs/base/common/collections'; -import { Color } from 'vs/base/common/color'; import { Emitter, Event, EventBufferer, Relay } from 'vs/base/common/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -298,11 +298,6 @@ interface ITreeRendererOptions { readonly hideTwistiesOfChildlessElements?: boolean; } -interface IRenderData<TTemplateData> { - templateData: ITreeListTemplateData<TTemplateData>; - height: number; -} - interface Collection<T> { readonly elements: T[]; readonly onDidChange: Event<T[]>; @@ -332,12 +327,11 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer readonly templateId: string; private renderedElements = new Map<T, ITreeNode<T, TFilterData>>(); - private renderedNodes = new Map<ITreeNode<T, TFilterData>, IRenderData<TTemplateData>>(); + private renderedNodes = new Map<ITreeNode<T, TFilterData>, ITreeListTemplateData<TTemplateData>>(); private indent: number = TreeRenderer.DefaultIndent; private hideTwistiesOfChildlessElements: boolean = false; private shouldRenderIndentGuides: boolean = false; - private renderedIndentGuides = new SetMap<ITreeNode<T, TFilterData>, HTMLDivElement>(); private activeIndentNodes = new Set<ITreeNode<T, TFilterData>>(); private indentGuidesDisposable: IDisposable = Disposable.None; @@ -348,19 +342,27 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer private modelProvider: () => ITreeModel<T, TFilterData, TRef>, onDidChangeCollapseState: Event<ICollapseStateChangeEvent<T, TFilterData>>, private activeNodes: Collection<ITreeNode<T, TFilterData>>, + private renderedIndentGuides: SetMap<ITreeNode<T, TFilterData>, HTMLDivElement>, options: ITreeRendererOptions = {} ) { this.templateId = renderer.templateId; this.updateOptions(options); Event.map(onDidChangeCollapseState, e => e.node)(this.onDidChangeNodeTwistieState, this, this.disposables); - renderer.onDidChangeTwistieState?.(this.onDidChangeTwistieState, this, this.disposables); } updateOptions(options: ITreeRendererOptions = {}): void { if (typeof options.indent !== 'undefined') { - this.indent = clamp(options.indent, 0, 40); + const indent = clamp(options.indent, 0, 40); + + if (indent !== this.indent) { + this.indent = indent; + + for (const [node, templateData] of this.renderedNodes) { + this.renderTreeElement(node, templateData); + } + } } if (typeof options.renderIndentGuides !== 'undefined') { @@ -368,6 +370,11 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer if (shouldRenderIndentGuides !== this.shouldRenderIndentGuides) { this.shouldRenderIndentGuides = shouldRenderIndentGuides; + + for (const [node, templateData] of this.renderedNodes) { + this._renderIndentGuides(node, templateData); + } + this.indentGuidesDisposable.dispose(); if (shouldRenderIndentGuides) { @@ -396,21 +403,9 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer } renderElement(node: ITreeNode<T, TFilterData>, index: number, templateData: ITreeListTemplateData<TTemplateData>, height: number | undefined): void { - if (typeof height === 'number') { - this.renderedNodes.set(node, { templateData, height }); - this.renderedElements.set(node.element, node); - } - - const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent; - templateData.twistie.style.paddingLeft = `${indent}px`; - templateData.indent.style.width = `${indent + this.indent - 16}px`; - - this.renderTwistie(node, templateData); - - if (typeof height === 'number') { - this.renderIndentGuides(node, templateData); - } - + this.renderedNodes.set(node, templateData); + this.renderedElements.set(node.element, node); + this.renderTreeElement(node, templateData); this.renderer.renderElement(node, index, templateData.templateData, height); } @@ -440,19 +435,28 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer } private onDidChangeNodeTwistieState(node: ITreeNode<T, TFilterData>): void { - const data = this.renderedNodes.get(node); + const templateData = this.renderedNodes.get(node); - if (!data) { + if (!templateData) { return; } - this.renderTwistie(node, data.templateData); this._onDidChangeActiveNodes(this.activeNodes.elements); - this.renderIndentGuides(node, data.templateData); + this.renderTreeElement(node, templateData); } - private renderTwistie(node: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>) { - templateData.twistie.classList.remove(...Codicon.treeItemExpanded.classNamesArray); + private renderTreeElement(node: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>) { + const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent; + templateData.twistie.style.paddingLeft = `${indent}px`; + templateData.indent.style.width = `${indent + this.indent - 16}px`; + + if (node.collapsible) { + templateData.container.setAttribute('aria-expanded', String(!node.collapsed)); + } else { + templateData.container.removeAttribute('aria-expanded'); + } + + templateData.twistie.classList.remove(...ThemeIcon.asClassNameArray(Codicon.treeItemExpanded)); let twistieRendered = false; @@ -462,7 +466,7 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer if (node.collapsible && (!this.hideTwistiesOfChildlessElements || node.visibleChildrenCount > 0)) { if (!twistieRendered) { - templateData.twistie.classList.add(...Codicon.treeItemExpanded.classNamesArray); + templateData.twistie.classList.add(...ThemeIcon.asClassNameArray(Codicon.treeItemExpanded)); } templateData.twistie.classList.add('collapsible'); @@ -471,14 +475,10 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer templateData.twistie.classList.remove('collapsible', 'collapsed'); } - if (node.collapsible) { - templateData.container.setAttribute('aria-expanded', String(!node.collapsed)); - } else { - templateData.container.removeAttribute('aria-expanded'); - } + this._renderIndentGuides(node, templateData); } - private renderIndentGuides(target: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>): void { + private _renderIndentGuides(node: ITreeNode<T, TFilterData>, templateData: ITreeListTemplateData<TTemplateData>): void { clearNode(templateData.indent); templateData.indentGuidesDisposable.dispose(); @@ -489,8 +489,6 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer const disposableStore = new DisposableStore(); const model = this.modelProvider(); - let node = target; - while (true) { const ref = model.getNodeLocation(node); const parentRef = model.getParentNodeLocation(ref); @@ -622,12 +620,23 @@ class FindFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi const labels = Array.isArray(label) ? label : [label]; for (const l of labels) { - const labelStr = l && l.toString(); + const labelStr: string = l && l.toString(); if (typeof labelStr === 'undefined') { return { data: FuzzyScore.Default, visibility }; } - const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true }); + let score: FuzzyScore | undefined; + if (this.tree.findMatchType === TreeFindMatchType.Contiguous) { + const index = labelStr.toLowerCase().indexOf(this._lowercasePattern); + if (index > -1) { + score = [Number.MAX_SAFE_INTEGER, 0]; + for (let i = this._lowercasePattern.length; i > 0; i--) { + score.push(index + i - 1); + } + } + } else { + score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true }); + } if (score) { this._matchCount++; return labels.length === 1 ? @@ -637,7 +646,13 @@ class FindFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi } if (this.tree.findMode === TreeFindMode.Filter) { - return TreeVisibility.Recurse; + if (typeof this.tree.options.defaultFindVisibility === 'number') { + return this.tree.options.defaultFindVisibility; + } else if (this.tree.options.defaultFindVisibility) { + return this.tree.options.defaultFindVisibility(element); + } else { + return TreeVisibility.Recurse; + } } else { return { data: FuzzyScore.Default, visibility }; } @@ -653,35 +668,72 @@ class FindFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDi } } -export interface ICaseSensitiveToggleOpts { +export interface ITreeFindToggleOpts { readonly isChecked: boolean; - readonly inputActiveOptionBorder?: Color; - readonly inputActiveOptionForeground?: Color; - readonly inputActiveOptionBackground?: Color; + readonly inputActiveOptionBorder: string | undefined; + readonly inputActiveOptionForeground: string | undefined; + readonly inputActiveOptionBackground: string | undefined; } export class ModeToggle extends Toggle { - constructor(opts?: ICaseSensitiveToggleOpts) { + constructor(opts: ITreeFindToggleOpts) { super({ - icon: Codicon.filter, + icon: Codicon.listFilter, title: localize('filter', "Filter"), - isChecked: opts?.isChecked ?? false, - inputActiveOptionBorder: opts?.inputActiveOptionBorder, - inputActiveOptionForeground: opts?.inputActiveOptionForeground, - inputActiveOptionBackground: opts?.inputActiveOptionBackground + isChecked: opts.isChecked ?? false, + inputActiveOptionBorder: opts.inputActiveOptionBorder, + inputActiveOptionForeground: opts.inputActiveOptionForeground, + inputActiveOptionBackground: opts.inputActiveOptionBackground }); } } -export interface IFindWidgetStyles extends IFindInputStyles, IListStyles { } +export class FuzzyToggle extends Toggle { + constructor(opts: ITreeFindToggleOpts) { + super({ + icon: Codicon.searchFuzzy, + title: localize('fuzzySearch', "Fuzzy Match"), + isChecked: opts.isChecked ?? false, + inputActiveOptionBorder: opts.inputActiveOptionBorder, + inputActiveOptionForeground: opts.inputActiveOptionForeground, + inputActiveOptionBackground: opts.inputActiveOptionBackground + }); + } +} -export interface IFindWidgetOpts extends IFindWidgetStyles { } +export interface IFindWidgetStyles { + listFilterWidgetBackground: string | undefined; + listFilterWidgetOutline: string | undefined; + listFilterWidgetNoMatchesOutline: string | undefined; + listFilterWidgetShadow: string | undefined; + readonly toggleStyles: IToggleStyles; + readonly inputBoxStyles: IInputBoxStyles; +} + +export interface IFindWidgetOptions { + readonly history?: string[]; + readonly styles?: IFindWidgetStyles; +} + +const unthemedFindWidgetStyles: IFindWidgetStyles = { + inputBoxStyles: unthemedInboxStyles, + toggleStyles: unthemedToggleStyles, + listFilterWidgetBackground: undefined, + listFilterWidgetNoMatchesOutline: undefined, + listFilterWidgetOutline: undefined, + listFilterWidgetShadow: undefined +}; export enum TreeFindMode { Highlight, Filter } +export enum TreeFindMatchType { + Fuzzy, + Contiguous +} + class FindWidget<T, TFilterData> extends Disposable { private readonly elements = h('.monaco-tree-type-filter', [ @@ -695,35 +747,67 @@ class FindWidget<T, TFilterData> extends Disposable { this.findInput.inputBox.setPlaceHolder(mode === TreeFindMode.Filter ? localize('type to filter', "Type to filter") : localize('type to search', "Type to search")); } + set matchType(matchType: TreeFindMatchType) { + this.matchTypeToggle.checked = matchType === TreeFindMatchType.Fuzzy; + } + + get value(): string { + return this.findInput.inputBox.value; + } + + set value(value: string) { + this.findInput.inputBox.value = value; + } + private readonly modeToggle: ModeToggle; + private readonly matchTypeToggle: FuzzyToggle; private readonly findInput: FindInput; private readonly actionbar: ActionBar; private width = 0; private right = 0; + private top = 0; readonly _onDidDisable = new Emitter<void>(); readonly onDidDisable = this._onDidDisable.event; readonly onDidChangeValue: Event<string>; readonly onDidChangeMode: Event<TreeFindMode>; + readonly onDidChangeMatchType: Event<TreeFindMatchType>; constructor( container: HTMLElement, private tree: AbstractTree<T, TFilterData, any>, contextViewProvider: IContextViewProvider, mode: TreeFindMode, - options?: IFindWidgetOpts + matchType: TreeFindMatchType, + options?: IFindWidgetOptions ) { super(); container.appendChild(this.elements.root); this._register(toDisposable(() => container.removeChild(this.elements.root))); - this.modeToggle = this._register(new ModeToggle({ ...options, isChecked: mode === TreeFindMode.Filter })); - this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store); + const styles = options?.styles ?? unthemedFindWidgetStyles; - this.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, false, { + if (styles.listFilterWidgetBackground) { + this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground; + } + + if (styles.listFilterWidgetShadow) { + this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; + } + + this.modeToggle = this._register(new ModeToggle({ ...styles.toggleStyles, isChecked: mode === TreeFindMode.Filter })); + this.matchTypeToggle = this._register(new FuzzyToggle({ ...styles.toggleStyles, isChecked: matchType === TreeFindMatchType.Fuzzy })); + this.onDidChangeMode = Event.map(this.modeToggle.onChange, () => this.modeToggle.checked ? TreeFindMode.Filter : TreeFindMode.Highlight, this._store); + this.onDidChangeMatchType = Event.map(this.matchTypeToggle.onChange, () => this.matchTypeToggle.checked ? TreeFindMatchType.Fuzzy : TreeFindMatchType.Contiguous, this._store); + + this.findInput = this._register(new FindInput(this.elements.findInput, contextViewProvider, { label: localize('type to search', "Type to search"), - additionalToggles: [this.modeToggle] + additionalToggles: [this.modeToggle, this.matchTypeToggle], + showCommonFindToggles: false, + inputBoxStyles: styles.inputBoxStyles, + toggleStyles: styles.toggleStyles, + history: options?.history })); this.actionbar = this._register(new ActionBar(this.elements.actionbar)); @@ -735,12 +819,34 @@ class FindWidget<T, TFilterData> extends Disposable { .event; this._register(onKeyDown((e): any => { - switch (e.keyCode) { - case KeyCode.DownArrow: - e.preventDefault(); - e.stopPropagation(); + // Using equals() so we reserve modified keys for future use + if (e.equals(KeyCode.Enter)) { + // This is the only keyboard way to return to the tree from a history item that isn't the last one + e.preventDefault(); + e.stopPropagation(); + this.findInput.inputBox.addToHistory(); + this.tree.domFocus(); + return; + } + if (e.equals(KeyCode.DownArrow)) { + e.preventDefault(); + e.stopPropagation(); + if (this.findInput.inputBox.isAtLastInHistory() || this.findInput.inputBox.isNowhereInHistory()) { + // Retain original pre-history DownArrow behavior + this.findInput.inputBox.addToHistory(); this.tree.domFocus(); - return; + } else { + // Downward through history + this.findInput.inputBox.showNextValue(); + } + return; + } + if (e.equals(KeyCode.UpArrow)) { + e.preventDefault(); + e.stopPropagation(); + // Upward through history + this.findInput.inputBox.showPreviousValue(); + return; } })); @@ -756,11 +862,18 @@ class FindWidget<T, TFilterData> extends Disposable { const startRight = this.right; const startX = e.pageX; + const startTop = this.top; + const startY = e.pageY; this.elements.grab.classList.add('grabbing'); + const transition = this.elements.root.style.transition; + this.elements.root.style.transition = 'unset'; + const update = (e: MouseEvent) => { const deltaX = e.pageX - startX; this.right = startRight - deltaX; + const deltaY = e.pageY - startY; + this.top = startTop + deltaY; this.layout(); }; @@ -768,6 +881,7 @@ class FindWidget<T, TFilterData> extends Disposable { disposables.add(onWindowMouseUp.event(e => { update(e); this.elements.grab.classList.remove('grabbing'); + this.elements.root.style.transition = transition; disposables.dispose(); })); })); @@ -778,6 +892,7 @@ class FindWidget<T, TFilterData> extends Disposable { this._register(onGrabKeyDown((e): any => { let right: number | undefined; + let top: number | undefined; if (e.keyCode === KeyCode.LeftArrow) { right = Number.POSITIVE_INFINITY; @@ -787,28 +902,37 @@ class FindWidget<T, TFilterData> extends Disposable { right = this.right === 0 ? Number.POSITIVE_INFINITY : 0; } + if (e.keyCode === KeyCode.UpArrow) { + top = 0; + } else if (e.keyCode === KeyCode.DownArrow) { + top = Number.POSITIVE_INFINITY; + } + if (right !== undefined) { e.preventDefault(); e.stopPropagation(); this.right = right; this.layout(); } + + if (top !== undefined) { + e.preventDefault(); + e.stopPropagation(); + this.top = top; + const transition = this.elements.root.style.transition; + this.elements.root.style.transition = 'unset'; + this.layout(); + setTimeout(() => { + this.elements.root.style.transition = transition; + }, 0); + } })); this.onDidChangeValue = this.findInput.onDidChange; - this.style(options ?? {}); } - style(styles: IFindWidgetStyles): void { - this.findInput.style(styles); - - if (styles.listFilterWidgetBackground) { - this.elements.root.style.backgroundColor = styles.listFilterWidgetBackground.toString(); - } - - if (styles.listFilterWidgetShadow) { - this.elements.root.style.boxShadow = `0 0 8px 2px ${styles.listFilterWidgetShadow}`; - } + getHistory(): string[] { + return this.findInput.inputBox.getHistory(); } focus() { @@ -817,12 +941,17 @@ class FindWidget<T, TFilterData> extends Disposable { select() { this.findInput.select(); + + // Reposition to last in history + this.findInput.inputBox.addToHistory(true); } layout(width: number = this.width): void { this.width = width; this.right = clamp(this.right, 0, Math.max(0, width - 212)); this.elements.root.style.right = `${this.right}px`; + this.top = clamp(this.top, 0, 24); + this.elements.root.style.top = `${this.top}px`; } showMessage(message: IMessage): void { @@ -841,10 +970,15 @@ class FindWidget<T, TFilterData> extends Disposable { } } +interface IFindControllerOptions extends IFindWidgetOptions { } + class FindController<T, TFilterData> implements IDisposable { + private _history: string[] | undefined; + private _pattern = ''; get pattern(): string { return this._pattern; } + private previousPattern = ''; private _mode: TreeFindMode; get mode(): TreeFindMode { return this._mode; } @@ -864,13 +998,33 @@ class FindController<T, TFilterData> implements IDisposable { this._onDidChangeMode.fire(mode); } + private _matchType: TreeFindMatchType; + get matchType(): TreeFindMatchType { return this._matchType; } + set matchType(matchType: TreeFindMatchType) { + if (matchType === this._matchType) { + return; + } + + this._matchType = matchType; + + if (this.widget) { + this.widget.matchType = this._matchType; + } + + this.tree.refilter(); + this.render(); + this._onDidChangeMatchType.fire(matchType); + } + private widget: FindWidget<T, TFilterData> | undefined; - private styles: IFindWidgetStyles | undefined; private width = 0; private readonly _onDidChangeMode = new Emitter<TreeFindMode>(); readonly onDidChangeMode = this._onDidChangeMode.event; + private readonly _onDidChangeMatchType = new Emitter<TreeFindMatchType>(); + readonly onDidChangeMatchType = this._onDidChangeMatchType.event; + private readonly _onDidChangePattern = new Emitter<string>(); readonly onDidChangePattern = this._onDidChangePattern.event; @@ -885,12 +1039,24 @@ class FindController<T, TFilterData> implements IDisposable { model: ITreeModel<T, TFilterData, any>, private view: List<ITreeNode<T, TFilterData>>, private filter: FindFilter<T>, - private readonly contextViewProvider: IContextViewProvider + private readonly contextViewProvider: IContextViewProvider, + private readonly options: IFindControllerOptions = {} ) { this._mode = tree.options.defaultFindMode ?? TreeFindMode.Highlight; + this._matchType = tree.options.defaultFindMatchType ?? TreeFindMatchType.Fuzzy; model.onDidSplice(this.onDidSpliceModel, this, this.disposables); } + updateOptions(optionsUpdate: IAbstractTreeOptionsUpdate = {}): void { + if (optionsUpdate.defaultFindMode !== undefined) { + this.mode = optionsUpdate.defaultFindMode; + } + + if (optionsUpdate.defaultFindMatchType !== undefined) { + this.matchType = optionsUpdate.defaultFindMatchType; + } + } + open(): void { if (this.widget) { this.widget.focus(); @@ -898,17 +1064,20 @@ class FindController<T, TFilterData> implements IDisposable { return; } - this.mode = this.tree.options.defaultFindMode ?? TreeFindMode.Highlight; - this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this.mode, this.styles); + this.widget = new FindWidget(this.view.getHTMLElement(), this.tree, this.contextViewProvider, this.mode, this.matchType, { ...this.options, history: this._history }); this.enabledDisposables.add(this.widget); this.widget.onDidChangeValue(this.onDidChangeValue, this, this.enabledDisposables); this.widget.onDidChangeMode(mode => this.mode = mode, undefined, this.enabledDisposables); + this.widget.onDidChangeMatchType(matchType => this.matchType = matchType, undefined, this.enabledDisposables); this.widget.onDidDisable(this.close, this, this.enabledDisposables); this.widget.layout(this.width); this.widget.focus(); + this.widget.value = this.previousPattern; + this.widget.select(); + this._onDidChangeOpenState.fire(true); } @@ -917,11 +1086,13 @@ class FindController<T, TFilterData> implements IDisposable { return; } + this._history = this.widget.getHistory(); this.widget = undefined; this.enabledDisposables.dispose(); this.enabledDisposables = new DisposableStore(); + this.previousPattern = this.pattern; this.onDidChangeValue(''); this.tree.domFocus(); @@ -965,7 +1136,11 @@ class FindController<T, TFilterData> implements IDisposable { const noMatches = this.filter.totalCount > 0 && this.filter.matchCount === 0; if (this.pattern && noMatches) { - this.widget?.showMessage({ type: MessageType.WARNING, content: localize('not found', "No elements found.") }); + if (this.tree.options.showNotFoundMessage ?? true) { + this.widget?.showMessage({ type: MessageType.WARNING, content: localize('not found', "No elements found.") }); + } else { + this.widget?.showMessage({ type: MessageType.WARNING }); + } } else { this.widget?.clearMessage(); } @@ -983,17 +1158,13 @@ class FindController<T, TFilterData> implements IDisposable { return !FuzzyScore.isDefault(node.filterData as any as FuzzyScore); } - style(styles: IFindWidgetStyles): void { - this.styles = styles; - this.widget?.style(styles); - } - layout(width: number): void { this.width = width; this.widget?.layout(width); } dispose() { + this._history = undefined; this._onDidChangePattern.dispose(); this.enabledDisposables.dispose(); this.disposables.dispose(); @@ -1031,8 +1202,11 @@ export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions { readonly typeNavigationEnabled?: boolean; readonly typeNavigationMode?: TypeNavigationMode; readonly defaultFindMode?: TreeFindMode; + readonly defaultFindMatchType?: TreeFindMatchType; + readonly showNotFoundMessage?: boolean; readonly smoothScrolling?: boolean; readonly horizontalScrolling?: boolean; + readonly scrollByPage?: boolean; readonly mouseWheelScrollSensitivity?: number; readonly fastScrollSensitivity?: number; readonly expandOnDoubleClick?: boolean; @@ -1046,6 +1220,8 @@ export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTr readonly dnd?: ITreeDragAndDrop<T>; readonly additionalScrollHeight?: number; readonly findWidgetEnabled?: boolean; + readonly findWidgetStyles?: IFindWidgetStyles; + readonly defaultFindVisibility?: TreeVisibility | ((e: T) => TreeVisibility); } function dfs<T, TFilterData>(node: ITreeNode<T, TFilterData>, fn: (node: ITreeNode<T, TFilterData>) => void): void { @@ -1142,7 +1318,7 @@ class Trait<T> { } else { const insertedNode = insertedNodesMap.get(id); - if (insertedNode) { + if (insertedNode && insertedNode.visible) { nodes.push(insertedNode); } } @@ -1183,6 +1359,10 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController< return; } + if (e.browserEvent.isHandledByList) { + return; + } + const node = e.element; if (!node) { @@ -1214,13 +1394,14 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController< } if (node.collapsible) { - const model = ((this.tree as any).model as ITreeModel<T, TFilterData, TRef>); // internal - const location = model.getNodeLocation(node); + const location = this.tree.getNodeLocation(node); const recursive = e.browserEvent.altKey; this.tree.setFocus([location]); - model.setCollapsed(location, undefined, recursive); + this.tree.toggleCollapsed(location, recursive); if (expandOnlyOnTwistieClick && onTwistie) { + // Do not set this before calling a handler on the super class, because it will reject it as handled + e.browserEvent.isHandledByList = true; return; } } @@ -1235,6 +1416,10 @@ class TreeNodeListMouseController<T, TFilterData, TRef> extends MouseController< return; } + if (e.browserEvent.isHandledByList) { + return; + } + super.onDoubleClick(e); } } @@ -1266,7 +1451,7 @@ class TreeNodeList<T, TFilterData, TRef> extends List<ITreeNode<T, TFilterData>> return new TreeNodeListMouseController(this, options.tree); } - override splice(start: number, deleteCount: number, elements: ITreeNode<T, TFilterData>[] = []): void { + override splice(start: number, deleteCount: number, elements: readonly ITreeNode<T, TFilterData>[] = []): void { super.splice(start, deleteCount, elements); if (elements.length === 0) { @@ -1377,6 +1562,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable set findMode(findMode: TreeFindMode) { if (this.findController) { this.findController.mode = findMode; } } readonly onDidChangeFindMode: Event<TreeFindMode>; + get findMatchType(): TreeFindMatchType { return this.findController?.matchType ?? TreeFindMatchType.Fuzzy; } + set findMatchType(findFuzzy: TreeFindMatchType) { if (this.findController) { this.findController.matchType = findFuzzy; } } + readonly onDidChangeFindMatchType: Event<TreeFindMatchType>; + get onDidChangeFindPattern(): Event<string> { return this.findController ? this.findController.onDidChangePattern : Event.None; } get expandOnDoubleClick(): boolean { return typeof this._options.expandOnDoubleClick === 'undefined' ? true : this._options.expandOnDoubleClick; } @@ -1399,7 +1588,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable const onDidChangeCollapseStateRelay = new Relay<ICollapseStateChangeEvent<T, TFilterData>>(); const onDidChangeActiveNodes = new Relay<ITreeNode<T, TFilterData>[]>(); const activeNodes = this.disposables.add(new EventCollection(onDidChangeActiveNodes.event)); - this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, TRef, any>(r, () => this.model, onDidChangeCollapseStateRelay.event, activeNodes, _options)); + const renderedIndentGuides = new SetMap<ITreeNode<T, TFilterData>, HTMLDivElement>(); + this.renderers = renderers.map(r => new TreeRenderer<T, TFilterData, TRef, any>(r, () => this.model, onDidChangeCollapseStateRelay.event, activeNodes, renderedIndentGuides, _options)); for (const r of this.renderers) { this.disposables.add(r); } @@ -1461,13 +1651,16 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable } if ((_options.findWidgetEnabled ?? true) && _options.keyboardNavigationLabelProvider && _options.contextViewProvider) { - this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider); + const opts = this.options.findWidgetStyles ? { styles: this.options.findWidgetStyles } : undefined; + this.findController = new FindController(this, this.model, this.view, filter!, _options.contextViewProvider, opts); this.focusNavigationFilter = node => this.findController!.shouldAllowFocus(node); this.onDidChangeFindOpenState = this.findController.onDidChangeOpenState; this.disposables.add(this.findController!); this.onDidChangeFindMode = this.findController.onDidChangeMode; + this.onDidChangeFindMatchType = this.findController.onDidChangeMatchType; } else { this.onDidChangeFindMode = Event.None; + this.onDidChangeFindMatchType = Event.None; } this.styleElement = createStyleSheet(this.view.getHTMLElement()); @@ -1482,6 +1675,8 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable } this.view.updateOptions(this._options); + this.findController?.updateOptions(optionsUpdate); + this._onDidUpdateOptions.fire(this._options); this.getHTMLElement().classList.toggle('always', this._options.renderIndentGuides === RenderIndentGuides.Always); @@ -1511,10 +1706,18 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable return this.view.contentHeight; } + get contentWidth(): number { + return this.view.contentWidth; + } + get onDidChangeContentHeight(): Event<number> { return this.view.onDidChangeContentHeight; } + get onDidChangeContentWidth(): Event<number> { + return this.view.onDidChangeContentWidth; + } + get scrollTop(): number { return this.view.scrollTop; } @@ -1564,6 +1767,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable this.view.ariaLabel = value; } + get selectionSize() { + return this.selection.getNodes().length; + } + domFocus(): void { this.view.domFocus(); } @@ -1585,13 +1792,12 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable const content: string[] = []; if (styles.treeIndentGuidesStroke) { - content.push(`.monaco-list${suffix}:hover .monaco-tl-indent > .indent-guide, .monaco-list${suffix}.always .monaco-tl-indent > .indent-guide { border-color: ${styles.treeIndentGuidesStroke.transparent(0.4)}; }`); + content.push(`.monaco-list${suffix}:hover .monaco-tl-indent > .indent-guide, .monaco-list${suffix}.always .monaco-tl-indent > .indent-guide { border-color: ${styles.treeInactiveIndentGuidesStroke}; }`); content.push(`.monaco-list${suffix} .monaco-tl-indent > .indent-guide.active { border-color: ${styles.treeIndentGuidesStroke}; }`); } this.styleElement.textContent = content.join('\n'); - this.findController?.style(styles); this.view.style(styles); } @@ -1613,6 +1819,10 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable return this.model.getNode(location); } + getNodeLocation(node: ITreeNode<T, TFilterData>): TRef { + return this.model.getNodeLocation(node); + } + collapse(location: TRef, recursive: boolean = false): boolean { return this.model.setCollapsed(location, true, recursive); } diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 9ff33775f8..e9d113eb01 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -11,15 +11,15 @@ import { ComposedTreeDelegate, TreeFindMode as TreeFindMode, IAbstractTreeOption import { ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { getVisibleState, isFilterResult } from 'vs/base/browser/ui/tree/indexTreeModel'; import { CompressibleObjectTree, ICompressibleKeyboardNavigationLabelProvider, ICompressibleObjectTreeOptions, ICompressibleTreeRenderer, IObjectTreeOptions, IObjectTreeSetChildrenOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; -import { IAsyncDataSource, ICollapseStateChangeEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeElement, ITreeEvent, ITreeFilter, ITreeMouseEvent, ITreeNode, ITreeRenderer, ITreeSorter, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree'; +import { IAsyncDataSource, ICollapseStateChangeEvent, IObjectTreeElement, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeEvent, ITreeFilter, ITreeMouseEvent, ITreeNode, ITreeRenderer, ITreeSorter, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree'; import { CancelablePromise, createCancelablePromise, Promises, timeout } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import { isCancellationError, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; -import { IThemable } from 'vs/base/common/styler'; import { isIterable } from 'vs/base/common/types'; export interface IAsyncDataTreeNode<TInput, T> { // {{SQL CARBON EDIT}} - exporting interface @@ -110,10 +110,10 @@ class AsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> implements IT renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean { if (element.slow) { - twistieElement.classList.add(...Codicon.treeItemLoading.classNamesArray); + twistieElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.treeItemLoading)); return true; } else { - twistieElement.classList.remove(...Codicon.treeItemLoading.classNamesArray); + twistieElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.treeItemLoading)); return false; } } @@ -269,7 +269,17 @@ function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOpt e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) ) ), - additionalScrollHeight: options.additionalScrollHeight + defaultFindVisibility: e => { + if (e.hasChildren && e.stale) { + return TreeVisibility.Visible; + } else if (typeof options.defaultFindVisibility === 'number') { + return options.defaultFindVisibility; + } else if (typeof options.defaultFindVisibility === 'undefined') { + return TreeVisibility.Recurse; + } else { + return (options.defaultFindVisibility as ((e: T) => TreeVisibility))(e.element as T); + } + } }; } @@ -301,7 +311,7 @@ function dfs<TInput, T>(node: IAsyncDataTreeNode<TInput, T>, fn: (node: IAsyncDa node.children.forEach(child => dfs(child, fn)); } -export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable, IThemable { +export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable { protected readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>; protected readonly root: IAsyncDataTreeNode<TInput, T>; @@ -336,6 +346,10 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable get onDidFocus(): Event<void> { return this.tree.onDidFocus; } get onDidBlur(): Event<void> { return this.tree.onDidBlur; } + /** + * To be used internally only! + * @deprecated + */ get onDidChangeModel(): Event<void> { return this.tree.onDidChangeModel; } get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T> | null, TFilterData>> { return this.tree.onDidChangeCollapseState; } @@ -424,10 +438,18 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable return this.tree.contentHeight; } + get contentWidth(): number { + return this.tree.contentWidth; + } + get onDidChangeContentHeight(): Event<number> { return this.tree.onDidChangeContentHeight; } + get onDidChangeContentWidth(): Event<number> { + return this.tree.onDidChangeContentWidth; + } + get scrollTop(): number { return this.tree.scrollTop; } @@ -974,7 +996,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable this._onDidRender.fire(); } - protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): ITreeElement<IAsyncDataTreeNode<TInput, T>> { + protected asTreeElement(node: IAsyncDataTreeNode<TInput, T>, viewStateContext?: IAsyncDataTreeViewStateContext<TInput, T>): IObjectTreeElement<IAsyncDataTreeNode<TInput, T>> { if (node.stale) { return { element: node, @@ -1095,10 +1117,10 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i renderTwistie(element: IAsyncDataTreeNode<TInput, T>, twistieElement: HTMLElement): boolean { if (element.slow) { - twistieElement.classList.add(...Codicon.treeItemLoading.classNamesArray); + twistieElement.classList.add(...ThemeIcon.asClassNameArray(Codicon.treeItemLoading)); return true; } else { - twistieElement.classList.remove(...Codicon.treeItemLoading.classNamesArray); + twistieElement.classList.remove(...ThemeIcon.asClassNameArray(Codicon.treeItemLoading)); return false; } } @@ -1150,7 +1172,7 @@ export interface ICompressibleAsyncDataTreeOptionsUpdate extends IAsyncDataTreeO export class CompressibleAsyncDataTree<TInput, T, TFilterData = void> extends AsyncDataTree<TInput, T, TFilterData> { - protected override readonly tree!: CompressibleObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>; + protected declare readonly tree: CompressibleObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>; protected readonly compressibleNodeMapper: CompressibleAsyncDataTreeNodeMapper<TInput, T, TFilterData> = new WeakMapper(node => new CompressibleAsyncDataTreeNodeWrapper(node)); private filter?: ITreeFilter<T, TFilterData>; diff --git a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts index 9874805d85..1b2c8dcd91 100644 --- a/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/compressedObjectTreeModel.ts @@ -6,12 +6,12 @@ import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { IIndexTreeModelSpliceOptions, IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IObjectTreeModel, IObjectTreeModelOptions, IObjectTreeModelSetChildrenOptions, ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; -import { ICollapseStateChangeEvent, ITreeElement, ITreeModel, ITreeModelSpliceEvent, ITreeNode, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree'; +import { ICollapseStateChangeEvent, IObjectTreeElement, ITreeModel, ITreeModelSpliceEvent, ITreeNode, TreeError, TreeFilterResult, TreeVisibility, WeakMapper } from 'vs/base/browser/ui/tree/tree'; import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; // Exported only for test reasons, do not use directly -export interface ICompressedTreeElement<T> extends ITreeElement<T> { +export interface ICompressedTreeElement<T> extends IObjectTreeElement<T> { readonly children?: Iterable<ICompressedTreeElement<T>>; readonly incompressible?: boolean; } @@ -22,7 +22,7 @@ export interface ICompressedTreeNode<T> { readonly incompressible: boolean; } -function noCompress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> { +function noCompress<T>(element: ICompressedTreeElement<T>): ICompressedTreeElement<ICompressedTreeNode<T>> { const elements = [element.element]; const incompressible = element.incompressible || false; @@ -35,7 +35,7 @@ function noCompress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompre } // Exported only for test reasons, do not use directly -export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<ICompressedTreeNode<T>> { +export function compress<T>(element: ICompressedTreeElement<T>): ICompressedTreeElement<ICompressedTreeNode<T>> { const elements = [element.element]; const incompressible = element.incompressible || false; @@ -65,7 +65,7 @@ export function compress<T>(element: ICompressedTreeElement<T>): ITreeElement<IC }; } -function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> { +function _decompress<T>(element: ICompressedTreeElement<ICompressedTreeNode<T>>, index = 0): ICompressedTreeElement<T> { let children: Iterable<ICompressedTreeElement<T>>; if (index < element.element.elements.length - 1) { @@ -93,7 +93,7 @@ function _decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>, index = 0 } // Exported only for test reasons, do not use directly -export function decompress<T>(element: ITreeElement<ICompressedTreeNode<T>>): ICompressedTreeElement<T> { +export function decompress<T>(element: ICompressedTreeElement<ICompressedTreeNode<T>>): ICompressedTreeElement<T> { return _decompress(element, 0); } @@ -159,7 +159,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e const compressedNode = this.nodes.get(element); if (!compressedNode) { - throw new Error('Unknown compressed tree node'); + throw new TreeError(this.user, 'Unknown compressed tree node'); } const node = this.model.getNode(compressedNode) as ITreeNode<ICompressedTreeNode<T>, TFilterData>; @@ -205,7 +205,7 @@ export class CompressedObjectTreeModel<T extends NonNullable<any>, TFilterData e private _setChildren( node: ICompressedTreeNode<T> | null, - children: Iterable<ITreeElement<ICompressedTreeNode<T>>>, + children: Iterable<IObjectTreeElement<ICompressedTreeNode<T>>>, options: IIndexTreeModelSpliceOptions<ICompressedTreeNode<T>, TFilterData>, ): void { const insertedElements = new Set<T | null>(); diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 30be08f52d..9326be0f2a 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -16,7 +16,7 @@ export interface IDataTreeOptions<T, TFilterData = void> extends IAbstractTreeOp export class DataTree<TInput, T, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> { - protected override model!: ObjectTreeModel<T, TFilterData>; + protected declare model: ObjectTreeModel<T, TFilterData>; private input: TInput | undefined; private identityProvider: IIdentityProvider<T> | undefined; diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index 0c99eb2b5b..67ea22e2ab 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -14,7 +14,7 @@ export interface IIndexTreeOptions<T, TFilterData = void> extends IAbstractTreeO export class IndexTree<T, TFilterData = void> extends AbstractTree<T, TFilterData, number[]> { - protected override model!: IndexTreeModel<T, TFilterData>; + protected declare model: IndexTreeModel<T, TFilterData>; constructor( user: string, diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index 86b6ac8c4f..c9bf4cc100 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -6,7 +6,8 @@ import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { ICollapseStateChangeEvent, ITreeElement, ITreeFilter, ITreeFilterDataResult, ITreeModel, ITreeModelSpliceEvent, ITreeNode, TreeError, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { splice, tail2 } from 'vs/base/common/arrays'; -import { Delayer, MicrotaskDelay } from 'vs/base/common/async'; +import { Delayer } from 'vs/base/common/async'; +import { MicrotaskDelay } from 'vs/base/common/symbols'; import { LcsDiff } from 'vs/base/common/diff/diff'; import { Emitter, Event, EventBufferer } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; @@ -526,12 +527,12 @@ export class IndexTreeModel<T extends Exclude<any, undefined>, TFilterData = voi const childElements = treeElement.children || Iterable.empty(); const childRevealed = revealed && visibility !== TreeVisibility.Hidden && !node.collapsed; - const childNodes = Iterable.map(childElements, el => this.createTreeNode(el, node, visibility, childRevealed, treeListElements, onDidCreateNode)); let visibleChildrenCount = 0; let renderNodeCount = 1; - for (const child of childNodes) { + for (const el of childElements) { + const child = this.createTreeNode(el, node, visibility, childRevealed, treeListElements, onDidCreateNode); node.children.push(child); renderNodeCount += child.renderNodeCount; diff --git a/src/vs/base/browser/ui/tree/media/tree.css b/src/vs/base/browser/ui/tree/media/tree.css index 29f5337d8d..14938ee6c8 100644 --- a/src/vs/base/browser/ui/tree/media/tree.css +++ b/src/vs/base/browser/ui/tree/media/tree.css @@ -10,6 +10,9 @@ position: relative; } +.monaco-tl-row.disabled { + cursor: default; +} .monaco-tl-indent { height: 100%; position: absolute; @@ -29,7 +32,7 @@ border-left: 1px solid transparent; } -.monaco-tl-indent > .indent-guide { +.monaco-workbench:not(.reduce-motion) .monaco-tl-indent > .indent-guide { transition: border-color 0.1s linear; } @@ -73,14 +76,20 @@ top: 0; display: flex; padding: 3px; - transition: top 0.3s; max-width: 200px; z-index: 100; margin: 0 6px; + border: 1px solid var(--vscode-widget-border); + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + +.monaco-workbench:not(.reduce-motion) .monaco-tree-type-filter { + transition: top 0.3s; } .monaco-tree-type-filter.disabled { - top: -40px; + top: -40px !important; } .monaco-tree-type-filter-grab { diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index c967ad91b1..21e1480d46 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -8,7 +8,7 @@ import { AbstractTree, IAbstractTreeOptions, IAbstractTreeOptionsUpdate } from ' import { CompressibleObjectTreeModel, ElementMapper, ICompressedTreeElement, ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IObjectTreeModel, ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; -import { ICollapseStateChangeEvent, ITreeElement, ITreeModel, ITreeNode, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; +import { ICollapseStateChangeEvent, IObjectTreeElement, ITreeModel, ITreeNode, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { memoize } from 'vs/base/common/decorators'; import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; @@ -36,16 +36,9 @@ export interface IObjectTreeSetChildrenOptions<T> { readonly diffIdentityProvider?: IIdentityProvider<T>; } -export interface IObjectTreeViewState { - readonly focus: string[]; - readonly selection: string[]; - readonly expanded: string[]; - readonly scrollTop: number; -} - export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends AbstractTree<T | null, TFilterData, T | null> { - protected override model!: IObjectTreeModel<T, TFilterData>; + protected declare model: IObjectTreeModel<T, TFilterData>; override get onDidChangeCollapseState(): Event<ICollapseStateChangeEvent<T | null, TFilterData>> { return this.model.onDidChangeCollapseState; } @@ -59,7 +52,7 @@ export class ObjectTree<T extends NonNullable<any>, TFilterData = void> extends super(user, container, delegate, renderers, options as IObjectTreeOptions<T | null, TFilterData>); } - setChildren(element: T | null, children: Iterable<ITreeElement<T>> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions<T>): void { + setChildren(element: T | null, children: Iterable<IObjectTreeElement<T>> = Iterable.empty(), options?: IObjectTreeSetChildrenOptions<T>): void { this.model.setChildren(element, children, options); } @@ -197,7 +190,7 @@ export interface ICompressibleObjectTreeOptionsUpdate extends IAbstractTreeOptio export class CompressibleObjectTree<T extends NonNullable<any>, TFilterData = void> extends ObjectTree<T, TFilterData> implements ICompressedTreeNodeProvider<T, TFilterData> { - protected override model!: CompressibleObjectTreeModel<T, TFilterData>; + protected declare model: CompressibleObjectTreeModel<T, TFilterData>; constructor( user: string, diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 5d05070f9c..fde58deb32 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -5,14 +5,14 @@ import { IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { IIndexTreeModelOptions, IIndexTreeModelSpliceOptions, IList, IndexTreeModel } from 'vs/base/browser/ui/tree/indexTreeModel'; -import { ICollapseStateChangeEvent, ITreeElement, ITreeModel, ITreeModelSpliceEvent, ITreeNode, ITreeSorter, TreeError } from 'vs/base/browser/ui/tree/tree'; +import { ICollapseStateChangeEvent, IObjectTreeElement, ITreeElement, ITreeModel, ITreeModelSpliceEvent, ITreeNode, ITreeSorter, ObjectTreeElementCollapseState, TreeError } from 'vs/base/browser/ui/tree/tree'; import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; export type ITreeNodeCallback<T, TFilterData> = (node: ITreeNode<T, TFilterData>) => void; export interface IObjectTreeModel<T extends NonNullable<any>, TFilterData extends NonNullable<any> = void> extends ITreeModel<T | null, TFilterData, T | null> { - setChildren(element: T | null, children: Iterable<ITreeElement<T>> | undefined, options?: IObjectTreeModelSetChildrenOptions<T, TFilterData>): void; + setChildren(element: T | null, children: Iterable<IObjectTreeElement<T>> | undefined, options?: IObjectTreeModelSetChildrenOptions<T, TFilterData>): void; resort(element?: T | null, recursive?: boolean): void; updateElementHeight(element: T, height: number | undefined): void; } @@ -64,7 +64,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non setChildren( element: T | null, - children: Iterable<ITreeElement<T>> = Iterable.empty(), + children: Iterable<IObjectTreeElement<T>> = Iterable.empty(), options: IObjectTreeModelSetChildrenOptions<T, TFilterData> = {}, ): void { const location = this.getElementLocation(element); @@ -127,7 +127,7 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non ); } - private preserveCollapseState(elements: Iterable<ITreeElement<T>> = Iterable.empty()): Iterable<ITreeElement<T>> { + private preserveCollapseState(elements: Iterable<IObjectTreeElement<T>> = Iterable.empty()): Iterable<ITreeElement<T>> { if (this.sorter) { elements = [...elements].sort(this.sorter.compare.bind(this.sorter)); } @@ -141,14 +141,37 @@ export class ObjectTreeModel<T extends NonNullable<any>, TFilterData extends Non } if (!node) { + let collapsed: boolean | undefined; + + if (typeof treeElement.collapsed === 'undefined') { + collapsed = undefined; + } else if (treeElement.collapsed === ObjectTreeElementCollapseState.Collapsed || treeElement.collapsed === ObjectTreeElementCollapseState.PreserveOrCollapsed) { + collapsed = true; + } else if (treeElement.collapsed === ObjectTreeElementCollapseState.Expanded || treeElement.collapsed === ObjectTreeElementCollapseState.PreserveOrExpanded) { + collapsed = false; + } else { + collapsed = Boolean(treeElement.collapsed); + } + return { ...treeElement, - children: this.preserveCollapseState(treeElement.children) + children: this.preserveCollapseState(treeElement.children), + collapsed }; } const collapsible = typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : node.collapsible; - const collapsed = typeof treeElement.collapsed !== 'undefined' ? treeElement.collapsed : node.collapsed; + let collapsed: boolean | undefined; + + if (typeof treeElement.collapsed === 'undefined' || treeElement.collapsed === ObjectTreeElementCollapseState.PreserveOrCollapsed || treeElement.collapsed === ObjectTreeElementCollapseState.PreserveOrExpanded) { + collapsed = node.collapsed; + } else if (treeElement.collapsed === ObjectTreeElementCollapseState.Collapsed) { + collapsed = true; + } else if (treeElement.collapsed === ObjectTreeElementCollapseState.Expanded) { + collapsed = false; + } else { + collapsed = Boolean(treeElement.collapsed); + } return { ...treeElement, diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 126012ace8..dd1d3e67c2 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -78,6 +78,28 @@ export interface ITreeElement<T> { readonly collapsed?: boolean; } +export enum ObjectTreeElementCollapseState { + Expanded, + Collapsed, + + /** + * If the element is already in the tree, preserve its current state. Else, expand it. + */ + PreserveOrExpanded, + + /** + * If the element is already in the tree, preserve its current state. Else, collapse it. + */ + PreserveOrCollapsed, +} + +export interface IObjectTreeElement<T> { + readonly element: T; + readonly children?: Iterable<IObjectTreeElement<T>>; + readonly collapsible?: boolean; + readonly collapsed?: boolean | ObjectTreeElementCollapseState; +} + export interface ITreeNode<T, TFilterData = void> { readonly element: T; readonly children: ITreeNode<T, TFilterData>[]; @@ -134,8 +156,8 @@ export interface ITreeRenderer<T, TFilterData = void, TTemplateData = void> exte } export interface ITreeEvent<T> { - elements: T[]; - browserEvent?: UIEvent; + readonly elements: readonly T[]; + readonly browserEvent?: UIEvent; } export enum TreeMouseEventTarget { @@ -146,15 +168,15 @@ export enum TreeMouseEventTarget { } export interface ITreeMouseEvent<T> { - browserEvent: MouseEvent; - element: T | null; - target: TreeMouseEventTarget; + readonly browserEvent: MouseEvent; + readonly element: T | null; + readonly target: TreeMouseEventTarget; } export interface ITreeContextMenuEvent<T> { - browserEvent: UIEvent; - element: T | null; - anchor: HTMLElement | { x: number; y: number }; + readonly browserEvent: UIEvent; + readonly element: T | null; + readonly anchor: HTMLElement | { readonly x: number; readonly y: number }; } export interface ITreeNavigator<T> { diff --git a/src/vs/base/browser/ui/widget.ts b/src/vs/base/browser/ui/widget.ts index 669da7baa0..7e924cb63c 100644 --- a/src/vs/base/browser/ui/widget.ts +++ b/src/vs/base/browser/ui/widget.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Gesture } from 'vs/base/browser/touch'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; export abstract class Widget extends Disposable { @@ -51,7 +51,7 @@ export abstract class Widget extends Disposable { this._register(dom.addDisposableListener(domNode, dom.EventType.CHANGE, listener)); } - protected ignoreGesture(domNode: HTMLElement): void { - Gesture.ignoreTarget(domNode); + protected ignoreGesture(domNode: HTMLElement): IDisposable { + return Gesture.ignoreTarget(domNode); } } diff --git a/src/vs/base/common/actions.ts b/src/vs/base/common/actions.ts index 3beed2aaec..06f934a6b5 100644 --- a/src/vs/base/common/actions.ts +++ b/src/vs/base/common/actions.ts @@ -14,17 +14,20 @@ export interface ITelemetryData { } export type WorkbenchActionExecutedClassification = { - owner: 'bpasero'; id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was run.' }; from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the component the action was run from.' }; + detail?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Optional details about how the action was run, e.g which keybinding was used.' }; + owner: 'bpasero'; + comment: 'Provides insight into actions that are executed within the workbench.'; }; export type WorkbenchActionExecutedEvent = { id: string; from: string; + detail?: string; }; -export interface IAction extends IDisposable { +export interface IAction { readonly id: string; label: string; tooltip: string; @@ -32,12 +35,12 @@ export interface IAction extends IDisposable { enabled: boolean; checked?: boolean; expanded?: boolean | undefined; // {{SQL CARBON EDIT}} - run(event?: unknown): unknown; + run(...args: unknown[]): unknown; } export interface IActionRunner extends IDisposable { readonly onDidRun: Event<IRunEvent>; - readonly onBeforeRun: Event<IRunEvent>; + readonly onWillRun: Event<IRunEvent>; run(action: IAction, context?: unknown): unknown; } @@ -183,10 +186,10 @@ export interface IRunEvent { export class ActionRunner extends Disposable implements IActionRunner { - private _onBeforeRun = this._register(new Emitter<IRunEvent>()); - readonly onBeforeRun = this._onBeforeRun.event; + private readonly _onWillRun = this._register(new Emitter<IRunEvent>()); + readonly onWillRun = this._onWillRun.event; - private _onDidRun = this._register(new Emitter<IRunEvent>()); + private readonly _onDidRun = this._register(new Emitter<IRunEvent>()); readonly onDidRun = this._onDidRun.event; async run(action: IAction, context?: unknown): Promise<void> { @@ -194,7 +197,7 @@ export class ActionRunner extends Disposable implements IActionRunner { return; } - this._onBeforeRun.fire({ action }); + this._onWillRun.fire({ action }); let error: Error | undefined = undefined; try { @@ -211,7 +214,7 @@ export class ActionRunner extends Disposable implements IActionRunner { } } -export class Separator extends Action { +export class Separator implements IAction { /** * Joins all non-empty lists of actions with separators. @@ -233,12 +236,14 @@ export class Separator extends Action { static readonly ID = 'vs.actions.separator'; - constructor(label?: string) { - super(Separator.ID, label, label ? 'separator text' : 'separator'); + readonly id: string = Separator.ID; - this.checked = false; - this.enabled = false; - } + readonly label: string = ''; + readonly tooltip: string = ''; + readonly class: string = 'separator'; + readonly enabled: boolean = false; + readonly checked: boolean = false; + async run() { } } export class SubmenuAction implements IAction { @@ -260,12 +265,6 @@ export class SubmenuAction implements IAction { this._actions = actions; } - dispose(): void { - // there is NOTHING to dispose and the SubmenuAction should - // never have anything to dispose as it is a convenience type - // to bridge into the rendering world. - } - async run(): Promise<void> { } // {{SQL CARBON EDIT}} @@ -297,7 +296,6 @@ export function toAction(props: { id: string; label: string; enabled?: boolean; enabled: props.enabled ?? true, checked: props.checked ?? false, run: async () => props.run(), - tooltip: props.label, - dispose: () => { } + tooltip: props.label }; } diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts index 046561da04..97f1b9e09f 100644 --- a/src/vs/base/common/amd.ts +++ b/src/vs/base/common/amd.ts @@ -40,7 +40,10 @@ export abstract class LoaderStats { map.set(stat.detail, duration + stat.timestamp); } - const stats = require.getStats().slice(0).sort((a, b) => a.timestamp - b.timestamp); + let stats: readonly LoaderEvent[] = []; + if (typeof require.getStats === 'function') { + stats = require.getStats().slice(0).sort((a, b) => a.timestamp - b.timestamp); + } for (const stat of stats) { switch (stat.type) { diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index be9df2bae8..ca458fdb3a 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -178,6 +178,7 @@ export function groupBy<T>(data: ReadonlyArray<T>, compare: (a: T, b: T) => numb } interface IMutableSplice<T> extends ISplice<T> { + readonly toInsert: T[]; deleteCount: number; } @@ -829,3 +830,79 @@ export class ArrayQueue<T> { return result; } } + +/** + * This class is faster than an iterator and array for lazy computed data. +*/ +export class CallbackIterable<T> { + public static readonly empty = new CallbackIterable<never>(_callback => { }); + + constructor( + /** + * Calls the callback for every item. + * Stops when the callback returns false. + */ + public readonly iterate: (callback: (item: T) => boolean) => void + ) { + } + + forEach(handler: (item: T) => void) { + this.iterate(item => { handler(item); return true; }); + } + + toArray(): T[] { + const result: T[] = []; + this.iterate(item => { result.push(item); return true; }); + return result; + } + + filter(predicate: (item: T) => boolean): CallbackIterable<T> { + return new CallbackIterable(cb => this.iterate(item => predicate(item) ? cb(item) : true)); + } + + map<TResult>(mapFn: (item: T) => TResult): CallbackIterable<TResult> { + return new CallbackIterable<TResult>(cb => this.iterate(item => cb(mapFn(item)))); + } + + some(predicate: (item: T) => boolean): boolean { + let result = false; + this.iterate(item => { result = predicate(item); return !result; }); + return result; + } + + findFirst(predicate: (item: T) => boolean): T | undefined { + let result: T | undefined; + this.iterate(item => { + if (predicate(item)) { + result = item; + return false; + } + return true; + }); + return result; + } + + findLast(predicate: (item: T) => boolean): T | undefined { + let result: T | undefined; + this.iterate(item => { + if (predicate(item)) { + result = item; + } + return true; + }); + return result; + } + + findLastMaxBy(comparator: Comparator<T>): T | undefined { + let result: T | undefined; + let first = true; + this.iterate(item => { + if (first || CompareResult.isGreaterThan(comparator(item, result!))) { + first = false; + result = item; + } + return true; + }); + return result; + } +} diff --git a/src/vs/base/common/assert.ts b/src/vs/base/common/assert.ts index e504c5b0a8..c4292d78d3 100644 --- a/src/vs/base/common/assert.ts +++ b/src/vs/base/common/assert.ts @@ -3,11 +3,60 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; + /** * Throws an error with the provided message if the provided value does not evaluate to a true Javascript value. + * + * @deprecated Use `assert(...)` instead. + * This method is usually used like this: + * ```ts + * import * as assert from 'vs/base/common/assert'; + * assert.ok(...); + * ``` + * + * However, `assert` in that example is a user chosen name. + * There is no tooling for generating such an import statement. + * Thus, the `assert(...)` function should be used instead. */ export function ok(value?: unknown, message?: string) { if (!value) { throw new Error(message ? `Assertion failed (${message})` : 'Assertion Failed'); } } + +export function assertNever(value: never, message = 'Unreachable'): never { + throw new Error(message); +} + +export function assert(condition: boolean): void { + if (!condition) { + throw new BugIndicatingError('Assertion Failed'); + } +} + +/** + * condition must be side-effect free! + */ +export function assertFn(condition: () => boolean): void { + if (!condition()) { + // eslint-disable-next-line no-debugger + debugger; + // Reevaluate `condition` again to make debugging easier + condition(); + onUnexpectedError(new BugIndicatingError('Assertion Failed')); + } +} + +export function checkAdjacentItems<T>(items: readonly T[], predicate: (item1: T, item2: T) => boolean): boolean { + let i = 0; + while (i < items.length - 1) { + const a = items[i]; + const b = items[i + 1]; + if (!predicate(a, b)) { + return false; + } + i++; + } + return true; +} diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 0979ef200e..3f4dafbe4c 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -10,6 +10,7 @@ import { Disposable, IDisposable, MutableDisposable, toDisposable } from 'vs/bas import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { setTimeout0 } from 'vs/base/common/platform'; +import { MicrotaskDelay } from './symbols'; export function isThenable<T>(obj: unknown): obj is Promise<T> { return !!obj && typeof (obj as unknown as Promise<T>).then === 'function'; @@ -93,18 +94,21 @@ export function raceCancellationError<T>(promise: Promise<T>, token: Cancellatio } /** - * Returns as soon as one of the promises is resolved and cancels remaining promises + * Returns as soon as one of the promises resolves or rejects and cancels remaining promises */ export async function raceCancellablePromises<T>(cancellablePromises: CancelablePromise<T>[]): Promise<T> { let resolvedPromiseIndex = -1; const promises = cancellablePromises.map((promise, index) => promise.then(result => { resolvedPromiseIndex = index; return result; })); - const result = await Promise.race(promises); - cancellablePromises.forEach((cancellablePromise, index) => { - if (index !== resolvedPromiseIndex) { - cancellablePromise.cancel(); - } - }); - return result; + try { + const result = await Promise.race(promises); + return result; + } finally { + cancellablePromises.forEach((cancellablePromise, index) => { + if (index !== resolvedPromiseIndex) { + cancellablePromise.cancel(); + } + }); + } } export function raceTimeout<T>(promise: Promise<T>, timeout: number, onTimeout?: () => void): Promise<T | undefined> { @@ -274,9 +278,6 @@ const microtaskDeferred = (fn: () => void): IScheduledLater => { }; }; -/** Can be passed into the Delayed to defer using a microtask */ -export const MicrotaskDelay = Symbol('MicrotaskDelay'); - /** * A helper to delay (debounce) execution of a task that is being requested often. * @@ -819,7 +820,7 @@ export class IntervalTimer implements IDisposable { } } -export class RunOnceScheduler { +export class RunOnceScheduler implements IDisposable { protected runner: ((...args: unknown[]) => void) | null; @@ -875,6 +876,13 @@ export class RunOnceScheduler { return this.timeoutToken !== -1; } + flush(): void { + if (this.isScheduled()) { + this.cancel(); + this.doRun(); + } + } + private onTimeout() { this.timeoutToken = -1; if (this.runner) { @@ -961,6 +969,7 @@ export class ProcessTimeRunOnceScheduler { } export class RunOnceWorker<T> extends RunOnceScheduler { + private units: T[] = []; constructor(runner: (units: T[]) => void, timeout: number) { @@ -1068,7 +1077,9 @@ export class ThrottledWorker<T> extends Disposable { } // Add to pending units first - this.pendingWork.push(...units); + for (const unit of units) { + this.pendingWork.push(unit); + } // If not throttled, start working directly // Otherwise, when the throttle delay has @@ -1111,7 +1122,22 @@ export interface IdleDeadline { } /** - * Execute the callback the next time the browser is idle + * Execute the callback the next time the browser is idle, returning an + * {@link IDisposable} that will cancel the callback when disposed. This wraps + * [requestIdleCallback] so it will fallback to [setTimeout] if the environment + * doesn't support it. + * + * @param callback The callback to run when idle, this includes an + * [IdleDeadline] that provides the time alloted for the idle callback by the + * browser. Not respecting this deadline will result in a degraded user + * experience. + * @param timeout A timeout at which point to queue no longer wait for an idle + * callback but queue it on the regular event loop (like setTimeout). Typically + * this should not be used. + * + * [IdleDeadline]: https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline + * [requestIdleCallback]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback + * [setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout */ export let runWhenIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable; @@ -1227,15 +1253,15 @@ export async function retry<T>(task: ITask<Promise<T>>, delay: number, retries: //#region Task Sequentializer interface IPendingTask { - taskId: number; - cancel: () => void; - promise: Promise<void>; + readonly taskId: number; + readonly cancel: () => void; + readonly promise: Promise<void>; } -interface ISequentialTask { - promise: Promise<void>; - promiseResolve: () => void; - promiseReject: (error: Error) => void; +interface INextTask { + readonly promise: Promise<void>; + readonly promiseResolve: () => void; + readonly promiseReject: (error: Error) => void; run: () => Promise<void>; } @@ -1243,9 +1269,14 @@ export interface ITaskSequentializerWithPendingTask { readonly pending: Promise<void>; } +export interface ITaskSequentializerWithNextTask { + readonly next: INextTask; +} + export class TaskSequentializer { + private _pending?: IPendingTask; - private _next?: ISequentialTask; + private _next?: INextTask; hasPending(taskId?: number) { // {{SQL CARBON EDIT}} - type constraint causing compiler errors if (!this._pending) { @@ -1260,7 +1291,7 @@ export class TaskSequentializer { } get pending(): Promise<void> | undefined { - return this._pending ? this._pending.promise : undefined; + return this._pending?.promise; } cancelPending(): void { @@ -1324,6 +1355,14 @@ export class TaskSequentializer { return this._next.promise; } + + hasNext(): this is ITaskSequentializerWithNextTask { + return !!this._next; + } + + async join(): Promise<void> { + return this._next?.promise ?? this._pending?.promise; + } } //#endregion @@ -1367,6 +1406,11 @@ export class IntervalCounter { export type ValueCallback<T = unknown> = (value: T | Promise<T>) => void; +const enum DeferredOutcome { + Resolved, + Rejected +} + /** * Creates a promise whose resolution or rejection can be controlled imperatively. */ @@ -1374,22 +1418,25 @@ export class DeferredPromise<T> { private completeCallback!: ValueCallback<T>; private errorCallback!: (err: unknown) => void; - private rejected = false; - private resolved = false; + private outcome?: { outcome: DeferredOutcome.Rejected; value: any } | { outcome: DeferredOutcome.Resolved; value: T }; public get isRejected() { - return this.rejected; + return this.outcome?.outcome === DeferredOutcome.Rejected; } public get isResolved() { - return this.resolved; + return this.outcome?.outcome === DeferredOutcome.Resolved; } public get isSettled() { - return this.rejected || this.resolved; + return !!this.outcome; } - public p: Promise<T>; + public get value() { + return this.outcome?.outcome === DeferredOutcome.Resolved ? this.outcome?.value : undefined; + } + + public readonly p: Promise<T>; constructor() { this.p = new Promise<T>((c, e) => { @@ -1401,7 +1448,7 @@ export class DeferredPromise<T> { public complete(value: T) { return new Promise<void>(resolve => { this.completeCallback(value); - this.resolved = true; + this.outcome = { outcome: DeferredOutcome.Resolved, value }; resolve(); }); } @@ -1409,17 +1456,13 @@ export class DeferredPromise<T> { public error(err: unknown) { return new Promise<void>(resolve => { this.errorCallback(err); - this.rejected = true; + this.outcome = { outcome: DeferredOutcome.Rejected, value: err }; resolve(); }); } public cancel() { - new Promise<void>(resolve => { - this.errorCallback(new CancellationError()); - this.rejected = true; - resolve(); - }); + return this.error(new CancellationError()); } } @@ -1515,7 +1558,7 @@ export interface AsyncIterableEmitter<T> { /** * An executor for the `AsyncIterableObject` that has access to an emitter. */ -export interface AyncIterableExecutor<T> { +export interface AsyncIterableExecutor<T> { /** * @param emitter An object that allows to emit async values valid only for the duration of the executor. */ @@ -1562,7 +1605,7 @@ export class AsyncIterableObject<T> implements AsyncIterable<T> { private _error: Error | null; private readonly _onStateChanged: Emitter<void>; - constructor(executor: AyncIterableExecutor<T>) { + constructor(executor: AsyncIterableExecutor<T>) { this._state = AsyncIterableSourceState.Initial; this._results = []; this._error = null; @@ -1716,7 +1759,7 @@ export class AsyncIterableObject<T> implements AsyncIterable<T> { export class CancelableAsyncIterableObject<T> extends AsyncIterableObject<T> { constructor( private readonly _source: CancellationTokenSource, - executor: AyncIterableExecutor<T> + executor: AsyncIterableExecutor<T> ) { super(executor); } diff --git a/src/vs/base/common/buffer.ts b/src/vs/base/common/buffer.ts index 346ef6098c..be51831d7e 100644 --- a/src/vs/base/common/buffer.ts +++ b/src/vs/base/common/buffer.ts @@ -3,11 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Lazy } from 'vs/base/common/lazy'; import * as streams from 'vs/base/common/stream'; declare const Buffer: any; const hasBuffer = (typeof Buffer !== 'undefined'); +const indexOfTable = new Lazy(() => new Uint8Array(256)); let textEncoder: TextEncoder | null; let textDecoder: TextDecoder | null; @@ -169,6 +171,52 @@ export class VSBuffer { writeUInt8(value: number, offset: number): void { writeUInt8(this.buffer, value, offset); } + + indexOf(subarray: VSBuffer | Uint8Array) { + const needle = subarray instanceof VSBuffer ? subarray.buffer : subarray; + const needleLen = needle.byteLength; + const haystack = this.buffer; + const haystackLen = haystack.byteLength; + + if (needleLen === 0) { + return 0; + } + + if (needleLen === 1) { + return haystack.indexOf(needle[0]); + } + + if (needleLen > haystackLen) { + return -1; + } + + // find index of the subarray using boyer-moore-horspool algorithm + const table = indexOfTable.value; + table.fill(needle.length); + for (let i = 0; i < needle.length; i++) { + table[needle[i]] = needle.length - i - 1; + } + + let i = needle.length - 1; + let j = i; + let result = -1; + while (i < haystackLen) { + if (haystack[i] === needle[j]) { + if (j === 0) { + result = i; + break; + } + + i--; + j--; + } else { + i += Math.max(needle.length - j, table[haystack[i]]); + j = needle.length - 1; + } + } + + return result; + } } export function readUInt16LE(source: Uint8Array, offset: number): number { diff --git a/src/vs/base/common/cancellation.ts b/src/vs/base/common/cancellation.ts index ee9fce3712..e70084d353 100644 --- a/src/vs/base/common/cancellation.ts +++ b/src/vs/base/common/cancellation.ts @@ -129,9 +129,7 @@ export class CancellationTokenSource { if (cancel) { this.cancel(); } - if (this._parentListener) { - this._parentListener.dispose(); - } + this._parentListener?.dispose(); if (!this._token) { // ensure to initialize with an empty token if we had none this._token = CancellationToken.None; diff --git a/src/vs/base/common/charCode.ts b/src/vs/base/common/charCode.ts index dd50aad527..b5d0fa2064 100644 --- a/src/vs/base/common/charCode.ts +++ b/src/vs/base/common/charCode.ts @@ -225,6 +225,12 @@ export const enum CharCode { */ Tilde = 126, + /** + * The   (no-break space) character. + * Unicode Character 'NO-BREAK SPACE' (U+00A0) + */ + NoBreakSpace = 160, + U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent diff --git a/src/vs/base/common/codicons.ts b/src/vs/base/common/codicons.ts index 9f94df4e4f..f3f2c103b3 100644 --- a/src/vs/base/common/codicons.ts +++ b/src/vs/base/common/codicons.ts @@ -2,24 +2,35 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { ThemeIcon } from 'vs/base/common/themables'; +import { isString } from 'vs/base/common/types'; -import { SqlIconId } from 'sql/base/common/codicons'; // {{SQL CARBON EDIT}} -import { Event } from 'vs/base/common/event'; +const _codiconFontCharacters: { [id: string]: number } = Object.create(null); -export interface IIconRegistry { - readonly all: IterableIterator<Codicon>; - readonly onDidRegister: Event<Codicon>; - get(id: string): Codicon | undefined; +function register(id: string, fontCharacter: number | string): ThemeIcon { + if (isString(fontCharacter)) { + const val = _codiconFontCharacters[fontCharacter]; + if (val === undefined) { + throw new Error(`${id} references an unknown codicon: ${fontCharacter}`); + } + fontCharacter = val; + } + _codiconFontCharacters[id] = fontCharacter; + return { id }; } -// Selects all codicon names encapsulated in the `$()` syntax and wraps the -// results with spaces so that screen readers can read the text better. -export function getCodiconAriaLabel(text: string | undefined) { - if (!text) { - return ''; - } +/** + * Only to be used by the iconRegistry. + */ +export function getCodiconFontCharacters(): { [id: string]: number } { + return _codiconFontCharacters; +} - return text.replace(/\$\((.*?)\)/g, (_match, codiconName) => ` ${codiconName} `).trim(); +/** + * Only to be used by the iconRegistry. + */ +export function getAllCodicons(): ThemeIcon[] { + return Object.values(Codicon); } /** @@ -29,628 +40,559 @@ export function getCodiconAriaLabel(text: string | undefined) { * themeable, component should define new, UI component specific icons using `iconRegistry.registerIcon`. * In that call a Codicon can be named as default. */ -export class Codicon implements CSSIcon { - - private constructor(public readonly id: string, public readonly definition: IconDefinition, public description?: string) { - Codicon._allCodicons.push(this); - } - public get classNames() { return 'codicon codicon-' + this.id; } - // classNamesArray is useful for migrating to ES6 classlist - public get classNamesArray() { return ['codicon', 'codicon-' + this.id]; } - public get cssSelector() { return '.codicon.codicon-' + this.id; } - - // registry - private static _allCodicons: Codicon[] = []; - - /** - * @returns Returns all default icons covered by the codicon font. Only to be used by the icon registry in platform. - */ - public static getAll(): readonly Codicon[] { - return Codicon._allCodicons; - } +export const Codicon = { // built-in icons, with image name - public static readonly add = new Codicon('add', { fontCharacter: '\\ea60' }); - public static readonly plus = new Codicon('plus', Codicon.add.definition); - public static readonly gistNew = new Codicon('gist-new', Codicon.add.definition); - public static readonly repoCreate = new Codicon('repo-create', Codicon.add.definition); - public static readonly lightbulb = new Codicon('lightbulb', { fontCharacter: '\\ea61' }); - public static readonly lightBulb = new Codicon('light-bulb', { fontCharacter: '\\ea61' }); - public static readonly repo = new Codicon('repo', { fontCharacter: '\\ea62' }); - public static readonly repoDelete = new Codicon('repo-delete', { fontCharacter: '\\ea62' }); - public static readonly gistFork = new Codicon('gist-fork', { fontCharacter: '\\ea63' }); - public static readonly repoForked = new Codicon('repo-forked', { fontCharacter: '\\ea63' }); - public static readonly gitPullRequest = new Codicon('git-pull-request', { fontCharacter: '\\ea64' }); - public static readonly gitPullRequestAbandoned = new Codicon('git-pull-request-abandoned', { fontCharacter: '\\ea64' }); - public static readonly recordKeys = new Codicon('record-keys', { fontCharacter: '\\ea65' }); - public static readonly keyboard = new Codicon('keyboard', { fontCharacter: '\\ea65' }); - public static readonly tag = new Codicon('tag', { fontCharacter: '\\ea66' }); - public static readonly tagAdd = new Codicon('tag-add', { fontCharacter: '\\ea66' }); - public static readonly tagRemove = new Codicon('tag-remove', { fontCharacter: '\\ea66' }); - public static readonly person = new Codicon('person', { fontCharacter: '\\ea67' }); - public static readonly personFollow = new Codicon('person-follow', { fontCharacter: '\\ea67' }); - public static readonly personOutline = new Codicon('person-outline', { fontCharacter: '\\ea67' }); - public static readonly personFilled = new Codicon('person-filled', { fontCharacter: '\\ea67' }); - public static readonly gitBranch = new Codicon('git-branch', { fontCharacter: '\\ea68' }); - public static readonly gitBranchCreate = new Codicon('git-branch-create', { fontCharacter: '\\ea68' }); - public static readonly gitBranchDelete = new Codicon('git-branch-delete', { fontCharacter: '\\ea68' }); - public static readonly sourceControl = new Codicon('source-control', { fontCharacter: '\\ea68' }); - public static readonly mirror = new Codicon('mirror', { fontCharacter: '\\ea69' }); - public static readonly mirrorPublic = new Codicon('mirror-public', { fontCharacter: '\\ea69' }); - public static readonly star = new Codicon('star', { fontCharacter: '\\ea6a' }); - public static readonly starAdd = new Codicon('star-add', { fontCharacter: '\\ea6a' }); - public static readonly starDelete = new Codicon('star-delete', { fontCharacter: '\\ea6a' }); - public static readonly starEmpty = new Codicon('star-empty', { fontCharacter: '\\ea6a' }); - public static readonly comment = new Codicon('comment', { fontCharacter: '\\ea6b' }); - public static readonly commentAdd = new Codicon('comment-add', { fontCharacter: '\\ea6b' }); - public static readonly alert = new Codicon('alert', { fontCharacter: '\\ea6c' }); - public static readonly warning = new Codicon('warning', { fontCharacter: '\\ea6c' }); - public static readonly search = new Codicon('search', { fontCharacter: '\\ea6d' }); - public static readonly searchSave = new Codicon('search-save', { fontCharacter: '\\ea6d' }); - public static readonly logOut = new Codicon('log-out', { fontCharacter: '\\ea6e' }); - public static readonly signOut = new Codicon('sign-out', { fontCharacter: '\\ea6e' }); - public static readonly logIn = new Codicon('log-in', { fontCharacter: '\\ea6f' }); - public static readonly signIn = new Codicon('sign-in', { fontCharacter: '\\ea6f' }); - public static readonly eye = new Codicon('eye', { fontCharacter: '\\ea70' }); - public static readonly eyeUnwatch = new Codicon('eye-unwatch', { fontCharacter: '\\ea70' }); - public static readonly eyeWatch = new Codicon('eye-watch', { fontCharacter: '\\ea70' }); - public static readonly circleFilled = new Codicon('circle-filled', { fontCharacter: '\\ea71' }); - public static readonly primitiveDot = new Codicon('primitive-dot', { fontCharacter: '\\ea71' }); - public static readonly closeDirty = new Codicon('close-dirty', { fontCharacter: '\\ea71' }); - public static readonly debugBreakpoint = new Codicon('debug-breakpoint', { fontCharacter: '\\ea71' }); - public static readonly debugBreakpointDisabled = new Codicon('debug-breakpoint-disabled', { fontCharacter: '\\ea71' }); - public static readonly debugHint = new Codicon('debug-hint', { fontCharacter: '\\ea71' }); - public static readonly primitiveSquare = new Codicon('primitive-square', { fontCharacter: '\\ea72' }); - public static readonly edit = new Codicon('edit', { fontCharacter: '\\ea73' }); - public static readonly pencil = new Codicon('pencil', { fontCharacter: '\\ea73' }); - public static readonly info = new Codicon('info', { fontCharacter: '\\ea74' }); - public static readonly issueOpened = new Codicon('issue-opened', { fontCharacter: '\\ea74' }); - public static readonly gistPrivate = new Codicon('gist-private', { fontCharacter: '\\ea75' }); - public static readonly gitForkPrivate = new Codicon('git-fork-private', { fontCharacter: '\\ea75' }); - public static readonly lock = new Codicon('lock', { fontCharacter: '\\ea75' }); - public static readonly mirrorPrivate = new Codicon('mirror-private', { fontCharacter: '\\ea75' }); - public static readonly close = new Codicon('close', { fontCharacter: '\\ea76' }); - public static readonly removeClose = new Codicon('remove-close', { fontCharacter: '\\ea76' }); - public static readonly x = new Codicon('x', { fontCharacter: '\\ea76' }); - public static readonly repoSync = new Codicon('repo-sync', { fontCharacter: '\\ea77' }); - public static readonly sync = new Codicon('sync', { fontCharacter: '\\ea77' }); - public static readonly clone = new Codicon('clone', { fontCharacter: '\\ea78' }); - public static readonly desktopDownload = new Codicon('desktop-download', { fontCharacter: '\\ea78' }); - public static readonly beaker = new Codicon('beaker', { fontCharacter: '\\ea79' }); - public static readonly microscope = new Codicon('microscope', { fontCharacter: '\\ea79' }); - public static readonly vm = new Codicon('vm', { fontCharacter: '\\ea7a' }); - public static readonly deviceDesktop = new Codicon('device-desktop', { fontCharacter: '\\ea7a' }); - public static readonly file = new Codicon('file', { fontCharacter: '\\ea7b' }); - public static readonly fileText = new Codicon('file-text', { fontCharacter: '\\ea7b' }); - public static readonly more = new Codicon('more', { fontCharacter: '\\ea7c' }); - public static readonly ellipsis = new Codicon('ellipsis', { fontCharacter: '\\ea7c' }); - public static readonly kebabHorizontal = new Codicon('kebab-horizontal', { fontCharacter: '\\ea7c' }); - public static readonly mailReply = new Codicon('mail-reply', { fontCharacter: '\\ea7d' }); - public static readonly reply = new Codicon('reply', { fontCharacter: '\\ea7d' }); - public static readonly organization = new Codicon('organization', { fontCharacter: '\\ea7e' }); - public static readonly organizationFilled = new Codicon('organization-filled', { fontCharacter: '\\ea7e' }); - public static readonly organizationOutline = new Codicon('organization-outline', { fontCharacter: '\\ea7e' }); - public static readonly newFile = new Codicon('new-file', { fontCharacter: '\\ea7f' }); - public static readonly fileAdd = new Codicon('file-add', { fontCharacter: '\\ea7f' }); - public static readonly newFolder = new Codicon('new-folder', { fontCharacter: '\\ea80' }); - public static readonly fileDirectoryCreate = new Codicon('file-directory-create', { fontCharacter: '\\ea80' }); - public static readonly trash = new Codicon('trash', { fontCharacter: '\\ea81' }); - public static readonly trashcan = new Codicon('trashcan', { fontCharacter: '\\ea81' }); - public static readonly history = new Codicon('history', { fontCharacter: '\\ea82' }); - public static readonly clock = new Codicon('clock', { fontCharacter: '\\ea82' }); - public static readonly folder = new Codicon('folder', { fontCharacter: '\\ea83' }); - public static readonly fileDirectory = new Codicon('file-directory', { fontCharacter: '\\ea83' }); - public static readonly symbolFolder = new Codicon('symbol-folder', { fontCharacter: '\\ea83' }); - public static readonly logoGithub = new Codicon('logo-github', { fontCharacter: '\\ea84' }); - public static readonly markGithub = new Codicon('mark-github', { fontCharacter: '\\ea84' }); - public static readonly github = new Codicon('github', { fontCharacter: '\\ea84' }); - public static readonly terminal = new Codicon('terminal', { fontCharacter: '\\ea85' }); - public static readonly console = new Codicon('console', { fontCharacter: '\\ea85' }); - public static readonly repl = new Codicon('repl', { fontCharacter: '\\ea85' }); - public static readonly zap = new Codicon('zap', { fontCharacter: '\\ea86' }); - public static readonly symbolEvent = new Codicon('symbol-event', { fontCharacter: '\\ea86' }); - public static readonly error = new Codicon('error', { fontCharacter: '\\ea87' }); - public static readonly stop = new Codicon('stop', { fontCharacter: '\\ea87' }); - public static readonly variable = new Codicon('variable', { fontCharacter: '\\ea88' }); - public static readonly symbolVariable = new Codicon('symbol-variable', { fontCharacter: '\\ea88' }); - public static readonly array = new Codicon('array', { fontCharacter: '\\ea8a' }); - public static readonly symbolArray = new Codicon('symbol-array', { fontCharacter: '\\ea8a' }); - public static readonly symbolModule = new Codicon('symbol-module', { fontCharacter: '\\ea8b' }); - public static readonly symbolPackage = new Codicon('symbol-package', { fontCharacter: '\\ea8b' }); - public static readonly symbolNamespace = new Codicon('symbol-namespace', { fontCharacter: '\\ea8b' }); - public static readonly symbolObject = new Codicon('symbol-object', { fontCharacter: '\\ea8b' }); - public static readonly symbolMethod = new Codicon('symbol-method', { fontCharacter: '\\ea8c' }); - public static readonly symbolFunction = new Codicon('symbol-function', { fontCharacter: '\\ea8c' }); - public static readonly symbolConstructor = new Codicon('symbol-constructor', { fontCharacter: '\\ea8c' }); - public static readonly symbolBoolean = new Codicon('symbol-boolean', { fontCharacter: '\\ea8f' }); - public static readonly symbolNull = new Codicon('symbol-null', { fontCharacter: '\\ea8f' }); - public static readonly symbolNumeric = new Codicon('symbol-numeric', { fontCharacter: '\\ea90' }); - public static readonly symbolNumber = new Codicon('symbol-number', { fontCharacter: '\\ea90' }); - public static readonly symbolStructure = new Codicon('symbol-structure', { fontCharacter: '\\ea91' }); - public static readonly symbolStruct = new Codicon('symbol-struct', { fontCharacter: '\\ea91' }); - public static readonly symbolParameter = new Codicon('symbol-parameter', { fontCharacter: '\\ea92' }); - public static readonly symbolTypeParameter = new Codicon('symbol-type-parameter', { fontCharacter: '\\ea92' }); - public static readonly symbolKey = new Codicon('symbol-key', { fontCharacter: '\\ea93' }); - public static readonly symbolText = new Codicon('symbol-text', { fontCharacter: '\\ea93' }); - public static readonly symbolReference = new Codicon('symbol-reference', { fontCharacter: '\\ea94' }); - public static readonly goToFile = new Codicon('go-to-file', { fontCharacter: '\\ea94' }); - public static readonly symbolEnum = new Codicon('symbol-enum', { fontCharacter: '\\ea95' }); - public static readonly symbolValue = new Codicon('symbol-value', { fontCharacter: '\\ea95' }); - public static readonly symbolRuler = new Codicon('symbol-ruler', { fontCharacter: '\\ea96' }); - public static readonly symbolUnit = new Codicon('symbol-unit', { fontCharacter: '\\ea96' }); - public static readonly activateBreakpoints = new Codicon('activate-breakpoints', { fontCharacter: '\\ea97' }); - public static readonly archive = new Codicon('archive', { fontCharacter: '\\ea98' }); - public static readonly arrowBoth = new Codicon('arrow-both', { fontCharacter: '\\ea99' }); - public static readonly arrowDown = new Codicon('arrow-down', { fontCharacter: '\\ea9a' }); - public static readonly arrowLeft = new Codicon('arrow-left', { fontCharacter: '\\ea9b' }); - public static readonly arrowRight = new Codicon('arrow-right', { fontCharacter: '\\ea9c' }); - public static readonly arrowSmallDown = new Codicon('arrow-small-down', { fontCharacter: '\\ea9d' }); - public static readonly arrowSmallLeft = new Codicon('arrow-small-left', { fontCharacter: '\\ea9e' }); - public static readonly arrowSmallRight = new Codicon('arrow-small-right', { fontCharacter: '\\ea9f' }); - public static readonly arrowSmallUp = new Codicon('arrow-small-up', { fontCharacter: '\\eaa0' }); - public static readonly arrowUp = new Codicon('arrow-up', { fontCharacter: '\\eaa1' }); - public static readonly bell = new Codicon('bell', { fontCharacter: '\\eaa2' }); - public static readonly bold = new Codicon('bold', { fontCharacter: '\\eaa3' }); - public static readonly book = new Codicon('book', { fontCharacter: '\\eaa4' }); - public static readonly bookmark = new Codicon('bookmark', { fontCharacter: '\\eaa5' }); - public static readonly debugBreakpointConditionalUnverified = new Codicon('debug-breakpoint-conditional-unverified', { fontCharacter: '\\eaa6' }); - public static readonly debugBreakpointConditional = new Codicon('debug-breakpoint-conditional', { fontCharacter: '\\eaa7' }); - public static readonly debugBreakpointConditionalDisabled = new Codicon('debug-breakpoint-conditional-disabled', { fontCharacter: '\\eaa7' }); - public static readonly debugBreakpointDataUnverified = new Codicon('debug-breakpoint-data-unverified', { fontCharacter: '\\eaa8' }); - public static readonly debugBreakpointData = new Codicon('debug-breakpoint-data', { fontCharacter: '\\eaa9' }); - public static readonly debugBreakpointDataDisabled = new Codicon('debug-breakpoint-data-disabled', { fontCharacter: '\\eaa9' }); - public static readonly debugBreakpointLogUnverified = new Codicon('debug-breakpoint-log-unverified', { fontCharacter: '\\eaaa' }); - public static readonly debugBreakpointLog = new Codicon('debug-breakpoint-log', { fontCharacter: '\\eaab' }); - public static readonly debugBreakpointLogDisabled = new Codicon('debug-breakpoint-log-disabled', { fontCharacter: '\\eaab' }); - public static readonly briefcase = new Codicon('briefcase', { fontCharacter: '\\eaac' }); - public static readonly broadcast = new Codicon('broadcast', { fontCharacter: '\\eaad' }); - public static readonly browser = new Codicon('browser', { fontCharacter: '\\eaae' }); - public static readonly bug = new Codicon('bug', { fontCharacter: '\\eaaf' }); - public static readonly calendar = new Codicon('calendar', { fontCharacter: '\\eab0' }); - public static readonly caseSensitive = new Codicon('case-sensitive', { fontCharacter: '\\eab1' }); - public static readonly check = new Codicon('check', { fontCharacter: '\\eab2' }); - public static readonly checklist = new Codicon('checklist', { fontCharacter: '\\eab3' }); - public static readonly chevronDown = new Codicon('chevron-down', { fontCharacter: '\\eab4' }); - public static readonly dropDownButton = new Codicon('drop-down-button', Codicon.chevronDown.definition); - public static readonly chevronLeft = new Codicon('chevron-left', { fontCharacter: '\\eab5' }); - public static readonly chevronRight = new Codicon('chevron-right', { fontCharacter: '\\eab6' }); - public static readonly chevronUp = new Codicon('chevron-up', { fontCharacter: '\\eab7' }); - public static readonly chromeClose = new Codicon('chrome-close', { fontCharacter: '\\eab8' }); - public static readonly chromeMaximize = new Codicon('chrome-maximize', { fontCharacter: '\\eab9' }); - public static readonly chromeMinimize = new Codicon('chrome-minimize', { fontCharacter: '\\eaba' }); - public static readonly chromeRestore = new Codicon('chrome-restore', { fontCharacter: '\\eabb' }); - public static readonly circleOutline = new Codicon('circle-outline', { fontCharacter: '\\eabc' }); - public static readonly debugBreakpointUnverified = new Codicon('debug-breakpoint-unverified', { fontCharacter: '\\eabc' }); - public static readonly circleSlash = new Codicon('circle-slash', { fontCharacter: '\\eabd' }); - public static readonly circuitBoard = new Codicon('circuit-board', { fontCharacter: '\\eabe' }); - public static readonly clearAll = new Codicon('clear-all', { fontCharacter: '\\eabf' }); - public static readonly clippy = new Codicon('clippy', { fontCharacter: '\\eac0' }); - public static readonly closeAll = new Codicon('close-all', { fontCharacter: '\\eac1' }); - public static readonly cloudDownload = new Codicon('cloud-download', { fontCharacter: '\\eac2' }); - public static readonly cloudUpload = new Codicon('cloud-upload', { fontCharacter: '\\eac3' }); - public static readonly code = new Codicon('code', { fontCharacter: '\\eac4' }); - public static readonly collapseAll = new Codicon('collapse-all', { fontCharacter: '\\eac5' }); - public static readonly colorMode = new Codicon('color-mode', { fontCharacter: '\\eac6' }); - public static readonly commentDiscussion = new Codicon('comment-discussion', { fontCharacter: '\\eac7' }); - public static readonly compareChanges = new Codicon('compare-changes', { fontCharacter: '\\eafd' }); - public static readonly creditCard = new Codicon('credit-card', { fontCharacter: '\\eac9' }); - public static readonly dash = new Codicon('dash', { fontCharacter: '\\eacc' }); - public static readonly dashboard = new Codicon('dashboard', { fontCharacter: '\\eacd' }); - public static readonly database = new Codicon('database', { fontCharacter: '\\eace' }); - public static readonly debugContinue = new Codicon('debug-continue', { fontCharacter: '\\eacf' }); - public static readonly debugDisconnect = new Codicon('debug-disconnect', { fontCharacter: '\\ead0' }); - public static readonly debugPause = new Codicon('debug-pause', { fontCharacter: '\\ead1' }); - public static readonly debugRestart = new Codicon('debug-restart', { fontCharacter: '\\ead2' }); - public static readonly debugStart = new Codicon('debug-start', { fontCharacter: '\\ead3' }); - public static readonly debugStepInto = new Codicon('debug-step-into', { fontCharacter: '\\ead4' }); - public static readonly debugStepOut = new Codicon('debug-step-out', { fontCharacter: '\\ead5' }); - public static readonly debugStepOver = new Codicon('debug-step-over', { fontCharacter: '\\ead6' }); - public static readonly debugStop = new Codicon('debug-stop', { fontCharacter: '\\ead7' }); - public static readonly debug = new Codicon('debug', { fontCharacter: '\\ead8' }); - public static readonly deviceCameraVideo = new Codicon('device-camera-video', { fontCharacter: '\\ead9' }); - public static readonly deviceCamera = new Codicon('device-camera', { fontCharacter: '\\eada' }); - public static readonly deviceMobile = new Codicon('device-mobile', { fontCharacter: '\\eadb' }); - public static readonly diffAdded = new Codicon('diff-added', { fontCharacter: '\\eadc' }); - public static readonly diffIgnored = new Codicon('diff-ignored', { fontCharacter: '\\eadd' }); - public static readonly diffModified = new Codicon('diff-modified', { fontCharacter: '\\eade' }); - public static readonly diffRemoved = new Codicon('diff-removed', { fontCharacter: '\\eadf' }); - public static readonly diffRenamed = new Codicon('diff-renamed', { fontCharacter: '\\eae0' }); - public static readonly diff = new Codicon('diff', { fontCharacter: '\\eae1' }); - public static readonly discard = new Codicon('discard', { fontCharacter: '\\eae2' }); - public static readonly editorLayout = new Codicon('editor-layout', { fontCharacter: '\\eae3' }); - public static readonly emptyWindow = new Codicon('empty-window', { fontCharacter: '\\eae4' }); - public static readonly exclude = new Codicon('exclude', { fontCharacter: '\\eae5' }); - public static readonly extensions = new Codicon('extensions', { fontCharacter: '\\eae6' }); - public static readonly eyeClosed = new Codicon('eye-closed', { fontCharacter: '\\eae7' }); - public static readonly fileBinary = new Codicon('file-binary', { fontCharacter: '\\eae8' }); - public static readonly fileCode = new Codicon('file-code', { fontCharacter: '\\eae9' }); - public static readonly fileMedia = new Codicon('file-media', { fontCharacter: '\\eaea' }); - public static readonly filePdf = new Codicon('file-pdf', { fontCharacter: '\\eaeb' }); - public static readonly fileSubmodule = new Codicon('file-submodule', { fontCharacter: '\\eaec' }); - public static readonly fileSymlinkDirectory = new Codicon('file-symlink-directory', { fontCharacter: '\\eaed' }); - public static readonly fileSymlinkFile = new Codicon('file-symlink-file', { fontCharacter: '\\eaee' }); - public static readonly fileZip = new Codicon('file-zip', { fontCharacter: '\\eaef' }); - public static readonly files = new Codicon('files', { fontCharacter: '\\eaf0' }); - public static readonly filter = new Codicon('filter', { fontCharacter: '\\eaf1' }); - public static readonly flame = new Codicon('flame', { fontCharacter: '\\eaf2' }); - public static readonly foldDown = new Codicon('fold-down', { fontCharacter: '\\eaf3' }); - public static readonly foldUp = new Codicon('fold-up', { fontCharacter: '\\eaf4' }); - public static readonly fold = new Codicon('fold', { fontCharacter: '\\eaf5' }); - public static readonly folderActive = new Codicon('folder-active', { fontCharacter: '\\eaf6' }); - public static readonly folderOpened = new Codicon('folder-opened', { fontCharacter: '\\eaf7' }); - public static readonly gear = new Codicon('gear', { fontCharacter: '\\eaf8' }); - public static readonly gift = new Codicon('gift', { fontCharacter: '\\eaf9' }); - public static readonly gistSecret = new Codicon('gist-secret', { fontCharacter: '\\eafa' }); - public static readonly gist = new Codicon('gist', { fontCharacter: '\\eafb' }); - public static readonly gitCommit = new Codicon('git-commit', { fontCharacter: '\\eafc' }); - public static readonly gitCompare = new Codicon('git-compare', { fontCharacter: '\\eafd' }); - public static readonly gitMerge = new Codicon('git-merge', { fontCharacter: '\\eafe' }); - public static readonly githubAction = new Codicon('github-action', { fontCharacter: '\\eaff' }); - public static readonly githubAlt = new Codicon('github-alt', { fontCharacter: '\\eb00' }); - public static readonly globe = new Codicon('globe', { fontCharacter: '\\eb01' }); - public static readonly grabber = new Codicon('grabber', { fontCharacter: '\\eb02' }); - public static readonly graph = new Codicon('graph', { fontCharacter: '\\eb03' }); - public static readonly gripper = new Codicon('gripper', { fontCharacter: '\\eb04' }); - public static readonly heart = new Codicon('heart', { fontCharacter: '\\eb05' }); - public static readonly home = new Codicon('home', { fontCharacter: '\\eb06' }); - public static readonly horizontalRule = new Codicon('horizontal-rule', { fontCharacter: '\\eb07' }); - public static readonly hubot = new Codicon('hubot', { fontCharacter: '\\eb08' }); - public static readonly inbox = new Codicon('inbox', { fontCharacter: '\\eb09' }); - public static readonly issueClosed = new Codicon('issue-closed', { fontCharacter: '\\eba4' }); - public static readonly issueReopened = new Codicon('issue-reopened', { fontCharacter: '\\eb0b' }); - public static readonly issues = new Codicon('issues', { fontCharacter: '\\eb0c' }); - public static readonly italic = new Codicon('italic', { fontCharacter: '\\eb0d' }); - public static readonly jersey = new Codicon('jersey', { fontCharacter: '\\eb0e' }); - public static readonly json = new Codicon('json', { fontCharacter: '\\eb0f' }); - public static readonly kebabVertical = new Codicon('kebab-vertical', { fontCharacter: '\\eb10' }); - public static readonly key = new Codicon('key', { fontCharacter: '\\eb11' }); - public static readonly law = new Codicon('law', { fontCharacter: '\\eb12' }); - public static readonly lightbulbAutofix = new Codicon('lightbulb-autofix', { fontCharacter: '\\eb13' }); - public static readonly linkExternal = new Codicon('link-external', { fontCharacter: '\\eb14' }); - public static readonly link = new Codicon('link', { fontCharacter: '\\eb15' }); - public static readonly listOrdered = new Codicon('list-ordered', { fontCharacter: '\\eb16' }); - public static readonly listUnordered = new Codicon('list-unordered', { fontCharacter: '\\eb17' }); - public static readonly liveShare = new Codicon('live-share', { fontCharacter: '\\eb18' }); - public static readonly loading = new Codicon('loading', { fontCharacter: '\\eb19' }); - public static readonly location = new Codicon('location', { fontCharacter: '\\eb1a' }); - public static readonly mailRead = new Codicon('mail-read', { fontCharacter: '\\eb1b' }); - public static readonly mail = new Codicon('mail', { fontCharacter: '\\eb1c' }); - public static readonly markdown = new Codicon('markdown', { fontCharacter: '\\eb1d' }); - public static readonly megaphone = new Codicon('megaphone', { fontCharacter: '\\eb1e' }); - public static readonly mention = new Codicon('mention', { fontCharacter: '\\eb1f' }); - public static readonly milestone = new Codicon('milestone', { fontCharacter: '\\eb20' }); - public static readonly mortarBoard = new Codicon('mortar-board', { fontCharacter: '\\eb21' }); - public static readonly move = new Codicon('move', { fontCharacter: '\\eb22' }); - public static readonly multipleWindows = new Codicon('multiple-windows', { fontCharacter: '\\eb23' }); - public static readonly mute = new Codicon('mute', { fontCharacter: '\\eb24' }); - public static readonly noNewline = new Codicon('no-newline', { fontCharacter: '\\eb25' }); - public static readonly note = new Codicon('note', { fontCharacter: '\\eb26' }); - public static readonly octoface = new Codicon('octoface', { fontCharacter: '\\eb27' }); - public static readonly openPreview = new Codicon('open-preview', { fontCharacter: '\\eb28' }); - public static readonly package_ = new Codicon('package', { fontCharacter: '\\eb29' }); - public static readonly paintcan = new Codicon('paintcan', { fontCharacter: '\\eb2a' }); - public static readonly pin = new Codicon('pin', { fontCharacter: '\\eb2b' }); - public static readonly play = new Codicon('play', { fontCharacter: '\\eb2c' }); - public static readonly run = new Codicon('run', { fontCharacter: '\\eb2c' }); - public static readonly plug = new Codicon('plug', { fontCharacter: '\\eb2d' }); - public static readonly preserveCase = new Codicon('preserve-case', { fontCharacter: '\\eb2e' }); - public static readonly preview = new Codicon('preview', { fontCharacter: '\\eb2f' }); - public static readonly project = new Codicon('project', { fontCharacter: '\\eb30' }); - public static readonly pulse = new Codicon('pulse', { fontCharacter: '\\eb31' }); - public static readonly question = new Codicon('question', { fontCharacter: '\\eb32' }); - public static readonly quote = new Codicon('quote', { fontCharacter: '\\eb33' }); - public static readonly radioTower = new Codicon('radio-tower', { fontCharacter: '\\eb34' }); - public static readonly reactions = new Codicon('reactions', { fontCharacter: '\\eb35' }); - public static readonly references = new Codicon('references', { fontCharacter: '\\eb36' }); - public static readonly refresh = new Codicon('refresh', { fontCharacter: '\\eb37' }); - public static readonly regex = new Codicon('regex', { fontCharacter: '\\eb38' }); - public static readonly remoteExplorer = new Codicon('remote-explorer', { fontCharacter: '\\eb39' }); - public static readonly remote = new Codicon('remote', { fontCharacter: '\\eb3a' }); - public static readonly remove = new Codicon('remove', { fontCharacter: '\\eb3b' }); - public static readonly replaceAll = new Codicon('replace-all', { fontCharacter: '\\eb3c' }); - public static readonly replace = new Codicon('replace', { fontCharacter: '\\eb3d' }); - public static readonly repoClone = new Codicon('repo-clone', { fontCharacter: '\\eb3e' }); - public static readonly repoForcePush = new Codicon('repo-force-push', { fontCharacter: '\\eb3f' }); - public static readonly repoPull = new Codicon('repo-pull', { fontCharacter: '\\eb40' }); - public static readonly repoPush = new Codicon('repo-push', { fontCharacter: '\\eb41' }); - public static readonly report = new Codicon('report', { fontCharacter: '\\eb42' }); - public static readonly requestChanges = new Codicon('request-changes', { fontCharacter: '\\eb43' }); - public static readonly rocket = new Codicon('rocket', { fontCharacter: '\\eb44' }); - public static readonly rootFolderOpened = new Codicon('root-folder-opened', { fontCharacter: '\\eb45' }); - public static readonly rootFolder = new Codicon('root-folder', { fontCharacter: '\\eb46' }); - public static readonly rss = new Codicon('rss', { fontCharacter: '\\eb47' }); - public static readonly ruby = new Codicon('ruby', { fontCharacter: '\\eb48' }); - public static readonly saveAll = new Codicon('save-all', { fontCharacter: '\\eb49' }); - public static readonly saveAs = new Codicon('save-as', { fontCharacter: '\\eb4a' }); - public static readonly save = new Codicon('save', { fontCharacter: '\\eb4b' }); - public static readonly screenFull = new Codicon('screen-full', { fontCharacter: '\\eb4c' }); - public static readonly screenNormal = new Codicon('screen-normal', { fontCharacter: '\\eb4d' }); - public static readonly searchStop = new Codicon('search-stop', { fontCharacter: '\\eb4e' }); - public static readonly server = new Codicon('server', { fontCharacter: '\\eb50' }); - public static readonly settingsGear = new Codicon('settings-gear', { fontCharacter: '\\eb51' }); - public static readonly settings = new Codicon('settings', { fontCharacter: '\\eb52' }); - public static readonly shield = new Codicon('shield', { fontCharacter: '\\eb53' }); - public static readonly smiley = new Codicon('smiley', { fontCharacter: '\\eb54' }); - public static readonly sortPrecedence = new Codicon('sort-precedence', { fontCharacter: '\\eb55' }); - public static readonly splitHorizontal = new Codicon('split-horizontal', { fontCharacter: '\\eb56' }); - public static readonly splitVertical = new Codicon('split-vertical', { fontCharacter: '\\eb57' }); - public static readonly squirrel = new Codicon('squirrel', { fontCharacter: '\\eb58' }); - public static readonly starFull = new Codicon('star-full', { fontCharacter: '\\eb59' }); - public static readonly starHalf = new Codicon('star-half', { fontCharacter: '\\eb5a' }); - public static readonly symbolClass = new Codicon('symbol-class', { fontCharacter: '\\eb5b' }); - public static readonly symbolColor = new Codicon('symbol-color', { fontCharacter: '\\eb5c' }); - public static readonly symbolCustomColor = new Codicon('symbol-customcolor', { fontCharacter: '\\eb5c' }); - public static readonly symbolConstant = new Codicon('symbol-constant', { fontCharacter: '\\eb5d' }); - public static readonly symbolEnumMember = new Codicon('symbol-enum-member', { fontCharacter: '\\eb5e' }); - public static readonly symbolField = new Codicon('symbol-field', { fontCharacter: '\\eb5f' }); - public static readonly symbolFile = new Codicon('symbol-file', { fontCharacter: '\\eb60' }); - public static readonly symbolInterface = new Codicon('symbol-interface', { fontCharacter: '\\eb61' }); - public static readonly symbolKeyword = new Codicon('symbol-keyword', { fontCharacter: '\\eb62' }); - public static readonly symbolMisc = new Codicon('symbol-misc', { fontCharacter: '\\eb63' }); - public static readonly symbolOperator = new Codicon('symbol-operator', { fontCharacter: '\\eb64' }); - public static readonly symbolProperty = new Codicon('symbol-property', { fontCharacter: '\\eb65' }); - public static readonly wrench = new Codicon('wrench', { fontCharacter: '\\eb65' }); - public static readonly wrenchSubaction = new Codicon('wrench-subaction', { fontCharacter: '\\eb65' }); - public static readonly symbolSnippet = new Codicon('symbol-snippet', { fontCharacter: '\\eb66' }); - public static readonly tasklist = new Codicon('tasklist', { fontCharacter: '\\eb67' }); - public static readonly telescope = new Codicon('telescope', { fontCharacter: '\\eb68' }); - public static readonly textSize = new Codicon('text-size', { fontCharacter: '\\eb69' }); - public static readonly threeBars = new Codicon('three-bars', { fontCharacter: '\\eb6a' }); - public static readonly thumbsdown = new Codicon('thumbsdown', { fontCharacter: '\\eb6b' }); - public static readonly thumbsup = new Codicon('thumbsup', { fontCharacter: '\\eb6c' }); - public static readonly tools = new Codicon('tools', { fontCharacter: '\\eb6d' }); - public static readonly triangleDown = new Codicon('triangle-down', { fontCharacter: '\\eb6e' }); - public static readonly triangleLeft = new Codicon('triangle-left', { fontCharacter: '\\eb6f' }); - public static readonly triangleRight = new Codicon('triangle-right', { fontCharacter: '\\eb70' }); - public static readonly triangleUp = new Codicon('triangle-up', { fontCharacter: '\\eb71' }); - public static readonly twitter = new Codicon('twitter', { fontCharacter: '\\eb72' }); - public static readonly unfold = new Codicon('unfold', { fontCharacter: '\\eb73' }); - public static readonly unlock = new Codicon('unlock', { fontCharacter: '\\eb74' }); - public static readonly unmute = new Codicon('unmute', { fontCharacter: '\\eb75' }); - public static readonly unverified = new Codicon('unverified', { fontCharacter: '\\eb76' }); - public static readonly verified = new Codicon('verified', { fontCharacter: '\\eb77' }); - public static readonly versions = new Codicon('versions', { fontCharacter: '\\eb78' }); - public static readonly vmActive = new Codicon('vm-active', { fontCharacter: '\\eb79' }); - public static readonly vmOutline = new Codicon('vm-outline', { fontCharacter: '\\eb7a' }); - public static readonly vmRunning = new Codicon('vm-running', { fontCharacter: '\\eb7b' }); - public static readonly watch = new Codicon('watch', { fontCharacter: '\\eb7c' }); - public static readonly whitespace = new Codicon('whitespace', { fontCharacter: '\\eb7d' }); - public static readonly wholeWord = new Codicon('whole-word', { fontCharacter: '\\eb7e' }); - public static readonly window = new Codicon('window', { fontCharacter: '\\eb7f' }); - public static readonly wordWrap = new Codicon('word-wrap', { fontCharacter: '\\eb80' }); - public static readonly zoomIn = new Codicon('zoom-in', { fontCharacter: '\\eb81' }); - public static readonly zoomOut = new Codicon('zoom-out', { fontCharacter: '\\eb82' }); - public static readonly listFilter = new Codicon('list-filter', { fontCharacter: '\\eb83' }); - public static readonly listFlat = new Codicon('list-flat', { fontCharacter: '\\eb84' }); - public static readonly listSelection = new Codicon('list-selection', { fontCharacter: '\\eb85' }); - public static readonly selection = new Codicon('selection', { fontCharacter: '\\eb85' }); - public static readonly listTree = new Codicon('list-tree', { fontCharacter: '\\eb86' }); - public static readonly debugBreakpointFunctionUnverified = new Codicon('debug-breakpoint-function-unverified', { fontCharacter: '\\eb87' }); - public static readonly debugBreakpointFunction = new Codicon('debug-breakpoint-function', { fontCharacter: '\\eb88' }); - public static readonly debugBreakpointFunctionDisabled = new Codicon('debug-breakpoint-function-disabled', { fontCharacter: '\\eb88' }); - public static readonly debugStackframeActive = new Codicon('debug-stackframe-active', { fontCharacter: '\\eb89' }); - public static readonly circleSmallFilled = new Codicon('circle-small-filled', { fontCharacter: '\\eb8a' }); - public static readonly debugStackframeDot = new Codicon('debug-stackframe-dot', Codicon.circleSmallFilled.definition); - public static readonly debugStackframe = new Codicon('debug-stackframe', { fontCharacter: '\\eb8b' }); - public static readonly debugStackframeFocused = new Codicon('debug-stackframe-focused', { fontCharacter: '\\eb8b' }); - public static readonly debugBreakpointUnsupported = new Codicon('debug-breakpoint-unsupported', { fontCharacter: '\\eb8c' }); - public static readonly symbolString = new Codicon('symbol-string', { fontCharacter: '\\eb8d' }); - public static readonly debugReverseContinue = new Codicon('debug-reverse-continue', { fontCharacter: '\\eb8e' }); - public static readonly debugStepBack = new Codicon('debug-step-back', { fontCharacter: '\\eb8f' }); - public static readonly debugRestartFrame = new Codicon('debug-restart-frame', { fontCharacter: '\\eb90' }); - public static readonly callIncoming = new Codicon('call-incoming', { fontCharacter: '\\eb92' }); - public static readonly callOutgoing = new Codicon('call-outgoing', { fontCharacter: '\\eb93' }); - public static readonly menu = new Codicon('menu', { fontCharacter: '\\eb94' }); - public static readonly expandAll = new Codicon('expand-all', { fontCharacter: '\\eb95' }); - public static readonly feedback = new Codicon('feedback', { fontCharacter: '\\eb96' }); - public static readonly groupByRefType = new Codicon('group-by-ref-type', { fontCharacter: '\\eb97' }); - public static readonly ungroupByRefType = new Codicon('ungroup-by-ref-type', { fontCharacter: '\\eb98' }); - public static readonly account = new Codicon('account', { fontCharacter: '\\eb99' }); - public static readonly bellDot = new Codicon('bell-dot', { fontCharacter: '\\eb9a' }); - public static readonly debugConsole = new Codicon('debug-console', { fontCharacter: '\\eb9b' }); - public static readonly library = new Codicon('library', { fontCharacter: '\\eb9c' }); - public static readonly output = new Codicon('output', { fontCharacter: '\\eb9d' }); - public static readonly runAll = new Codicon('run-all', { fontCharacter: '\\eb9e' }); - public static readonly syncIgnored = new Codicon('sync-ignored', { fontCharacter: '\\eb9f' }); - public static readonly pinned = new Codicon('pinned', { fontCharacter: '\\eba0' }); - public static readonly githubInverted = new Codicon('github-inverted', { fontCharacter: '\\eba1' }); - public static readonly debugAlt = new Codicon('debug-alt', { fontCharacter: '\\eb91' }); - public static readonly serverProcess = new Codicon('server-process', { fontCharacter: '\\eba2' }); - public static readonly serverEnvironment = new Codicon('server-environment', { fontCharacter: '\\eba3' }); - public static readonly pass = new Codicon('pass', { fontCharacter: '\\eba4' }); - public static readonly stopCircle = new Codicon('stop-circle', { fontCharacter: '\\eba5' }); - public static readonly playCircle = new Codicon('play-circle', { fontCharacter: '\\eba6' }); - public static readonly record = new Codicon('record', { fontCharacter: '\\eba7' }); - public static readonly debugAltSmall = new Codicon('debug-alt-small', { fontCharacter: '\\eba8' }); - public static readonly vmConnect = new Codicon('vm-connect', { fontCharacter: '\\eba9' }); - public static readonly cloud = new Codicon('cloud', { fontCharacter: '\\ebaa' }); - public static readonly merge = new Codicon('merge', { fontCharacter: '\\ebab' }); - public static readonly exportIcon = new Codicon('export', { fontCharacter: '\\ebac' }); - public static readonly graphLeft = new Codicon('graph-left', { fontCharacter: '\\ebad' }); - public static readonly magnet = new Codicon('magnet', { fontCharacter: '\\ebae' }); - public static readonly notebook = new Codicon('notebook', { fontCharacter: '\\ebaf' }); - public static readonly redo = new Codicon('redo', { fontCharacter: '\\ebb0' }); - public static readonly checkAll = new Codicon('check-all', { fontCharacter: '\\ebb1' }); - public static readonly pinnedDirty = new Codicon('pinned-dirty', { fontCharacter: '\\ebb2' }); - public static readonly passFilled = new Codicon('pass-filled', { fontCharacter: '\\ebb3' }); - public static readonly circleLargeFilled = new Codicon('circle-large-filled', { fontCharacter: '\\ebb4' }); - public static readonly circleLargeOutline = new Codicon('circle-large-outline', { fontCharacter: '\\ebb5' }); - public static readonly combine = new Codicon('combine', { fontCharacter: '\\ebb6' }); - public static readonly gather = new Codicon('gather', { fontCharacter: '\\ebb6' }); - public static readonly table = new Codicon('table', { fontCharacter: '\\ebb7' }); - public static readonly variableGroup = new Codicon('variable-group', { fontCharacter: '\\ebb8' }); - public static readonly typeHierarchy = new Codicon('type-hierarchy', { fontCharacter: '\\ebb9' }); - public static readonly typeHierarchySub = new Codicon('type-hierarchy-sub', { fontCharacter: '\\ebba' }); - public static readonly typeHierarchySuper = new Codicon('type-hierarchy-super', { fontCharacter: '\\ebbb' }); - public static readonly gitPullRequestCreate = new Codicon('git-pull-request-create', { fontCharacter: '\\ebbc' }); - public static readonly runAbove = new Codicon('run-above', { fontCharacter: '\\ebbd' }); - public static readonly runBelow = new Codicon('run-below', { fontCharacter: '\\ebbe' }); - public static readonly notebookTemplate = new Codicon('notebook-template', { fontCharacter: '\\ebbf' }); - public static readonly debugRerun = new Codicon('debug-rerun', { fontCharacter: '\\ebc0' }); - public static readonly workspaceTrusted = new Codicon('workspace-trusted', { fontCharacter: '\\ebc1' }); - public static readonly workspaceUntrusted = new Codicon('workspace-untrusted', { fontCharacter: '\\ebc2' }); - public static readonly workspaceUnspecified = new Codicon('workspace-unspecified', { fontCharacter: '\\ebc3' }); - public static readonly terminalCmd = new Codicon('terminal-cmd', { fontCharacter: '\\ebc4' }); - public static readonly terminalDebian = new Codicon('terminal-debian', { fontCharacter: '\\ebc5' }); - public static readonly terminalLinux = new Codicon('terminal-linux', { fontCharacter: '\\ebc6' }); - public static readonly terminalPowershell = new Codicon('terminal-powershell', { fontCharacter: '\\ebc7' }); - public static readonly terminalTmux = new Codicon('terminal-tmux', { fontCharacter: '\\ebc8' }); - public static readonly terminalUbuntu = new Codicon('terminal-ubuntu', { fontCharacter: '\\ebc9' }); - public static readonly terminalBash = new Codicon('terminal-bash', { fontCharacter: '\\ebca' }); - public static readonly arrowSwap = new Codicon('arrow-swap', { fontCharacter: '\\ebcb' }); - public static readonly copy = new Codicon('copy', { fontCharacter: '\\ebcc' }); - public static readonly personAdd = new Codicon('person-add', { fontCharacter: '\\ebcd' }); - public static readonly filterFilled = new Codicon('filter-filled', { fontCharacter: '\\ebce' }); - public static readonly wand = new Codicon('wand', { fontCharacter: '\\ebcf' }); - public static readonly debugLineByLine = new Codicon('debug-line-by-line', { fontCharacter: '\\ebd0' }); - public static readonly inspect = new Codicon('inspect', { fontCharacter: '\\ebd1' }); - public static readonly layers = new Codicon('layers', { fontCharacter: '\\ebd2' }); - public static readonly layersDot = new Codicon('layers-dot', { fontCharacter: '\\ebd3' }); - public static readonly layersActive = new Codicon('layers-active', { fontCharacter: '\\ebd4' }); - public static readonly compass = new Codicon('compass', { fontCharacter: '\\ebd5' }); - public static readonly compassDot = new Codicon('compass-dot', { fontCharacter: '\\ebd6' }); - public static readonly compassActive = new Codicon('compass-active', { fontCharacter: '\\ebd7' }); - public static readonly azure = new Codicon('azure', { fontCharacter: '\\ebd8' }); - public static readonly issueDraft = new Codicon('issue-draft', { fontCharacter: '\\ebd9' }); - public static readonly gitPullRequestClosed = new Codicon('git-pull-request-closed', { fontCharacter: '\\ebda' }); - public static readonly gitPullRequestDraft = new Codicon('git-pull-request-draft', { fontCharacter: '\\ebdb' }); - public static readonly debugAll = new Codicon('debug-all', { fontCharacter: '\\ebdc' }); - public static readonly debugCoverage = new Codicon('debug-coverage', { fontCharacter: '\\ebdd' }); - public static readonly runErrors = new Codicon('run-errors', { fontCharacter: '\\ebde' }); - public static readonly folderLibrary = new Codicon('folder-library', { fontCharacter: '\\ebdf' }); - public static readonly debugContinueSmall = new Codicon('debug-continue-small', { fontCharacter: '\\ebe0' }); - public static readonly beakerStop = new Codicon('beaker-stop', { fontCharacter: '\\ebe1' }); - public static readonly graphLine = new Codicon('graph-line', { fontCharacter: '\\ebe2' }); - public static readonly graphScatter = new Codicon('graph-scatter', { fontCharacter: '\\ebe3' }); - public static readonly pieChart = new Codicon('pie-chart', { fontCharacter: '\\ebe4' }); - public static readonly bracket = new Codicon('bracket', Codicon.json.definition); - public static readonly bracketDot = new Codicon('bracket-dot', { fontCharacter: '\\ebe5' }); - public static readonly bracketError = new Codicon('bracket-error', { fontCharacter: '\\ebe6' }); - public static readonly lockSmall = new Codicon('lock-small', { fontCharacter: '\\ebe7' }); - public static readonly azureDevops = new Codicon('azure-devops', { fontCharacter: '\\ebe8' }); - public static readonly verifiedFilled = new Codicon('verified-filled', { fontCharacter: '\\ebe9' }); - public static readonly newLine = new Codicon('newline', { fontCharacter: '\\ebea' }); - public static readonly layout = new Codicon('layout', { fontCharacter: '\\ebeb' }); - public static readonly layoutActivitybarLeft = new Codicon('layout-activitybar-left', { fontCharacter: '\\ebec' }); - public static readonly layoutActivitybarRight = new Codicon('layout-activitybar-right', { fontCharacter: '\\ebed' }); - public static readonly layoutPanelLeft = new Codicon('layout-panel-left', { fontCharacter: '\\ebee' }); - public static readonly layoutPanelCenter = new Codicon('layout-panel-center', { fontCharacter: '\\ebef' }); - public static readonly layoutPanelJustify = new Codicon('layout-panel-justify', { fontCharacter: '\\ebf0' }); - public static readonly layoutPanelRight = new Codicon('layout-panel-right', { fontCharacter: '\\ebf1' }); - public static readonly layoutPanel = new Codicon('layout-panel', { fontCharacter: '\\ebf2' }); - public static readonly layoutSidebarLeft = new Codicon('layout-sidebar-left', { fontCharacter: '\\ebf3' }); - public static readonly layoutSidebarRight = new Codicon('layout-sidebar-right', { fontCharacter: '\\ebf4' }); - public static readonly layoutStatusbar = new Codicon('layout-statusbar', { fontCharacter: '\\ebf5' }); - public static readonly layoutMenubar = new Codicon('layout-menubar', { fontCharacter: '\\ebf6' }); - public static readonly layoutCentered = new Codicon('layout-centered', { fontCharacter: '\\ebf7' }); - public static readonly layoutSidebarRightOff = new Codicon('layout-sidebar-right-off', { fontCharacter: '\\ec00' }); - public static readonly layoutPanelOff = new Codicon('layout-panel-off', { fontCharacter: '\\ec01' }); - public static readonly layoutSidebarLeftOff = new Codicon('layout-sidebar-left-off', { fontCharacter: '\\ec02' }); - public static readonly target = new Codicon('target', { fontCharacter: '\\ebf8' }); - public static readonly indent = new Codicon('indent', { fontCharacter: '\\ebf9' }); - public static readonly recordSmall = new Codicon('record-small', { fontCharacter: '\\ebfa' }); - public static readonly errorSmall = new Codicon('error-small', { fontCharacter: '\\ebfb' }); - public static readonly arrowCircleDown = new Codicon('arrow-circle-down', { fontCharacter: '\\ebfc' }); - public static readonly arrowCircleLeft = new Codicon('arrow-circle-left', { fontCharacter: '\\ebfd' }); - public static readonly arrowCircleRight = new Codicon('arrow-circle-right', { fontCharacter: '\\ebfe' }); - public static readonly arrowCircleUp = new Codicon('arrow-circle-up', { fontCharacter: '\\ebff' }); - public static readonly heartFilled = new Codicon('heart-filled', { fontCharacter: '\\ec04' }); - public static readonly map = new Codicon('map', { fontCharacter: '\\ec05' }); - public static readonly mapFilled = new Codicon('map-filled', { fontCharacter: '\\ec06' }); - public static readonly circleSmall = new Codicon('circle-small', { fontCharacter: '\\ec07' }); - public static readonly bellSlash = new Codicon('bell-slash', { fontCharacter: '\\ec08' }); - public static readonly bellSlashDot = new Codicon('bell-slash-dot', { fontCharacter: '\\ec09' }); - public static readonly commentUnresolved = new Codicon('comment-unresolved', { fontCharacter: '\\ec0a' }); - public static readonly gitPullRequestGoToChanges = new Codicon('git-pull-request-go-to-changes', { fontCharacter: '\\ec0b' }); - public static readonly gitPullRequestNewChanges = new Codicon('git-pull-request-new-changes', { fontCharacter: '\\ec0c' }); + add: register('add', 0xea60), + plus: register('plus', 0xea60), + gistNew: register('gist-new', 0xea60), + repoCreate: register('repo-create', 0xea60), + lightbulb: register('lightbulb', 0xea61), + lightBulb: register('light-bulb', 0xea61), + repo: register('repo', 0xea62), + repoDelete: register('repo-delete', 0xea62), + gistFork: register('gist-fork', 0xea63), + repoForked: register('repo-forked', 0xea63), + gitPullRequest: register('git-pull-request', 0xea64), + gitPullRequestAbandoned: register('git-pull-request-abandoned', 0xea64), + recordKeys: register('record-keys', 0xea65), + keyboard: register('keyboard', 0xea65), + tag: register('tag', 0xea66), + tagAdd: register('tag-add', 0xea66), + tagRemove: register('tag-remove', 0xea66), + person: register('person', 0xea67), + personFollow: register('person-follow', 0xea67), + personOutline: register('person-outline', 0xea67), + personFilled: register('person-filled', 0xea67), + gitBranch: register('git-branch', 0xea68), + gitBranchCreate: register('git-branch-create', 0xea68), + gitBranchDelete: register('git-branch-delete', 0xea68), + sourceControl: register('source-control', 0xea68), + mirror: register('mirror', 0xea69), + mirrorPublic: register('mirror-public', 0xea69), + star: register('star', 0xea6a), + starAdd: register('star-add', 0xea6a), + starDelete: register('star-delete', 0xea6a), + starEmpty: register('star-empty', 0xea6a), + comment: register('comment', 0xea6b), + commentAdd: register('comment-add', 0xea6b), + alert: register('alert', 0xea6c), + warning: register('warning', 0xea6c), + search: register('search', 0xea6d), + searchSave: register('search-save', 0xea6d), + logOut: register('log-out', 0xea6e), + signOut: register('sign-out', 0xea6e), + logIn: register('log-in', 0xea6f), + signIn: register('sign-in', 0xea6f), + eye: register('eye', 0xea70), + eyeUnwatch: register('eye-unwatch', 0xea70), + eyeWatch: register('eye-watch', 0xea70), + circleFilled: register('circle-filled', 0xea71), + primitiveDot: register('primitive-dot', 0xea71), + closeDirty: register('close-dirty', 0xea71), + debugBreakpoint: register('debug-breakpoint', 0xea71), + debugBreakpointDisabled: register('debug-breakpoint-disabled', 0xea71), + debugHint: register('debug-hint', 0xea71), + primitiveSquare: register('primitive-square', 0xea72), + edit: register('edit', 0xea73), + pencil: register('pencil', 0xea73), + info: register('info', 0xea74), + issueOpened: register('issue-opened', 0xea74), + gistPrivate: register('gist-private', 0xea75), + gitForkPrivate: register('git-fork-private', 0xea75), + lock: register('lock', 0xea75), + mirrorPrivate: register('mirror-private', 0xea75), + close: register('close', 0xea76), + removeClose: register('remove-close', 0xea76), + x: register('x', 0xea76), + repoSync: register('repo-sync', 0xea77), + sync: register('sync', 0xea77), + clone: register('clone', 0xea78), + desktopDownload: register('desktop-download', 0xea78), + beaker: register('beaker', 0xea79), + microscope: register('microscope', 0xea79), + vm: register('vm', 0xea7a), + deviceDesktop: register('device-desktop', 0xea7a), + file: register('file', 0xea7b), + fileText: register('file-text', 0xea7b), + more: register('more', 0xea7c), + ellipsis: register('ellipsis', 0xea7c), + kebabHorizontal: register('kebab-horizontal', 0xea7c), + mailReply: register('mail-reply', 0xea7d), + reply: register('reply', 0xea7d), + organization: register('organization', 0xea7e), + organizationFilled: register('organization-filled', 0xea7e), + organizationOutline: register('organization-outline', 0xea7e), + newFile: register('new-file', 0xea7f), + fileAdd: register('file-add', 0xea7f), + newFolder: register('new-folder', 0xea80), + fileDirectoryCreate: register('file-directory-create', 0xea80), + trash: register('trash', 0xea81), + trashcan: register('trashcan', 0xea81), + history: register('history', 0xea82), + clock: register('clock', 0xea82), + folder: register('folder', 0xea83), + fileDirectory: register('file-directory', 0xea83), + symbolFolder: register('symbol-folder', 0xea83), + logoGithub: register('logo-github', 0xea84), + markGithub: register('mark-github', 0xea84), + github: register('github', 0xea84), + terminal: register('terminal', 0xea85), + console: register('console', 0xea85), + repl: register('repl', 0xea85), + zap: register('zap', 0xea86), + symbolEvent: register('symbol-event', 0xea86), + error: register('error', 0xea87), + stop: register('stop', 0xea87), + variable: register('variable', 0xea88), + symbolVariable: register('symbol-variable', 0xea88), + array: register('array', 0xea8a), + symbolArray: register('symbol-array', 0xea8a), + symbolModule: register('symbol-module', 0xea8b), + symbolPackage: register('symbol-package', 0xea8b), + symbolNamespace: register('symbol-namespace', 0xea8b), + symbolObject: register('symbol-object', 0xea8b), + symbolMethod: register('symbol-method', 0xea8c), + symbolFunction: register('symbol-function', 0xea8c), + symbolConstructor: register('symbol-constructor', 0xea8c), + symbolBoolean: register('symbol-boolean', 0xea8f), + symbolNull: register('symbol-null', 0xea8f), + symbolNumeric: register('symbol-numeric', 0xea90), + symbolNumber: register('symbol-number', 0xea90), + symbolStructure: register('symbol-structure', 0xea91), + symbolStruct: register('symbol-struct', 0xea91), + symbolParameter: register('symbol-parameter', 0xea92), + symbolTypeParameter: register('symbol-type-parameter', 0xea92), + symbolKey: register('symbol-key', 0xea93), + symbolText: register('symbol-text', 0xea93), + symbolReference: register('symbol-reference', 0xea94), + goToFile: register('go-to-file', 0xea94), + symbolEnum: register('symbol-enum', 0xea95), + symbolValue: register('symbol-value', 0xea95), + symbolRuler: register('symbol-ruler', 0xea96), + symbolUnit: register('symbol-unit', 0xea96), + activateBreakpoints: register('activate-breakpoints', 0xea97), + archive: register('archive', 0xea98), + arrowBoth: register('arrow-both', 0xea99), + arrowDown: register('arrow-down', 0xea9a), + arrowLeft: register('arrow-left', 0xea9b), + arrowRight: register('arrow-right', 0xea9c), + arrowSmallDown: register('arrow-small-down', 0xea9d), + arrowSmallLeft: register('arrow-small-left', 0xea9e), + arrowSmallRight: register('arrow-small-right', 0xea9f), + arrowSmallUp: register('arrow-small-up', 0xeaa0), + arrowUp: register('arrow-up', 0xeaa1), + bell: register('bell', 0xeaa2), + bold: register('bold', 0xeaa3), + book: register('book', 0xeaa4), + bookmark: register('bookmark', 0xeaa5), + debugBreakpointConditionalUnverified: register('debug-breakpoint-conditional-unverified', 0xeaa6), + debugBreakpointConditional: register('debug-breakpoint-conditional', 0xeaa7), + debugBreakpointConditionalDisabled: register('debug-breakpoint-conditional-disabled', 0xeaa7), + debugBreakpointDataUnverified: register('debug-breakpoint-data-unverified', 0xeaa8), + debugBreakpointData: register('debug-breakpoint-data', 0xeaa9), + debugBreakpointDataDisabled: register('debug-breakpoint-data-disabled', 0xeaa9), + debugBreakpointLogUnverified: register('debug-breakpoint-log-unverified', 0xeaaa), + debugBreakpointLog: register('debug-breakpoint-log', 0xeaab), + debugBreakpointLogDisabled: register('debug-breakpoint-log-disabled', 0xeaab), + briefcase: register('briefcase', 0xeaac), + broadcast: register('broadcast', 0xeaad), + browser: register('browser', 0xeaae), + bug: register('bug', 0xeaaf), + calendar: register('calendar', 0xeab0), + caseSensitive: register('case-sensitive', 0xeab1), + check: register('check', 0xeab2), + checklist: register('checklist', 0xeab3), + chevronDown: register('chevron-down', 0xeab4), + dropDownButton: register('drop-down-button', 0xeab4), + chevronLeft: register('chevron-left', 0xeab5), + chevronRight: register('chevron-right', 0xeab6), + chevronUp: register('chevron-up', 0xeab7), + chromeClose: register('chrome-close', 0xeab8), + chromeMaximize: register('chrome-maximize', 0xeab9), + chromeMinimize: register('chrome-minimize', 0xeaba), + chromeRestore: register('chrome-restore', 0xeabb), + circle: register('circle', 0xeabc), + circleOutline: register('circle-outline', 0xeabc), + debugBreakpointUnverified: register('debug-breakpoint-unverified', 0xeabc), + circleSlash: register('circle-slash', 0xeabd), + circuitBoard: register('circuit-board', 0xeabe), + clearAll: register('clear-all', 0xeabf), + clippy: register('clippy', 0xeac0), + closeAll: register('close-all', 0xeac1), + cloudDownload: register('cloud-download', 0xeac2), + cloudUpload: register('cloud-upload', 0xeac3), + code: register('code', 0xeac4), + collapseAll: register('collapse-all', 0xeac5), + colorMode: register('color-mode', 0xeac6), + commentDiscussion: register('comment-discussion', 0xeac7), + compareChanges: register('compare-changes', 0xeafd), + creditCard: register('credit-card', 0xeac9), + dash: register('dash', 0xeacc), + dashboard: register('dashboard', 0xeacd), + database: register('database', 0xeace), + debugContinue: register('debug-continue', 0xeacf), + debugDisconnect: register('debug-disconnect', 0xead0), + debugPause: register('debug-pause', 0xead1), + debugRestart: register('debug-restart', 0xead2), + debugStart: register('debug-start', 0xead3), + debugStepInto: register('debug-step-into', 0xead4), + debugStepOut: register('debug-step-out', 0xead5), + debugStepOver: register('debug-step-over', 0xead6), + debugStop: register('debug-stop', 0xead7), + debug: register('debug', 0xead8), + deviceCameraVideo: register('device-camera-video', 0xead9), + deviceCamera: register('device-camera', 0xeada), + deviceMobile: register('device-mobile', 0xeadb), + diffAdded: register('diff-added', 0xeadc), + diffIgnored: register('diff-ignored', 0xeadd), + diffModified: register('diff-modified', 0xeade), + diffRemoved: register('diff-removed', 0xeadf), + diffRenamed: register('diff-renamed', 0xeae0), + diff: register('diff', 0xeae1), + discard: register('discard', 0xeae2), + editorLayout: register('editor-layout', 0xeae3), + emptyWindow: register('empty-window', 0xeae4), + exclude: register('exclude', 0xeae5), + extensions: register('extensions', 0xeae6), + eyeClosed: register('eye-closed', 0xeae7), + fileBinary: register('file-binary', 0xeae8), + fileCode: register('file-code', 0xeae9), + fileMedia: register('file-media', 0xeaea), + filePdf: register('file-pdf', 0xeaeb), + fileSubmodule: register('file-submodule', 0xeaec), + fileSymlinkDirectory: register('file-symlink-directory', 0xeaed), + fileSymlinkFile: register('file-symlink-file', 0xeaee), + fileZip: register('file-zip', 0xeaef), + files: register('files', 0xeaf0), + filter: register('filter', 0xeaf1), + flame: register('flame', 0xeaf2), + foldDown: register('fold-down', 0xeaf3), + foldUp: register('fold-up', 0xeaf4), + fold: register('fold', 0xeaf5), + folderActive: register('folder-active', 0xeaf6), + folderOpened: register('folder-opened', 0xeaf7), + gear: register('gear', 0xeaf8), + gift: register('gift', 0xeaf9), + gistSecret: register('gist-secret', 0xeafa), + gist: register('gist', 0xeafb), + gitCommit: register('git-commit', 0xeafc), + gitCompare: register('git-compare', 0xeafd), + gitMerge: register('git-merge', 0xeafe), + githubAction: register('github-action', 0xeaff), + githubAlt: register('github-alt', 0xeb00), + globe: register('globe', 0xeb01), + grabber: register('grabber', 0xeb02), + graph: register('graph', 0xeb03), + gripper: register('gripper', 0xeb04), + heart: register('heart', 0xeb05), + home: register('home', 0xeb06), + horizontalRule: register('horizontal-rule', 0xeb07), + hubot: register('hubot', 0xeb08), + inbox: register('inbox', 0xeb09), + issueClosed: register('issue-closed', 0xeba4), + issueReopened: register('issue-reopened', 0xeb0b), + issues: register('issues', 0xeb0c), + italic: register('italic', 0xeb0d), + jersey: register('jersey', 0xeb0e), + json: register('json', 0xeb0f), + bracket: register('bracket', 0xeb0f), + kebabVertical: register('kebab-vertical', 0xeb10), + key: register('key', 0xeb11), + law: register('law', 0xeb12), + lightbulbAutofix: register('lightbulb-autofix', 0xeb13), + linkExternal: register('link-external', 0xeb14), + link: register('link', 0xeb15), + listOrdered: register('list-ordered', 0xeb16), + listUnordered: register('list-unordered', 0xeb17), + liveShare: register('live-share', 0xeb18), + loading: register('loading', 0xeb19), + location: register('location', 0xeb1a), + mailRead: register('mail-read', 0xeb1b), + mail: register('mail', 0xeb1c), + markdown: register('markdown', 0xeb1d), + megaphone: register('megaphone', 0xeb1e), + mention: register('mention', 0xeb1f), + milestone: register('milestone', 0xeb20), + mortarBoard: register('mortar-board', 0xeb21), + move: register('move', 0xeb22), + multipleWindows: register('multiple-windows', 0xeb23), + mute: register('mute', 0xeb24), + noNewline: register('no-newline', 0xeb25), + note: register('note', 0xeb26), + octoface: register('octoface', 0xeb27), + openPreview: register('open-preview', 0xeb28), + package_: register('package', 0xeb29), + paintcan: register('paintcan', 0xeb2a), + pin: register('pin', 0xeb2b), + play: register('play', 0xeb2c), + run: register('run', 0xeb2c), + plug: register('plug', 0xeb2d), + preserveCase: register('preserve-case', 0xeb2e), + preview: register('preview', 0xeb2f), + project: register('project', 0xeb30), + pulse: register('pulse', 0xeb31), + question: register('question', 0xeb32), + quote: register('quote', 0xeb33), + radioTower: register('radio-tower', 0xeb34), + reactions: register('reactions', 0xeb35), + references: register('references', 0xeb36), + refresh: register('refresh', 0xeb37), + regex: register('regex', 0xeb38), + remoteExplorer: register('remote-explorer', 0xeb39), + remote: register('remote', 0xeb3a), + remove: register('remove', 0xeb3b), + replaceAll: register('replace-all', 0xeb3c), + replace: register('replace', 0xeb3d), + repoClone: register('repo-clone', 0xeb3e), + repoForcePush: register('repo-force-push', 0xeb3f), + repoPull: register('repo-pull', 0xeb40), + repoPush: register('repo-push', 0xeb41), + report: register('report', 0xeb42), + requestChanges: register('request-changes', 0xeb43), + rocket: register('rocket', 0xeb44), + rootFolderOpened: register('root-folder-opened', 0xeb45), + rootFolder: register('root-folder', 0xeb46), + rss: register('rss', 0xeb47), + ruby: register('ruby', 0xeb48), + saveAll: register('save-all', 0xeb49), + saveAs: register('save-as', 0xeb4a), + save: register('save', 0xeb4b), + screenFull: register('screen-full', 0xeb4c), + screenNormal: register('screen-normal', 0xeb4d), + searchStop: register('search-stop', 0xeb4e), + server: register('server', 0xeb50), + settingsGear: register('settings-gear', 0xeb51), + settings: register('settings', 0xeb52), + shield: register('shield', 0xeb53), + smiley: register('smiley', 0xeb54), + sortPrecedence: register('sort-precedence', 0xeb55), + splitHorizontal: register('split-horizontal', 0xeb56), + splitVertical: register('split-vertical', 0xeb57), + squirrel: register('squirrel', 0xeb58), + starFull: register('star-full', 0xeb59), + starHalf: register('star-half', 0xeb5a), + symbolClass: register('symbol-class', 0xeb5b), + symbolColor: register('symbol-color', 0xeb5c), + symbolCustomColor: register('symbol-customcolor', 0xeb5c), + symbolConstant: register('symbol-constant', 0xeb5d), + symbolEnumMember: register('symbol-enum-member', 0xeb5e), + symbolField: register('symbol-field', 0xeb5f), + symbolFile: register('symbol-file', 0xeb60), + symbolInterface: register('symbol-interface', 0xeb61), + symbolKeyword: register('symbol-keyword', 0xeb62), + symbolMisc: register('symbol-misc', 0xeb63), + symbolOperator: register('symbol-operator', 0xeb64), + symbolProperty: register('symbol-property', 0xeb65), + wrench: register('wrench', 0xeb65), + wrenchSubaction: register('wrench-subaction', 0xeb65), + symbolSnippet: register('symbol-snippet', 0xeb66), + tasklist: register('tasklist', 0xeb67), + telescope: register('telescope', 0xeb68), + textSize: register('text-size', 0xeb69), + threeBars: register('three-bars', 0xeb6a), + thumbsdown: register('thumbsdown', 0xeb6b), + thumbsup: register('thumbsup', 0xeb6c), + tools: register('tools', 0xeb6d), + triangleDown: register('triangle-down', 0xeb6e), + triangleLeft: register('triangle-left', 0xeb6f), + triangleRight: register('triangle-right', 0xeb70), + triangleUp: register('triangle-up', 0xeb71), + twitter: register('twitter', 0xeb72), + unfold: register('unfold', 0xeb73), + unlock: register('unlock', 0xeb74), + unmute: register('unmute', 0xeb75), + unverified: register('unverified', 0xeb76), + verified: register('verified', 0xeb77), + versions: register('versions', 0xeb78), + vmActive: register('vm-active', 0xeb79), + vmOutline: register('vm-outline', 0xeb7a), + vmRunning: register('vm-running', 0xeb7b), + watch: register('watch', 0xeb7c), + whitespace: register('whitespace', 0xeb7d), + wholeWord: register('whole-word', 0xeb7e), + window: register('window', 0xeb7f), + wordWrap: register('word-wrap', 0xeb80), + zoomIn: register('zoom-in', 0xeb81), + zoomOut: register('zoom-out', 0xeb82), + listFilter: register('list-filter', 0xeb83), + listFlat: register('list-flat', 0xeb84), + listSelection: register('list-selection', 0xeb85), + selection: register('selection', 0xeb85), + listTree: register('list-tree', 0xeb86), + debugBreakpointFunctionUnverified: register('debug-breakpoint-function-unverified', 0xeb87), + debugBreakpointFunction: register('debug-breakpoint-function', 0xeb88), + debugBreakpointFunctionDisabled: register('debug-breakpoint-function-disabled', 0xeb88), + debugStackframeActive: register('debug-stackframe-active', 0xeb89), + circleSmallFilled: register('circle-small-filled', 0xeb8a), + debugStackframeDot: register('debug-stackframe-dot', 0xeb8a), + debugStackframe: register('debug-stackframe', 0xeb8b), + debugStackframeFocused: register('debug-stackframe-focused', 0xeb8b), + debugBreakpointUnsupported: register('debug-breakpoint-unsupported', 0xeb8c), + symbolString: register('symbol-string', 0xeb8d), + debugReverseContinue: register('debug-reverse-continue', 0xeb8e), + debugStepBack: register('debug-step-back', 0xeb8f), + debugRestartFrame: register('debug-restart-frame', 0xeb90), + callIncoming: register('call-incoming', 0xeb92), + callOutgoing: register('call-outgoing', 0xeb93), + menu: register('menu', 0xeb94), + expandAll: register('expand-all', 0xeb95), + feedback: register('feedback', 0xeb96), + groupByRefType: register('group-by-ref-type', 0xeb97), + ungroupByRefType: register('ungroup-by-ref-type', 0xeb98), + account: register('account', 0xeb99), + bellDot: register('bell-dot', 0xeb9a), + debugConsole: register('debug-console', 0xeb9b), + library: register('library', 0xeb9c), + output: register('output', 0xeb9d), + runAll: register('run-all', 0xeb9e), + syncIgnored: register('sync-ignored', 0xeb9f), + pinned: register('pinned', 0xeba0), + githubInverted: register('github-inverted', 0xeba1), + debugAlt: register('debug-alt', 0xeb91), + serverProcess: register('server-process', 0xeba2), + serverEnvironment: register('server-environment', 0xeba3), + pass: register('pass', 0xeba4), + stopCircle: register('stop-circle', 0xeba5), + playCircle: register('play-circle', 0xeba6), + record: register('record', 0xeba7), + debugAltSmall: register('debug-alt-small', 0xeba8), + vmConnect: register('vm-connect', 0xeba9), + cloud: register('cloud', 0xebaa), + merge: register('merge', 0xebab), + exportIcon: register('export', 0xebac), + graphLeft: register('graph-left', 0xebad), + magnet: register('magnet', 0xebae), + notebook: register('notebook', 0xebaf), + redo: register('redo', 0xebb0), + checkAll: register('check-all', 0xebb1), + pinnedDirty: register('pinned-dirty', 0xebb2), + passFilled: register('pass-filled', 0xebb3), + circleLargeFilled: register('circle-large-filled', 0xebb4), + circleLarge: register('circle-large', 0xebb5), + circleLargeOutline: register('circle-large-outline', 0xebb5), + combine: register('combine', 0xebb6), + gather: register('gather', 0xebb6), + table: register('table', 0xebb7), + variableGroup: register('variable-group', 0xebb8), + typeHierarchy: register('type-hierarchy', 0xebb9), + typeHierarchySub: register('type-hierarchy-sub', 0xebba), + typeHierarchySuper: register('type-hierarchy-super', 0xebbb), + gitPullRequestCreate: register('git-pull-request-create', 0xebbc), + runAbove: register('run-above', 0xebbd), + runBelow: register('run-below', 0xebbe), + notebookTemplate: register('notebook-template', 0xebbf), + debugRerun: register('debug-rerun', 0xebc0), + workspaceTrusted: register('workspace-trusted', 0xebc1), + workspaceUntrusted: register('workspace-untrusted', 0xebc2), + workspaceUnspecified: register('workspace-unspecified', 0xebc3), + terminalCmd: register('terminal-cmd', 0xebc4), + terminalDebian: register('terminal-debian', 0xebc5), + terminalLinux: register('terminal-linux', 0xebc6), + terminalPowershell: register('terminal-powershell', 0xebc7), + terminalTmux: register('terminal-tmux', 0xebc8), + terminalUbuntu: register('terminal-ubuntu', 0xebc9), + terminalBash: register('terminal-bash', 0xebca), + arrowSwap: register('arrow-swap', 0xebcb), + copy: register('copy', 0xebcc), + personAdd: register('person-add', 0xebcd), + filterFilled: register('filter-filled', 0xebce), + wand: register('wand', 0xebcf), + debugLineByLine: register('debug-line-by-line', 0xebd0), + inspect: register('inspect', 0xebd1), + layers: register('layers', 0xebd2), + layersDot: register('layers-dot', 0xebd3), + layersActive: register('layers-active', 0xebd4), + compass: register('compass', 0xebd5), + compassDot: register('compass-dot', 0xebd6), + compassActive: register('compass-active', 0xebd7), + azure: register('azure', 0xebd8), + issueDraft: register('issue-draft', 0xebd9), + gitPullRequestClosed: register('git-pull-request-closed', 0xebda), + gitPullRequestDraft: register('git-pull-request-draft', 0xebdb), + debugAll: register('debug-all', 0xebdc), + debugCoverage: register('debug-coverage', 0xebdd), + runErrors: register('run-errors', 0xebde), + folderLibrary: register('folder-library', 0xebdf), + debugContinueSmall: register('debug-continue-small', 0xebe0), + beakerStop: register('beaker-stop', 0xebe1), + graphLine: register('graph-line', 0xebe2), + graphScatter: register('graph-scatter', 0xebe3), + pieChart: register('pie-chart', 0xebe4), + bracketDot: register('bracket-dot', 0xebe5), + bracketError: register('bracket-error', 0xebe6), + lockSmall: register('lock-small', 0xebe7), + azureDevops: register('azure-devops', 0xebe8), + verifiedFilled: register('verified-filled', 0xebe9), + newLine: register('newline', 0xebea), + layout: register('layout', 0xebeb), + layoutActivitybarLeft: register('layout-activitybar-left', 0xebec), + layoutActivitybarRight: register('layout-activitybar-right', 0xebed), + layoutPanelLeft: register('layout-panel-left', 0xebee), + layoutPanelCenter: register('layout-panel-center', 0xebef), + layoutPanelJustify: register('layout-panel-justify', 0xebf0), + layoutPanelRight: register('layout-panel-right', 0xebf1), + layoutPanel: register('layout-panel', 0xebf2), + layoutSidebarLeft: register('layout-sidebar-left', 0xebf3), + layoutSidebarRight: register('layout-sidebar-right', 0xebf4), + layoutStatusbar: register('layout-statusbar', 0xebf5), + layoutMenubar: register('layout-menubar', 0xebf6), + layoutCentered: register('layout-centered', 0xebf7), + layoutSidebarRightOff: register('layout-sidebar-right-off', 0xec00), + layoutPanelOff: register('layout-panel-off', 0xec01), + layoutSidebarLeftOff: register('layout-sidebar-left-off', 0xec02), + target: register('target', 0xebf8), + indent: register('indent', 0xebf9), + recordSmall: register('record-small', 0xebfa), + errorSmall: register('error-small', 0xebfb), + arrowCircleDown: register('arrow-circle-down', 0xebfc), + arrowCircleLeft: register('arrow-circle-left', 0xebfd), + arrowCircleRight: register('arrow-circle-right', 0xebfe), + arrowCircleUp: register('arrow-circle-up', 0xebff), + heartFilled: register('heart-filled', 0xec04), + map: register('map', 0xec05), + mapFilled: register('map-filled', 0xec06), + circleSmall: register('circle-small', 0xec07), + bellSlash: register('bell-slash', 0xec08), + bellSlashDot: register('bell-slash-dot', 0xec09), + commentUnresolved: register('comment-unresolved', 0xec0a), + gitPullRequestGoToChanges: register('git-pull-request-go-to-changes', 0xec0b), + gitPullRequestNewChanges: register('git-pull-request-new-changes', 0xec0c), + searchFuzzy: register('search-fuzzy', 0xec0d), + commentDraft: register('comment-draft', 0xec0e), + send: register('send', 0xec0f), + sparkle: register('sparkle', 0xec10), + insert: register('insert', 0xec11), // derived icons, that could become separate icons - public static readonly dialogError = new Codicon('dialog-error', Codicon.error.definition); - public static readonly dialogWarning = new Codicon('dialog-warning', Codicon.warning.definition); - public static readonly dialogInfo = new Codicon('dialog-info', Codicon.info.definition); - public static readonly dialogClose = new Codicon('dialog-close', Codicon.close.definition); + dialogError: register('dialog-error', 'error'), + dialogWarning: register('dialog-warning', 'warning'), + dialogInfo: register('dialog-info', 'info'), + dialogClose: register('dialog-close', 'close'), - public static readonly treeItemExpanded = new Codicon('tree-item-expanded', Codicon.chevronDown.definition); // collapsed is done with rotation + treeItemExpanded: register('tree-item-expanded', 'chevron-down'), // collapsed is done with rotation - public static readonly treeFilterOnTypeOn = new Codicon('tree-filter-on-type-on', Codicon.listFilter.definition); - public static readonly treeFilterOnTypeOff = new Codicon('tree-filter-on-type-off', Codicon.listSelection.definition); - public static readonly treeFilterClear = new Codicon('tree-filter-clear', Codicon.close.definition); + treeFilterOnTypeOn: register('tree-filter-on-type-on', 'list-filter'), + treeFilterOnTypeOff: register('tree-filter-on-type-off', 'list-selection'), + treeFilterClear: register('tree-filter-clear', 'close'), - public static readonly treeItemLoading = new Codicon('tree-item-loading', Codicon.loading.definition); + treeItemLoading: register('tree-item-loading', 'loading'), - public static readonly menuSelection = new Codicon('menu-selection', Codicon.check.definition); - public static readonly menuSubmenu = new Codicon('menu-submenu', Codicon.chevronRight.definition); + menuSelection: register('menu-selection', 'check'), + menuSubmenu: register('menu-submenu', 'chevron-right'), - public static readonly menuBarMore = new Codicon('menubar-more', Codicon.more.definition); + menuBarMore: register('menubar-more', 'more'), - public static readonly scrollbarButtonLeft = new Codicon('scrollbar-button-left', Codicon.triangleLeft.definition); - public static readonly scrollbarButtonRight = new Codicon('scrollbar-button-right', Codicon.triangleRight.definition); + scrollbarButtonLeft: register('scrollbar-button-left', 'triangle-left'), + scrollbarButtonRight: register('scrollbar-button-right', 'triangle-right'), - public static readonly scrollbarButtonUp = new Codicon('scrollbar-button-up', Codicon.triangleUp.definition); - public static readonly scrollbarButtonDown = new Codicon('scrollbar-button-down', Codicon.triangleDown.definition); + scrollbarButtonUp: register('scrollbar-button-up', 'triangle-up'), + scrollbarButtonDown: register('scrollbar-button-down', 'triangle-down'), - public static readonly toolBarMore = new Codicon('toolbar-more', Codicon.more.definition); + toolBarMore: register('toolbar-more', 'more'), - public static readonly quickInputBack = new Codicon('quick-input-back', Codicon.arrowLeft.definition); -} + quickInputBack: register('quick-input-back', 'arrow-left') -export function getClassNamesArray(id: string, modifier?: string) { - const classNames = ['codicon', 'codicon-' + id]; - if (modifier) { - classNames.push('codicon-modifier-' + modifier); - } - return classNames; -} - -export interface CSSIcon { - readonly id: string; -} - - -export namespace CSSIcon { - export const iconNameSegment = '[A-Za-z0-9]+'; - export const iconNameExpression = '[A-Za-z0-9-]+'; - export const iconModifierExpression = '~[A-Za-z]+'; - export const iconNameCharacter = '[A-Za-z0-9~-]'; - - const cssIconIdRegex = new RegExp(`^(${iconNameExpression})(${iconModifierExpression})?$`); - - export function asClassNameArray(icon: CSSIcon): string[] { - if (icon instanceof Codicon) { - return ['codicon', 'codicon-' + icon.id]; - } - const match = cssIconIdRegex.exec(icon.id); - if (!match) { - return asClassNameArray(Codicon.error); - } - const [, id, modifier] = match; - - // {{SQL CARBON EDIT}} Modifying method to not add 'codicon' in front of sql carbon icons. - let sqlCarbonIcons: string[] = [SqlIconId.activeConnectionsAction, SqlIconId.addServerAction, SqlIconId.addServerGroupAction, SqlIconId.serverPage]; - if (sqlCarbonIcons.includes(id)) { - return ['codicon', id]; - // {{SQL CARBON EDIT}} End of edit - } else { - const classNames = ['codicon', 'codicon-' + id]; - if (modifier) { - classNames.push('codicon-modifier-' + modifier.substr(1)); - } - return classNames; - } - } - - export function asClassName(icon: CSSIcon): string { - return asClassNameArray(icon).join(' '); - } - - export function asCSSSelector(icon: CSSIcon): string { - return '.' + asClassNameArray(icon).join('.'); - } -} - - -interface IconDefinition { - fontCharacter: string; -} +} as const; diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts index d26593ab96..076c720e7a 100644 --- a/src/vs/base/common/color.ts +++ b/src/vs/base/common/color.ts @@ -259,6 +259,16 @@ export class Color { return Color.Format.CSS.parseHex(hex) || Color.red; } + static equals(a: Color | null, b: Color | null): boolean { + if (!a && !b) { + return true; + } + if (!a || !b) { + return false; + } + return a.equals(b); + } + readonly rgba: RGBA; private _hsla?: HSLA; get hsla(): HSLA { diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index 8df41f1917..82eaa9782c 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IdleValue } from 'vs/base/common/async'; +import { Lazy } from 'vs/base/common/lazy'; import { sep } from 'vs/base/common/path'; // When comparing large numbers of strings it's better for performance to create an @@ -11,27 +11,27 @@ import { sep } from 'vs/base/common/path'; // than it is to use String.prototype.localeCompare() // A collator with numeric sorting enabled, and no sensitivity to case, accents or diacritics. -const intlFileNameCollatorBaseNumeric: IdleValue<{ collator: Intl.Collator; collatorIsNumeric: boolean }> = new IdleValue(() => { +const intlFileNameCollatorBaseNumeric: Lazy<{ collator: Intl.Collator; collatorIsNumeric: boolean }> = new Lazy(() => { const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); return { - collator: collator, + collator, collatorIsNumeric: collator.resolvedOptions().numeric }; }); // A collator with numeric sorting enabled. -const intlFileNameCollatorNumeric: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => { +const intlFileNameCollatorNumeric: Lazy<{ collator: Intl.Collator }> = new Lazy(() => { const collator = new Intl.Collator(undefined, { numeric: true }); return { - collator: collator + collator }; }); // A collator with numeric sorting enabled, and sensitivity to accents and diacritics but not case. -const intlFileNameCollatorNumericCaseInsensitive: IdleValue<{ collator: Intl.Collator }> = new IdleValue(() => { +const intlFileNameCollatorNumericCaseInsensitive: Lazy<{ collator: Intl.Collator }> = new Lazy(() => { const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'accent' }); return { - collator: collator + collator }; }); @@ -88,26 +88,6 @@ export function compareFileNamesUnicode(one: string | null, other: string | null return one < other ? -1 : 1; } -export function noIntlCompareFileNames(one: string | null, other: string | null, caseSensitive = false): number { - if (!caseSensitive) { - one = one && one.toLowerCase(); - other = other && other.toLowerCase(); - } - - const [oneName, oneExtension] = extractNameAndExtension(one); - const [otherName, otherExtension] = extractNameAndExtension(other); - - if (oneName !== otherName) { - return oneName < otherName ? -1 : 1; - } - - if (oneExtension === otherExtension) { - return 0; - } - - return oneExtension < otherExtension ? -1 : 1; -} - /** Compares filenames by extension, then by name. Disambiguates by unicode comparison. */ export function compareFileExtensions(one: string | null, other: string | null): number { const [oneName, oneExtension] = extractNameAndExtension(one); diff --git a/src/vs/base/common/dataTransfer.ts b/src/vs/base/common/dataTransfer.ts index 02dbe6cdb7..9a4c452303 100644 --- a/src/vs/base/common/dataTransfer.ts +++ b/src/vs/base/common/dataTransfer.ts @@ -3,9 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { distinct } from 'vs/base/common/arrays'; +import { Iterable } from 'vs/base/common/iterator'; import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; export interface IDataTransferFile { + readonly id: string; readonly name: string; readonly uri?: URI; data(): Promise<Uint8Array>; @@ -26,29 +30,77 @@ export function createStringDataTransferItem(stringOrPromise: string | Promise<s } export function createFileDataTransferItem(fileName: string, uri: URI | undefined, data: () => Promise<Uint8Array>): IDataTransferItem { + const file = { id: generateUuid(), name: fileName, uri, data }; return { asString: async () => '', - asFile: () => ({ name: fileName, uri, data }), + asFile: () => file, value: undefined, }; } -export class VSDataTransfer { +export interface IReadonlyVSDataTransfer extends Iterable<readonly [string, IDataTransferItem]> { + /** + * Get the total number of entries in this data transfer. + */ + get size(): number; + + /** + * Check if this data transfer contains data for `mimeType`. + * + * This uses exact matching and does not support wildcards. + */ + has(mimeType: string): boolean; + /** + * Check if this data transfer contains data matching `pattern`. + * + * This allows matching for wildcards, such as `image/*`. + * + * Use the special `files` mime type to match any file in the data transfer. + */ + matches(pattern: string): boolean; + + /** + * Retrieve the first entry for `mimeType`. + * + * Note that if you want to find all entries for a given mime type, use {@link IReadonlyVSDataTransfer.entries} instead. + */ + get(mimeType: string): IDataTransferItem | undefined; +} + +export class VSDataTransfer implements IReadonlyVSDataTransfer { private readonly _entries = new Map<string, IDataTransferItem[]>(); public get size(): number { - return this._entries.size; + let size = 0; + for (const _ of this._entries) { + size++; + } + return size; } public has(mimeType: string): boolean { return this._entries.has(this.toKey(mimeType)); } + public matches(pattern: string): boolean { + const mimes = [...this._entries.keys()]; + if (Iterable.some(this, ([_, item]) => item.asFile())) { + mimes.push('files'); + } + + return matchesMimeType_normalized(normalizeMimeType(pattern), mimes); + } + public get(mimeType: string): IDataTransferItem | undefined { return this._entries.get(this.toKey(mimeType))?.[0]; } + /** + * Add a new entry to this data transfer. + * + * This does not replace existing entries for `mimeType`. + */ public append(mimeType: string, value: IDataTransferItem): void { const existing = this._entries.get(mimeType); if (existing) { @@ -58,33 +110,85 @@ export class VSDataTransfer { } } + /** + * Set the entry for a given mime type. + * + * This replaces all existing entries for `mimeType`. + */ public replace(mimeType: string, value: IDataTransferItem): void { this._entries.set(this.toKey(mimeType), [value]); } + /** + * Remove all entries for `mimeType`. + */ public delete(mimeType: string) { this._entries.delete(this.toKey(mimeType)); } - public *entries(): Iterable<[string, IDataTransferItem]> { - for (const [mine, items] of this._entries.entries()) { + /** + * Iterate over all `[mime, item]` pairs in this data transfer. + * + * There may be multiple entries for each mime type. + */ + public *[Symbol.iterator](): IterableIterator<readonly [string, IDataTransferItem]> { + for (const [mine, items] of this._entries) { for (const item of items) { yield [mine, item]; } } } - public values(): Iterable<IDataTransferItem> { - return Array.from(this._entries.values()).flat(); - } - - public forEach(f: (value: IDataTransferItem, key: string) => void) { - for (const [mime, item] of this.entries()) { - f(item, mime); - } - } - private toKey(mimeType: string): string { - return mimeType.toLowerCase(); + return normalizeMimeType(mimeType); } } + +function normalizeMimeType(mimeType: string): string { + return mimeType.toLowerCase(); +} + +export function matchesMimeType(pattern: string, mimeTypes: readonly string[]): boolean { + return matchesMimeType_normalized( + normalizeMimeType(pattern), + mimeTypes.map(normalizeMimeType)); +} + +function matchesMimeType_normalized(normalizedPattern: string, normalizedMimeTypes: readonly string[]): boolean { + // Anything wildcard + if (normalizedPattern === '*/*') { + return normalizedMimeTypes.length > 0; + } + + // Exact match + if (normalizedMimeTypes.includes(normalizedPattern)) { + return true; + } + + // Wildcard, such as `image/*` + const wildcard = normalizedPattern.match(/^([a-z]+)\/([a-z]+|\*)$/i); + if (!wildcard) { + return false; + } + + const [_, type, subtype] = wildcard; + if (subtype === '*') { + return normalizedMimeTypes.some(mime => mime.startsWith(type + '/')); + } + + return false; +} + + +export const UriList = Object.freeze({ + // http://amundsen.com/hypermedia/urilist/ + create: (entries: ReadonlyArray<string | URI>): string => { + return distinct(entries.map(x => x.toString())).join('\r\n'); + }, + split: (str: string): string[] => { + return str.split('\r\n'); + }, + parse: (str: string): string[] => { + return UriList.split(str).filter(value => !value.startsWith('#')); + } +}); diff --git a/src/vs/base/common/date.ts b/src/vs/base/common/date.ts index 13be4438e0..4217365522 100644 --- a/src/vs/base/common/date.ts +++ b/src/vs/base/common/date.ts @@ -12,7 +12,16 @@ const week = day * 7; const month = day * 30; const year = day * 365; -export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTimeWords?: boolean): string { +/** + * Create a localized difference of the time between now and the specified date. + * @param date The date to generate the difference from. + * @param appendAgoLabel Whether to append the " ago" to the end. + * @param useFullTimeWords Whether to use full words (eg. seconds) instead of + * shortened (eg. secs). + * @param disallowNow Whether to disallow the string "now" when the difference + * is less than 30 seconds. + */ +export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTimeWords?: boolean, disallowNow?: boolean): string { if (typeof date !== 'number') { date = date.getTime(); } @@ -22,7 +31,7 @@ export function fromNow(date: number | Date, appendAgoLabel?: boolean, useFullTi return localize('date.fromNow.in', 'in {0}', fromNow(new Date().getTime() + seconds * 1000, false)); } - if (seconds < 30) { + if (!disallowNow && seconds < 30) { return localize('date.fromNow.now', 'now'); } diff --git a/src/vs/base/common/decorators.ts b/src/vs/base/common/decorators.ts index af629c0b28..107b5566c4 100644 --- a/src/vs/base/common/decorators.ts +++ b/src/vs/base/common/decorators.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export function createDecorator(mapFn: (fn: Function, key: string) => Function): Function { +function createDecorator(mapFn: (fn: Function, key: string) => Function): Function { return (target: any, key: string, descriptor: any) => { let fnKey: string | null = null; let fn: Function | null = null; diff --git a/src/vs/base/common/diff/diff.ts b/src/vs/base/common/diff/diff.ts index 382b7d6b19..961aedcc27 100644 --- a/src/vs/base/common/diff/diff.ts +++ b/src/vs/base/common/diff/diff.ts @@ -69,7 +69,7 @@ export interface IDiffResult { // The code below has been ported from a C# implementation in VS // -export class Debug { +class Debug { public static Assert(condition: boolean, message: string): void { if (!condition) { @@ -78,7 +78,7 @@ export class Debug { } } -export class MyArray { +class MyArray { /** * Copies a range of elements from an Array starting at the specified source index and pastes * them to another Array starting at the specified destination index. The length and the indexes diff --git a/src/vs/base/common/errorMessage.ts b/src/vs/base/common/errorMessage.ts index 9a976db9ae..3c41eb88ad 100644 --- a/src/vs/base/common/errorMessage.ts +++ b/src/vs/base/common/errorMessage.ts @@ -26,6 +26,11 @@ function stackToString(stack: string[] | string | undefined): string | undefined function detectSystemErrorMessage(exception: any): string { + // Custom node.js error from us + if (exception.code === 'ERR_UNC_HOST_NOT_ALLOWED') { + return `${exception.message}. Please update the 'security.allowedUNCHosts' setting if you want to allow this host.`; + } + // See https://nodejs.org/api/errors.html#errors_class_system_error if (typeof exception.code === 'string' && typeof exception.errno === 'number' && typeof exception.syscall === 'string') { return nls.localize('nodeExceptionMessage', "A system error occurred ({0})", exception.message); diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index d00f276490..2a07cbef12 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -78,6 +78,21 @@ export function setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => errorHandler.setUnexpectedErrorHandler(newUnexpectedErrorHandler); } +/** + * Returns if the error is a SIGPIPE error. SIGPIPE errors should generally be + * logged at most once, to avoid a loop. + * + * @see https://github.com/microsoft/vscode-remote-release/issues/6481 + */ +export function isSigPipeError(e: unknown): e is Error { + if (!e || typeof e !== 'object') { + return false; + } + + const cast = e as Record<string, string | undefined>; + return cast.code === 'EPIPE' && cast.syscall?.toUpperCase() === 'WRITE'; +} + export function onUnexpectedError(e: any): undefined { // ignore errors from cancelled promises if (!isCancellationError(e)) { @@ -125,15 +140,15 @@ export function transformErrorForSerialization(error: any): any { // see https://github.com/v8/v8/wiki/Stack%20Trace%20API#basic-stack-traces export interface V8CallSite { - getThis(): any; - getTypeName(): string; - getFunction(): string; - getFunctionName(): string; - getMethodName(): string; - getFileName(): string; - getLineNumber(): number; - getColumnNumber(): number; - getEvalOrigin(): string; + getThis(): unknown; + getTypeName(): string | null; + getFunction(): Function | undefined; + getFunctionName(): string | null; + getMethodName(): string | null; + getFileName(): string | null; + getLineNumber(): number | null; + getColumnNumber(): number | null; + getEvalOrigin(): string | undefined; isToplevel(): boolean; isEval(): boolean; isNative(): boolean; @@ -245,7 +260,7 @@ export class ErrorNoTelemetry extends Error { constructor(msg?: string) { super(msg); - this.name = 'ErrorNoTelemetry'; + this.name = 'CodeExpectedError'; } public static fromError(err: Error): ErrorNoTelemetry { @@ -260,7 +275,7 @@ export class ErrorNoTelemetry extends Error { } public static isErrorNoTelemetry(err: Error): err is ErrorNoTelemetry { - return err.name === 'ErrorNoTelemetry'; + return err.name === 'CodeExpectedError'; } } @@ -277,6 +292,6 @@ export class BugIndicatingError extends Error { // Because we know for sure only buggy code throws this, // we definitely want to break here and fix the bug. // eslint-disable-next-line no-debugger - debugger; + // debugger; } } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 91e039f410..92dc7c4e50 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -10,6 +10,7 @@ import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDispo import { LinkedList } from 'vs/base/common/linkedList'; import { IObservable, IObserver } from 'vs/base/common/observable'; import { StopWatch } from 'vs/base/common/stopwatch'; +import { MicrotaskDelay } from 'vs/base/common/symbols'; // ----------------------------------------------------------------------------------------------------------------------- @@ -27,8 +28,7 @@ const _enableSnapshotPotentialLeakWarning = false; // _enableSnapshotPotentialLeakWarning = Boolean("TRUE"); // causes a linter warning so that it cannot be pushed /** - * To an event a function with one or zero parameters - * can be subscribed. The event is the subscriber function itself. + * An event with zero or one parameters that can be subscribed to. The event is a function itself. */ export interface Event<T> { (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable; @@ -37,13 +37,12 @@ export interface Event<T> { export namespace Event { export const None: Event<any> = () => Disposable.None; - function _addLeakageTraceLogic(options: EmitterOptions) { if (_enableSnapshotPotentialLeakWarning) { - const { onListenerDidAdd: origListenerDidAdd } = options; + const { onDidAddListener: origListenerDidAdd } = options; const stack = Stacktrace.create(); let count = 0; - options.onListenerDidAdd = () => { + options.onDidAddListener = () => { if (++count === 2) { console.warn('snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here'); stack.print(); @@ -53,9 +52,30 @@ export namespace Event { } } + /** + * Given an event, returns another event which debounces calls and defers the listeners to a later task via a shared + * `setTimeout`. The event is converted into a signal (`Event<void>`) to avoid additional object creation as a + * result of merging events and to try prevent race conditions that could arise when using related deferred and + * non-deferred events. + * + * This is useful for deferring non-critical work (eg. general UI updates) to ensure it does not block critical work + * (eg. latency of keypress to text rendered). + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param disposable A disposable store to add the new EventEmitter to. + */ + export function defer(event: Event<unknown>, disposable?: DisposableStore): Event<void> { + return debounce<unknown, void>(event, () => void 0, 0, undefined, true, undefined, disposable); + } /** * Given an event, returns another event which only fires once. + * + * @param event The event source for the new event. */ export function once<T>(event: Event<T>): Event<T> { return (listener, thisArgs = null, disposables?) => { @@ -83,27 +103,47 @@ export namespace Event { } /** + * Maps an event of one type into an event of another type using a mapping function, similar to how + * `Array.prototype.map` works. + * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param map The mapping function. + * @param disposable A disposable store to add the new EventEmitter to. */ export function map<I, O>(event: Event<I>, map: (i: I) => O, disposable?: DisposableStore): Event<O> { return snapshot((listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables), disposable); } /** + * Wraps an event in another event that performs some function on the event object before firing. + * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param each The function to perform on the event object. + * @param disposable A disposable store to add the new EventEmitter to. */ export function forEach<I>(event: Event<I>, each: (i: I) => void, disposable?: DisposableStore): Event<I> { return snapshot((listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables), disposable); } /** + * Wraps an event in another event that fires only when some condition is met. + * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param filter The filter function that defines the condition. The event will fire for the object if this function + * returns true. + * @param disposable A disposable store to add the new EventEmitter to. */ export function filter<T, U>(event: Event<T | U>, filter: (e: T | U) => e is T, disposable?: DisposableStore): Event<T>; export function filter<T>(event: Event<T>, filter: (e: T) => boolean, disposable?: DisposableStore): Event<T>; @@ -120,8 +160,7 @@ export namespace Event { } /** - * Given a collection of events, returns a single event which emits - * whenever any of the provided events emit. + * Given a collection of events, returns a single event which emits whenever any of the provided events emit. */ export function any<T>(...events: Event<T>[]): Event<T>; export function any(...events: Event<any>[]): Event<void>; @@ -147,10 +186,10 @@ export namespace Event { let listener: IDisposable | undefined; const options: EmitterOptions | undefined = { - onFirstListenerAdd() { + onWillAddFirstListener() { listener = event(emitter.fire, emitter); }, - onLastListenerRemove() { + onDidRemoveLastListener() { listener?.dispose(); } }; @@ -167,28 +206,35 @@ export namespace Event { } /** + * Given an event, creates a new emitter that event that will debounce events based on {@link delay} and give an + * array event object of all events that fired. + * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. + * + * @param event The original event to debounce. + * @param merge A function that reduces all events into a single event. + * @param delay The number of milliseconds to debounce. + * @param leading Whether to fire a leading event without debouncing. + * @param flushOnListenerRemove Whether to fire all debounced events when a listener is removed. If this is not + * specified, some events could go missing. Use this if it's important that all events are processed, even if the + * listener gets disposed before the debounced event fires. + * @param leakWarningThreshold See {@link EmitterOptions.leakWarningThreshold}. + * @param disposable A disposable store to register the debounce emitter to. */ - export function debounce<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<T>; - /** - * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned - * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the - * returned event causes this utility to leak a listener on the original event. - */ - export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O>; - - export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O> { - + export function debounce<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<T>; + export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number | typeof MicrotaskDelay, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O>; + export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number | typeof MicrotaskDelay = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O> { let subscription: IDisposable; let output: O | undefined = undefined; let handle: any = undefined; let numDebouncedCalls = 0; + let doFire: (() => void) | undefined; const options: EmitterOptions | undefined = { leakWarningThreshold, - onFirstListenerAdd() { + onWillAddFirstListener() { subscription = event(cur => { numDebouncedCalls++; output = merge(output, cur); @@ -198,20 +244,34 @@ export namespace Event { output = undefined; } - clearTimeout(handle); - handle = setTimeout(() => { + doFire = () => { const _output = output; output = undefined; handle = undefined; if (!leading || numDebouncedCalls > 1) { emitter.fire(_output!); } - numDebouncedCalls = 0; - }, delay); + }; + + if (typeof delay === 'number') { + clearTimeout(handle); + handle = setTimeout(doFire, delay); + } else { + if (handle === undefined) { + handle = 0; + queueMicrotask(doFire); + } + } }); }, - onLastListenerRemove() { + onWillRemoveListener() { + if (flushOnListenerRemove && numDebouncedCalls > 0) { + doFire?.(); + } + }, + onDidRemoveLastListener() { + doFire = undefined; subscription.dispose(); } }; @@ -228,10 +288,40 @@ export namespace Event { } /** + * Debounces an event, firing after some delay (default=0) with an array of all event original objects. + * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. */ + export function accumulate<T>(event: Event<T>, delay: number = 0, disposable?: DisposableStore): Event<T[]> { + return Event.debounce<T, T[]>(event, (last, e) => { + if (!last) { + return [e]; + } + last.push(e); + return last; + }, delay, undefined, true, undefined, disposable); + } + + /** + * Filters an event such that some condition is _not_ met more than once in a row, effectively ensuring duplicate + * event objects from different sources do not fire the same event object. + * + * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned + * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the + * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param equals The equality condition. + * @param disposable A disposable store to add the new EventEmitter to. + * + * @example + * ``` + * // Fire only one time when a single window is opened or focused + * Event.latch(Event.any(onDidOpenWindow, onDidFocusWindow)) + * ``` + */ export function latch<T>(event: Event<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b, disposable?: DisposableStore): Event<T> { let firstCall = true; let cache: T; @@ -245,9 +335,21 @@ export namespace Event { } /** + * Splits an event whose parameter is a union type into 2 separate events for each type in the union. + * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. + * + * @example + * ``` + * const event = new EventEmitter<number | undefined>().event; + * const [numberEvent, undefinedEvent] = Event.split(event, isUndefined); + * ``` + * + * @param event The event source for the new event. + * @param isT A function that determines what event is of the first type. + * @param disposable A disposable store to add the new EventEmitter to. */ export function split<T, U>(event: Event<T | U>, isT: (e: T | U) => e is T, disposable?: DisposableStore): [Event<T>, Event<U>] { return [ @@ -257,9 +359,24 @@ export namespace Event { } /** + * Buffers an event until it has a listener attached. + * * *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned * event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the * returned event causes this utility to leak a listener on the original event. + * + * @param event The event source for the new event. + * @param flushAfterTimeout Determines whether to flush the buffer after a timeout immediately or after a + * `setTimeout` when the first event listener is added. + * @param _buffer Internal: A source event array used for tests. + * + * @example + * ``` + * // Start accumulating events, when the first listener is attached, flush + * // the event after a timeout such that multiple listeners attached before + * // the timeout would receive the event + * this.onInstallExtension = Event.buffer(service.onInstallExtension, true); + * ``` */ export function buffer<T>(event: Event<T>, flushAfterTimeout = false, _buffer: T[] = []): Event<T> { let buffer: T[] | null = _buffer.slice(); @@ -278,13 +395,13 @@ export namespace Event { }; const emitter = new Emitter<T>({ - onFirstListenerAdd() { + onWillAddFirstListener() { if (!listener) { listener = event(e => emitter.fire(e)); } }, - onFirstListenerDidAdd() { + onDidAddFirstListener() { if (buffer) { if (flushAfterTimeout) { setTimeout(flush); @@ -294,7 +411,7 @@ export namespace Event { } }, - onLastListenerRemove() { + onDidRemoveLastListener() { if (listener) { listener.dispose(); } @@ -314,8 +431,8 @@ export namespace Event { filter<R>(fn: (e: T | R) => e is R): IChainableEvent<R>; reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R>; latch(): IChainableEvent<T>; - debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<T>; - debounce<R>(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<R>; + debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number): IChainableEvent<T>; + debounce<R>(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number): IChainableEvent<R>; on(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore): IDisposable; once(listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[]): IDisposable; } @@ -326,38 +443,48 @@ export namespace Event { constructor(readonly event: Event<T>) { } + /** @see {@link Event.map} */ map<O>(fn: (i: T) => O): IChainableEvent<O> { return new ChainableEvent(map(this.event, fn, this.disposables)); } + /** @see {@link Event.forEach} */ forEach(fn: (i: T) => void): IChainableEvent<T> { return new ChainableEvent(forEach(this.event, fn, this.disposables)); } + /** @see {@link Event.filter} */ filter(fn: (e: T) => boolean): IChainableEvent<T>; filter<R>(fn: (e: T | R) => e is R): IChainableEvent<R>; filter(fn: (e: T) => boolean): IChainableEvent<T> { return new ChainableEvent(filter(this.event, fn, this.disposables)); } + /** @see {@link Event.reduce} */ reduce<R>(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent<R> { return new ChainableEvent(reduce(this.event, merge, initial, this.disposables)); } + /** @see {@link Event.reduce} */ latch(): IChainableEvent<T> { return new ChainableEvent(latch(this.event, undefined, this.disposables)); } - debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<T>; - debounce<R>(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, leakWarningThreshold?: number): IChainableEvent<R>; - debounce<R>(merge: (last: R | undefined, event: T) => R, delay: number = 100, leading = false, leakWarningThreshold?: number): IChainableEvent<R> { - return new ChainableEvent(debounce(this.event, merge, delay, leading, leakWarningThreshold, this.disposables)); + /** @see {@link Event.debounce} */ + debounce(merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number): IChainableEvent<T>; + debounce<R>(merge: (last: R | undefined, event: T) => R, delay?: number, leading?: boolean, flushOnListenerRemove?: boolean, leakWarningThreshold?: number): IChainableEvent<R>; + debounce<R>(merge: (last: R | undefined, event: T) => R, delay: number = 100, leading = false, flushOnListenerRemove = false, leakWarningThreshold?: number): IChainableEvent<R> { + return new ChainableEvent(debounce(this.event, merge, delay, leading, flushOnListenerRemove, leakWarningThreshold, this.disposables)); } + /** + * Attach a listener to the event. + */ on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[] | DisposableStore) { return this.event(listener, thisArgs, disposables); } + /** @see {@link Event.once} */ once(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) { return once(this.event)(listener, thisArgs, disposables); } @@ -367,6 +494,24 @@ export namespace Event { } } + /** + * Wraps the event in an {@link IChainableEvent}, allowing a more functional programming style. + * + * @example + * ``` + * // Normal + * const onEnterPressNormal = Event.filter( + * Event.map(onKeyPress.event, e => new StandardKeyboardEvent(e)), + * e.keyCode === KeyCode.Enter + * ).event; + * + * // Using chain + * const onEnterPressChain = Event.chain(onKeyPress.event) + * .map(e => new StandardKeyboardEvent(e)) + * .filter(e => e.keyCode === KeyCode.Enter) + * .event; + * ``` + */ export function chain<T>(event: Event<T>): IChainableEvent<T> { return new ChainableEvent(event); } @@ -376,11 +521,14 @@ export namespace Event { removeListener(event: string | symbol, listener: Function): unknown; } + /** + * Creates an {@link Event} from a node event emitter. + */ export function fromNodeEventEmitter<T>(emitter: NodeEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> { const fn = (...args: any[]) => result.fire(map(...args)); const onFirstListenerAdd = () => emitter.on(eventName, fn); const onLastListenerRemove = () => emitter.removeListener(eventName, fn); - const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove }); + const result = new Emitter<T>({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove }); return result.event; } @@ -390,24 +538,43 @@ export namespace Event { removeEventListener(event: string | symbol, listener: Function): void; } + /** + * Creates an {@link Event} from a DOM event emitter. + */ export function fromDOMEventEmitter<T>(emitter: DOMEventEmitter, eventName: string, map: (...args: any[]) => T = id => id): Event<T> { const fn = (...args: any[]) => result.fire(map(...args)); const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn); const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn); - const result = new Emitter<T>({ onFirstListenerAdd, onLastListenerRemove }); + const result = new Emitter<T>({ onWillAddFirstListener: onFirstListenerAdd, onDidRemoveLastListener: onLastListenerRemove }); return result.event; } + /** + * Creates a promise out of an event, using the {@link Event.once} helper. + */ export function toPromise<T>(event: Event<T>): Promise<T> { return new Promise(resolve => once(event)(resolve)); } + /** + * Adds a listener to an event and calls the listener immediately with undefined as the event object. + * + * @example + * ``` + * // Initialize the UI and update it when dataChangeEvent fires + * runAndSubscribe(dataChangeEvent, () => this._updateUI()); + * ``` + */ export function runAndSubscribe<T>(event: Event<T>, handler: (e: T | undefined) => any): IDisposable { handler(undefined); return event(e => handler(e)); } + /** + * Adds a listener to an event and calls the listener immediately with undefined as the event object. A new + * {@link DisposableStore} is passed to the listener which is disposed when the returned disposable is disposed. + */ export function runAndSubscribeWithStore<T>(event: Event<T>, handler: (e: T | undefined, disposableStore: DisposableStore) => any): IDisposable { let store: DisposableStore | null = null; @@ -432,13 +599,13 @@ export namespace Event { private _counter = 0; private _hasChanged = false; - constructor(readonly obs: IObservable<T, any>, store: DisposableStore | undefined) { - const options = { - onFirstListenerAdd: () => { - obs.addObserver(this); + constructor(readonly _observable: IObservable<T, any>, store: DisposableStore | undefined) { + const options: EmitterOptions = { + onWillAddFirstListener: () => { + _observable.addObserver(this); }, - onLastListenerRemove: () => { - obs.removeObserver(this); + onDidRemoveLastListener: () => { + _observable.removeObserver(this); } }; if (!store) { @@ -451,37 +618,112 @@ export namespace Event { } beginUpdate<T>(_observable: IObservable<T, void>): void { - // console.assert(_observable === this.obs); + // assert(_observable === this.obs); this._counter++; } + handlePossibleChange<T>(_observable: IObservable<T, unknown>): void { + // assert(_observable === this.obs); + } + handleChange<T, TChange>(_observable: IObservable<T, TChange>, _change: TChange): void { + // assert(_observable === this.obs); this._hasChanged = true; } endUpdate<T>(_observable: IObservable<T, void>): void { - if (--this._counter === 0) { + // assert(_observable === this.obs); + this._counter--; + if (this._counter === 0) { + this._observable.reportChanges(); if (this._hasChanged) { this._hasChanged = false; - this.emitter.fire(this.obs.get()); + this.emitter.fire(this._observable.get()); } } } } + /** + * Creates an event emitter that is fired when the observable changes. + * Each listeners subscribes to the emitter. + */ export function fromObservable<T>(obs: IObservable<T, any>, store?: DisposableStore): Event<T> { const observer = new EmitterObserver(obs, store); return observer.emitter.event; } + + /** + * Each listener is attached to the observable directly. + */ + export function fromObservableLight(observable: IObservable<any>): Event<void> { + return (listener) => { + let count = 0; + let didChange = false; + const observer: IObserver = { + beginUpdate() { + count++; + }, + endUpdate() { + count--; + if (count === 0) { + observable.reportChanges(); + if (didChange) { + didChange = false; + listener(); + } + } + }, + handlePossibleChange() { + // noop + }, + handleChange() { + didChange = true; + } + }; + observable.addObserver(observer); + return { + dispose() { + observable.removeObserver(observer); + } + }; + }; + } } export interface EmitterOptions { - onFirstListenerAdd?: Function; - onFirstListenerDidAdd?: Function; - onListenerDidAdd?: Function; - onLastListenerRemove?: Function; + /** + * Optional function that's called *before* the very first listener is added + */ + onWillAddFirstListener?: Function; + /** + * Optional function that's called *after* the very first listener is added + */ + onDidAddFirstListener?: Function; + /** + * Optional function that's called after a listener is added + */ + onDidAddListener?: Function; + /** + * Optional function that's called *after* remove the very last listener + */ + onDidRemoveLastListener?: Function; + /** + * Optional function that's called *before* a listener is removed + */ + onWillRemoveListener?: Function; + /** + * Optional function that's called when a listener throws an error. Defaults to + * {@link onUnexpectedError} + */ + onListenerError?: (e: any) => void; + /** + * Number of listeners that are allowed before assuming a leak. Default to + * a globally configured value + * + * @see setGlobalLeakWarningThreshold + */ leakWarningThreshold?: number; - /** * Pass in a delivery queue, which is useful for ensuring * in order event delivery across multiple emitters. @@ -493,32 +735,36 @@ export interface EmitterOptions { } -class EventProfiling { +export class EventProfiling { + + static readonly all = new Set<EventProfiling>(); private static _idPool = 0; - private _name: string; + readonly name: string; + public listenerCount: number = 0; + public invocationCount = 0; + public elapsedOverall = 0; + public durations: number[] = []; + private _stopWatch?: StopWatch; - private _listenerCount: number = 0; - private _invocationCount = 0; - private _elapsedOverall = 0; constructor(name: string) { - this._name = `${name}_${EventProfiling._idPool++}`; + this.name = `${name}_${EventProfiling._idPool++}`; + EventProfiling.all.add(this); } start(listenerCount: number): void { this._stopWatch = new StopWatch(true); - this._listenerCount = listenerCount; + this.listenerCount = listenerCount; } stop(): void { if (this._stopWatch) { const elapsed = this._stopWatch.elapsed(); - this._elapsedOverall += elapsed; - this._invocationCount += 1; - - console.info(`did FIRE ${this._name}: elapsed_ms: ${elapsed.toFixed(5)}, listener: ${this._listenerCount} (elapsed_overall: ${this._elapsedOverall.toFixed(2)}, invocations: ${this._invocationCount})`); + this.durations.push(elapsed); + this.elapsedOverall += elapsed; + this.invocationCount += 1; this._stopWatch = undefined; } } @@ -541,23 +787,17 @@ class LeakageMonitor { private _warnCountdown: number = 0; constructor( - readonly customThreshold?: number, + readonly threshold: number, readonly name: string = Math.random().toString(18).slice(2, 5), ) { } dispose(): void { - if (this._stacks) { - this._stacks.clear(); - } + this._stacks?.clear(); } check(stack: Stacktrace, listenerCount: number): undefined | (() => void) { - let threshold = _globalLeakWarningThreshold; - if (typeof this.customThreshold === 'number') { - threshold = this.customThreshold; - } - + const threshold = this.threshold; if (threshold <= 0 || listenerCount < threshold) { return undefined; } @@ -656,7 +896,7 @@ export class Emitter<T> { constructor(options?: EmitterOptions) { this._options = options; - this._leakageMon = _globalLeakWarningThreshold > 0 ? new LeakageMonitor(this._options && this._options.leakWarningThreshold) : undefined; + this._leakageMon = _globalLeakWarningThreshold > 0 || this._options?.leakWarningThreshold ? new LeakageMonitor(this._options?.leakWarningThreshold ?? _globalLeakWarningThreshold) : undefined; this._perfMon = this._options?._profName ? new EventProfiling(this._options._profName) : undefined; this._deliveryQueue = this._options?.deliveryQueue; } @@ -691,7 +931,7 @@ export class Emitter<T> { this._listeners.clear(); } this._deliveryQueue?.clear(this); - this._options?.onLastListenerRemove?.(); + this._options?.onDidRemoveLastListener?.(); this._leakageMon?.dispose(); } } @@ -707,15 +947,20 @@ export class Emitter<T> { this._listeners = new LinkedList(); } + if (this._leakageMon && this._listeners.size > this._leakageMon.threshold * 3) { + console.warn(`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far`); + return Disposable.None; + } + const firstListener = this._listeners.isEmpty(); - if (firstListener && this._options?.onFirstListenerAdd) { - this._options.onFirstListenerAdd(this); + if (firstListener && this._options?.onWillAddFirstListener) { + this._options.onWillAddFirstListener(this); } let removeMonitor: Function | undefined; let stack: Stacktrace | undefined; - if (this._leakageMon && this._listeners.size >= 30) { + if (this._leakageMon && this._listeners.size >= Math.ceil(this._leakageMon.threshold * 0.2)) { // check and record this emitter for potential leakage stack = Stacktrace.create(); removeMonitor = this._leakageMon.check(stack, this._listeners.size + 1); @@ -728,22 +973,23 @@ export class Emitter<T> { const listener = new Listener(callback, thisArgs, stack); const removeListener = this._listeners.push(listener); - if (firstListener && this._options?.onFirstListenerDidAdd) { - this._options.onFirstListenerDidAdd(this); + if (firstListener && this._options?.onDidAddFirstListener) { + this._options.onDidAddFirstListener(this); } - if (this._options?.onListenerDidAdd) { - this._options.onListenerDidAdd(this, callback, thisArgs); + if (this._options?.onDidAddListener) { + this._options.onDidAddListener(this, callback, thisArgs); } const result = listener.subscription.set(() => { removeMonitor?.(); if (!this._disposed) { + this._options?.onWillRemoveListener?.(this); removeListener(); - if (this._options && this._options.onLastListenerRemove) { + if (this._options && this._options.onDidRemoveLastListener) { const hasListeners = (this._listeners && !this._listeners.isEmpty()); if (!hasListeners) { - this._options.onLastListenerRemove(this); + this._options.onDidRemoveLastListener(this); } } } @@ -772,7 +1018,7 @@ export class Emitter<T> { // the driver of this if (!this._deliveryQueue) { - this._deliveryQueue = new PrivateEventDeliveryQueue(); + this._deliveryQueue = new PrivateEventDeliveryQueue(this._options?.onListenerError); } for (const listener of this._listeners) { @@ -792,13 +1038,18 @@ export class Emitter<T> { if (!this._listeners) { return false; } - return (!this._listeners.isEmpty()); + return !this._listeners.isEmpty(); } } export class EventDeliveryQueue { + protected _queue = new LinkedList<EventDeliveryQueueElement>(); + constructor( + private readonly _onListenerError: (e: any) => void = onUnexpectedError + ) { } + get size(): number { return this._queue.size; } @@ -823,7 +1074,7 @@ export class EventDeliveryQueue { try { element.listener.invoke(element.event); } catch (e) { - onUnexpectedError(e); + this._onListenerError(e); } } } @@ -920,6 +1171,10 @@ export class PauseableEmitter<T> extends Emitter<T> { protected _eventQueue = new LinkedList<T>(); private _mergeFn?: (input: T[]) => T; + public get isPaused(): boolean { + return this._isPaused !== 0; + } + constructor(options?: EmitterOptions & { merge?: (input: T[]) => T }) { super(options); this._mergeFn = options?.merge; @@ -934,9 +1189,11 @@ export class PauseableEmitter<T> extends Emitter<T> { if (this._mergeFn) { // use the merge function to create a single composite // event. make a copy in case firing pauses this emitter - const events = Array.from(this._eventQueue); - this._eventQueue.clear(); - super.fire(this._mergeFn(events)); + if (this._eventQueue.size > 0) { + const events = Array.from(this._eventQueue); + this._eventQueue.clear(); + super.fire(this._mergeFn(events)); + } } else { // no merging, fire each event individually and test @@ -994,6 +1251,11 @@ export class MicrotaskEmitter<T> extends Emitter<T> { this._mergeFn = options?.merge; } override fire(event: T): void { + + if (!this.hasListeners()) { + return; + } + this._queuedEvents.push(event); if (this._queuedEvents.length === 1) { queueMicrotask(() => { @@ -1016,8 +1278,8 @@ export class EventMultiplexer<T> implements IDisposable { constructor() { this.emitter = new Emitter<T>({ - onFirstListenerAdd: () => this.onFirstListenerAdd(), - onLastListenerRemove: () => this.onLastListenerRemove() + onWillAddFirstListener: () => this.onFirstListenerAdd(), + onDidRemoveLastListener: () => this.onLastListenerRemove() }); } @@ -1132,11 +1394,11 @@ export class Relay<T> implements IDisposable { private inputEventListener: IDisposable = Disposable.None; private readonly emitter = new Emitter<T>({ - onFirstListenerDidAdd: () => { + onDidAddFirstListener: () => { this.listening = true; this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter); }, - onLastListenerRemove: () => { + onDidRemoveLastListener: () => { this.listening = false; this.inputEventListener.dispose(); } diff --git a/src/vs/base/common/extpath.ts b/src/vs/base/common/extpath.ts index 94f22815ad..35e3ab2291 100644 --- a/src/vs/base/common/extpath.ts +++ b/src/vs/base/common/extpath.ts @@ -325,8 +325,8 @@ export function hasDriveLetter(path: string, isWindowsOS: boolean = isWindows): return false; } -export function getDriveLetter(path: string): string | undefined { - return hasDriveLetter(path) ? path[0] : undefined; +export function getDriveLetter(path: string, isWindowsOS: boolean = isWindows): string | undefined { + return hasDriveLetter(path, isWindowsOS) ? path[0] : undefined; } export function indexOfPath(path: string, candidate: string, ignoreCase?: boolean): number { @@ -382,11 +382,26 @@ export function parseLineAndColumnAware(rawPath: string): IPathWithLineAndColumn } const pathChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; +const windowsSafePathFirstChars = 'BDEFGHIJKMOQRSTUVWXYZbdefghijkmoqrstuvwxyz0123456789'; export function randomPath(parent?: string, prefix?: string, randomLength = 8): string { let suffix = ''; for (let i = 0; i < randomLength; i++) { - suffix += pathChars.charAt(Math.floor(Math.random() * pathChars.length)); + let pathCharsTouse: string; + if (i === 0 && isWindows && !prefix && (randomLength === 3 || randomLength === 4)) { + + // Windows has certain reserved file names that cannot be used, such + // as AUX, CON, PRN, etc. We want to avoid generating a random name + // that matches that pattern, so we use a different set of characters + // for the first character of the name that does not include any of + // the reserved names first characters. + + pathCharsTouse = windowsSafePathFirstChars; + } else { + pathCharsTouse = pathChars; + } + + suffix += pathCharsTouse.charAt(Math.floor(Math.random() * pathCharsTouse.length)); } let randomFileName: string; diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 48ce27176c..72df2512fa 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -316,7 +316,18 @@ function _matchesWords(word: string, target: string, i: number, j: number, conti nextWordIndex++; } } - return result === null ? null : join({ start: j, end: j + 1 }, result); + + if (!result) { + return null; + } + + // If the characters don't exactly match, then they must be word separators (see charactersMatch(...)). + // We don't want to include this in the matches but we don't want to throw the target out all together so we return `result`. + if (word.charCodeAt(i) !== target.charCodeAt(j)) { + return result; + } + + return join({ start: j, end: j + 1 }, result); } } @@ -370,7 +381,7 @@ export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null { export function anyScore(pattern: string, lowPattern: string, patternPos: number, word: string, lowWord: string, wordPos: number): FuzzyScore { const max = Math.min(13, pattern.length); for (; patternPos < max; patternPos++) { - const result = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, { firstMatchCanBeWeak: false, boostFullMatch: true }); + const result = fuzzyScore(pattern, lowPattern, patternPos, word, lowWord, wordPos, { firstMatchCanBeWeak: true, boostFullMatch: true }); if (result) { return result; } diff --git a/src/vs/base/common/fuzzyScorer.ts b/src/vs/base/common/fuzzyScorer.ts index 8bf09d8522..212f0f0b03 100644 --- a/src/vs/base/common/fuzzyScorer.ts +++ b/src/vs/base/common/fuzzyScorer.ts @@ -19,7 +19,7 @@ export type FuzzyScorerCache = { [key: string]: IItemScore }; const NO_MATCH = 0; const NO_SCORE: FuzzyScore = [NO_MATCH, []]; -// const DEBUG = false; +// const DEBUG = true; // const DEBUG_MATRIX = false; export function scoreFuzzy(target: string, query: string, queryLower: string, allowNonContiguousMatches: boolean): FuzzyScore { @@ -149,7 +149,7 @@ function doScoreFuzzy(query: string, queryLower: string, queryLength: number, ta // Print matrix // if (DEBUG_MATRIX) { - // printMatrix(query, target, matches, scores); + // printMatrix(query, target, matches, scores); // } return [scores[queryLength * targetLength - 1], positions.reverse()]; @@ -162,11 +162,15 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin return score; // no match of characters } + // if (DEBUG) { + // console.groupCollapsed(`%cFound a match of char: ${queryLowerCharAtIndex} at index ${targetIndex}`, 'font-weight: normal'); + // } + // Character match bonus score += 1; // if (DEBUG) { - // console.groupCollapsed(`%cCharacter match bonus: +1 (char: ${queryLowerCharAtIndex} at index ${targetIndex}, total score: ${score})`, 'font-weight: normal'); + // console.log(`%cCharacter match bonus: +1`, 'font-weight: normal'); // } // Consecutive match bonus @@ -174,7 +178,7 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin score += (matchesSequenceLength * 5); // if (DEBUG) { - // console.log(`Consecutive match bonus: +${matchesSequenceLength * 5}`); + // console.log(`Consecutive match bonus: +${matchesSequenceLength * 5}`); // } } @@ -204,7 +208,7 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin score += separatorBonus; // if (DEBUG) { - // console.log(`After separator bonus: +${separatorBonus}`); + // console.log(`After separator bonus: +${separatorBonus}`); // } } @@ -222,6 +226,7 @@ function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: strin } // if (DEBUG) { + // console.log(`Total score: ${score}`); // console.groupEnd(); // } diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 4e2def02ae..0103313c8c 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -10,7 +10,7 @@ import { isEqualOrParent } from 'vs/base/common/extpath'; import { LRUCache } from 'vs/base/common/map'; import { basename, extname, posix, sep } from 'vs/base/common/path'; import { isLinux } from 'vs/base/common/platform'; -import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { escapeRegExpCharacters, ltrim } from 'vs/base/common/strings'; export interface IRelativePattern { @@ -367,7 +367,12 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | // Given we have checked `base` being a parent of `path`, // we can now remove the `base` portion of the `path` // and only match on the remaining path components - return parsedPattern(path.substr(arg2.base.length + 1), basename); + // For that we try to extract the portion of the `path` + // that comes after the `base` portion. We have to account + // for the fact that `base` might end in a path separator + // (https://github.com/microsoft/vscode/issues/162498) + + return parsedPattern(ltrim(path.substr(arg2.base.length), sep), basename); }; // Make sure to preserve associated metadata @@ -724,7 +729,7 @@ function parseExpressionPattern(pattern: string, value: boolean | SiblingClause, return null; } - const clausePattern = when.replace('$(basename)', name!); + const clausePattern = when.replace('$(basename)', () => name!); const matched = hasSibling(clausePattern); return isThenable(matched) ? matched.then(match => match ? pattern : null) : diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 4c1bb1e7b0..1bfd4e736e 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -28,10 +28,8 @@ export class HistoryNavigator<T> implements INavigator<T> { } public next(): T | null { - if (this._currentPosition() !== this._elements.length - 1) { - return this._navigator.next(); - } - return null; + // This will navigate past the end of the last element, and in that case the input should be cleared + return this._navigator.next(); } public previous(): T | null { @@ -53,6 +51,18 @@ export class HistoryNavigator<T> implements INavigator<T> { return this._navigator.last(); } + public isFirst(): boolean { + return this._currentPosition() === 0; + } + + public isLast(): boolean { + return this._currentPosition() >= this._elements.length - 1; + } + + public isNowhere(): boolean { + return this._navigator.current() === null; + } + public has(t: T): boolean { return this._history.has(t); } @@ -106,6 +116,7 @@ interface HistoryNode<T> { export class HistoryNavigator2<T> { + private valueSet: Set<T>; private head: HistoryNode<T>; private tail: HistoryNode<T>; private cursor: HistoryNode<T>; @@ -123,6 +134,7 @@ export class HistoryNavigator2<T> { next: undefined }; + this.valueSet = new Set<T>([history[0]]); for (let i = 1; i < history.length; i++) { this.add(history[i]); } @@ -140,7 +152,15 @@ export class HistoryNavigator2<T> { this.cursor = this.tail; this.size++; + if (this.valueSet.has(value)) { + this._deleteFromList(value); + } else { + this.valueSet.add(value); + } + while (this.size > this.capacity) { + this.valueSet.delete(this.head.value); + this.head = this.head.next!; this.head.previous = undefined; this.size--; @@ -151,8 +171,20 @@ export class HistoryNavigator2<T> { * @returns old last value */ replaceLast(value: T): T { + if (this.tail.value === value) { + return value; + } + const oldValue = this.tail.value; + this.valueSet.delete(oldValue); this.tail.value = value; + + if (this.valueSet.has(value)) { + this._deleteFromList(value); + } else { + this.valueSet.add(value); + } + return oldValue; } @@ -181,14 +213,7 @@ export class HistoryNavigator2<T> { } has(t: T): boolean { - let temp: HistoryNode<T> | undefined = this.head; - while (temp) { - if (temp.value === t) { - return true; - } - temp = temp.next; - } - return false; + return this.valueSet.has(t); } resetCursor(): T { @@ -204,4 +229,24 @@ export class HistoryNavigator2<T> { node = node.next; } } + + private _deleteFromList(value: T): void { + let temp = this.head; + + while (temp !== this.tail) { + if (temp.value === value) { + if (temp === this.head) { + this.head = this.head.next!; + this.head.previous = undefined; + } else { + temp.previous!.next = temp.next; + temp.next!.previous = temp.previous; + } + + this.size--; + } + + temp = temp.next!; + } + } } diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index 2c4f32cf17..b21ba1db02 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -9,9 +9,13 @@ import { isEqual } from 'vs/base/common/resources'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; +export interface MarkdownStringTrustedOptions { + readonly enabledCommands: readonly string[]; +} + export interface IMarkdownString { readonly value: string; - readonly isTrusted?: boolean; + readonly isTrusted?: boolean | MarkdownStringTrustedOptions; readonly supportThemeIcons?: boolean; readonly supportHtml?: boolean; readonly baseUri?: UriComponents; @@ -26,14 +30,14 @@ export const enum MarkdownStringTextNewlineStyle { export class MarkdownString implements IMarkdownString { public value: string; - public isTrusted?: boolean; + public isTrusted?: boolean | MarkdownStringTrustedOptions; public supportThemeIcons?: boolean; public supportHtml?: boolean; public baseUri?: URI; constructor( value: string = '', - isTrustedOrOptions: boolean | { isTrusted?: boolean; supportThemeIcons?: boolean; supportHtml?: boolean } = false, + isTrustedOrOptions: boolean | { isTrusted?: boolean | MarkdownStringTrustedOptions; supportThemeIcons?: boolean; supportHtml?: boolean } = false, ) { this.value = value; if (typeof this.value !== 'string') { @@ -114,7 +118,7 @@ export function isMarkdownString(thing: any): thing is IMarkdownString { return true; } else if (thing && typeof thing === 'object') { return typeof (<IMarkdownString>thing).value === 'string' - && (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined) + && (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || typeof (<IMarkdownString>thing).isTrusted === 'object' || (<IMarkdownString>thing).isTrusted === undefined) && (typeof (<IMarkdownString>thing).supportThemeIcons === 'boolean' || (<IMarkdownString>thing).supportThemeIcons === undefined); } return false; @@ -136,7 +140,7 @@ export function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boo export function escapeMarkdownSyntaxTokens(text: string): string { // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash - return text.replace(/[\\`*_{}[\]()#+\-!]/g, '\\$&'); + return text.replace(/[\\`*_{}[\]()#+\-!~]/g, '\\$&'); } export function escapeDoubleQuotes(input: string) { @@ -147,7 +151,7 @@ export function removeMarkdownEscapes(text: string): string { if (!text) { return text; } - return text.replace(/\\([\\`*_{}[\]()#+\-.!])/g, '$1'); + return text.replace(/\\([\\`*_{}[\]()#+\-.!~])/g, '$1'); } export function parseHrefAndDimensions(href: string): { href: string; dimensions: string[] } { diff --git a/src/vs/base/common/iconLabels.ts b/src/vs/base/common/iconLabels.ts index 810f7434fc..f6db61b64f 100644 --- a/src/vs/base/common/iconLabels.ts +++ b/src/vs/base/common/iconLabels.ts @@ -3,14 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CSSIcon } from 'vs/base/common/codicons'; import { IMatch, matchesFuzzy } from 'vs/base/common/filters'; import { ltrim } from 'vs/base/common/strings'; +import { ThemeIcon } from 'vs/base/common/themables'; -export const iconStartMarker = '$('; +const iconStartMarker = '$('; -const iconsRegex = new RegExp(`\\$\\(${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups -const iconNameCharacterRegexp = new RegExp(CSSIcon.iconNameCharacter); +const iconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g'); export function escapeIcons(text: string): string { @@ -24,6 +23,10 @@ export function markdownEscapeEscapedIcons(text: string): string { } const stripIconsRegex = new RegExp(`(\\s)?(\\\\)?${iconsRegex.source}(\\s)?`, 'g'); + +/** + * Takes a label with icons (`$(iconId)xyz`) and strips the icons out (`xyz`) + */ export function stripIcons(text: string): string { if (text.indexOf(iconStartMarker) === -1) { return text; @@ -33,104 +36,57 @@ export function stripIcons(text: string): string { } +/** + * Takes a label with icons (`$(iconId)xyz`), removes the icon syntax adds whitespace so that screen readers can read the text better. + */ +export function getCodiconAriaLabel(text: string | undefined) { + if (!text) { + return ''; + } + + return text.replace(/\$\((.*?)\)/g, (_match, codiconName) => ` ${codiconName} `).trim(); +} + + export interface IParsedLabelWithIcons { readonly text: string; readonly iconOffsets?: readonly number[]; } -export function parseLabelWithIcons(text: string): IParsedLabelWithIcons { - const firstIconIndex = text.indexOf(iconStartMarker); - if (firstIconIndex === -1) { - return { text }; // return early if the word does not include an icon - } +const _parseIconsRegex = new RegExp(`\\$\\(${ThemeIcon.iconNameCharacter}+\\)`, 'g'); - return doParseLabelWithIcons(text, firstIconIndex); -} +/** + * Takes a label with icons (`abc $(iconId)xyz`) and returns the text (`abc xyz`) and the offsets of the icons (`[3]`) + */ +export function parseLabelWithIcons(input: string): IParsedLabelWithIcons { -function doParseLabelWithIcons(text: string, firstIconIndex: number): IParsedLabelWithIcons { + _parseIconsRegex.lastIndex = 0; + + let text = ''; const iconOffsets: number[] = []; - let textWithoutIcons: string = ''; - - function appendChars(chars: string) { - if (chars) { - textWithoutIcons += chars; - - for (const _ of chars) { - iconOffsets.push(iconsOffset); // make sure to fill in icon offsets - } - } - } - - let currentIconStart = -1; - let currentIconValue: string = ''; let iconsOffset = 0; - let char: string; - let nextChar: string; + while (true) { + const pos = _parseIconsRegex.lastIndex; + const match = _parseIconsRegex.exec(input); - let offset = firstIconIndex; - const length = text.length; - - // Append all characters until the first icon - appendChars(text.substr(0, firstIconIndex)); - - // example: $(file-symlink-file) my cool $(other-icon) entry - while (offset < length) { - char = text[offset]; - nextChar = text[offset + 1]; - - // beginning of icon: some value $( <-- - if (char === iconStartMarker[0] && nextChar === iconStartMarker[1]) { - currentIconStart = offset; - - // if we had a previous potential icon value without - // the closing ')', it was actually not an icon and - // so we have to add it to the actual value - appendChars(currentIconValue); - - currentIconValue = iconStartMarker; - - offset++; // jump over '(' - } - - // end of icon: some value $(some-icon) <-- - else if (char === ')' && currentIconStart !== -1) { - const currentIconLength = offset - currentIconStart + 1; // +1 to include the closing ')' - iconsOffset += currentIconLength; - currentIconStart = -1; - currentIconValue = ''; - } - - // within icon - else if (currentIconStart !== -1) { - // Make sure this is a real icon name - if (iconNameCharacterRegexp.test(char)) { - currentIconValue += char; - } else { - // This is not a real icon, treat it as text - appendChars(currentIconValue); - - currentIconStart = -1; - currentIconValue = ''; + const chars = input.substring(pos, match?.index); + if (chars.length > 0) { + text += chars; + for (let i = 0; i < chars.length; i++) { + iconOffsets.push(iconsOffset); } } - - // any value outside of icon - else { - appendChars(char); + if (!match) { + break; } - - offset++; + iconsOffset += match[0].length; } - // if we had a previous potential icon value without - // the closing ')', it was actually not an icon and - // so we have to add it to the actual value - appendChars(currentIconValue); - - return { text: textWithoutIcons, iconOffsets }; + return { text, iconOffsets }; } + export function matchesFuzzyIconAware(query: string, target: IParsedLabelWithIcons, enableSeparateSubstringMatching = false): IMatch[] | null { const { text, iconOffsets } = target; diff --git a/src/vs/base/common/ime.ts b/src/vs/base/common/ime.ts new file mode 100644 index 0000000000..cf24d34735 --- /dev/null +++ b/src/vs/base/common/ime.ts @@ -0,0 +1,36 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; + +export class IMEImpl { + + private readonly _onDidChange = new Emitter<void>(); + public readonly onDidChange = this._onDidChange.event; + + private _enabled = true; + + public get enabled() { + return this._enabled; + } + + /** + * Enable IME + */ + public enable(): void { + this._enabled = true; + this._onDidChange.fire(); + } + + /** + * Disable IME + */ + public disable(): void { + this._enabled = false; + this._onDidChange.fire(); + } +} + +export const IME = new IMEImpl(); diff --git a/src/vs/base/common/iterator.ts b/src/vs/base/common/iterator.ts index 08f97741a6..8c04eb286f 100644 --- a/src/vs/base/common/iterator.ts +++ b/src/vs/base/common/iterator.ts @@ -5,7 +5,7 @@ export namespace Iterable { - export function is<T = any>(thing: any): thing is IterableIterator<T> { + export function is<T = any>(thing: any): thing is Iterable<T> { return thing && typeof thing === 'object' && typeof thing[Symbol.iterator] === 'function'; } @@ -18,6 +18,14 @@ export namespace Iterable { yield element; } + export function wrap<T>(iterableOrElement: Iterable<T> | T): Iterable<T> { + if (is(iterableOrElement)) { + return iterableOrElement; + } else { + return single(iterableOrElement); + } + } + export function from<T>(iterable: Iterable<T> | undefined | null): Iterable<T> { return iterable || _empty; } @@ -76,14 +84,6 @@ export namespace Iterable { } } - export function* concatNested<T>(iterables: Iterable<Iterable<T>>): Iterable<T> { - for (const iterable of iterables) { - for (const element of iterable) { - yield element; - } - } - } - export function reduce<T, R>(iterable: Iterable<T>, reducer: (previousValue: R, currentValue: T) => R, initialValue: R): R { let value = initialValue; for (const element of iterable) { @@ -92,13 +92,6 @@ export namespace Iterable { return value; } - export function forEach<T>(iterable: Iterable<T>, fn: (t: T, index: number) => any): void { - let index = 0; - for (const element of iterable) { - fn(element, index++); - } - } - /** * Returns an iterable slice of the array, with the same semantics as `array.slice()`. */ @@ -143,33 +136,4 @@ export namespace Iterable { return [consumed, { [Symbol.iterator]() { return iterator; } }]; } - - /** - * Consumes `atMost` elements from iterable and returns the consumed elements, - * and an iterable for the rest of the elements. - */ - export function collect<T>(iterable: Iterable<T>): T[] { - return consume(iterable)[0]; - } - - /** - * Returns whether the iterables are the same length and all items are - * equal using the comparator function. - */ - export function equals<T>(a: Iterable<T>, b: Iterable<T>, 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 282e7f3aa8..511c028aa7 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -97,6 +97,11 @@ export const enum KeyCode { F17, F18, F19, + F20, + F21, + F22, + F23, + F24, NumLock, ScrollLock, @@ -482,250 +487,250 @@ for (let i = 0; i <= KeyCode.MAX_VALUE; i++) { (function () { // See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx - // See https://github.com/microsoft/node-native-keymap/blob/master/deps/chromium/keyboard_codes_win.h + // See https://github.com/microsoft/node-native-keymap/blob/88c0b0e5/deps/chromium/keyboard_codes_win.h const empty = ''; - type IMappingEntry = [number, 0 | 1, ScanCode, string, KeyCode, string, number, string, string, string]; + type IMappingEntry = [0 | 1, ScanCode, string, KeyCode, string, number, string, string, string]; const mappings: IMappingEntry[] = [ - // keyCodeOrd, immutable, scanCode, scanCodeStr, keyCode, keyCodeStr, eventKeyCode, vkey, usUserSettingsLabel, generalUserSettingsLabel - [0, 1, ScanCode.None, 'None', KeyCode.Unknown, 'unknown', 0, 'VK_UNKNOWN', empty, empty], - [0, 1, ScanCode.Hyper, 'Hyper', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Super, 'Super', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Fn, 'Fn', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.FnLock, 'FnLock', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Suspend, 'Suspend', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Resume, 'Resume', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Turbo, 'Turbo', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Sleep, 'Sleep', KeyCode.Unknown, empty, 0, 'VK_SLEEP', empty, empty], - [0, 1, ScanCode.WakeUp, 'WakeUp', KeyCode.Unknown, empty, 0, empty, empty, empty], - [31, 0, ScanCode.KeyA, 'KeyA', KeyCode.KeyA, 'A', 65, 'VK_A', empty, empty], - [32, 0, ScanCode.KeyB, 'KeyB', KeyCode.KeyB, 'B', 66, 'VK_B', empty, empty], - [33, 0, ScanCode.KeyC, 'KeyC', KeyCode.KeyC, 'C', 67, 'VK_C', empty, empty], - [34, 0, ScanCode.KeyD, 'KeyD', KeyCode.KeyD, 'D', 68, 'VK_D', empty, empty], - [35, 0, ScanCode.KeyE, 'KeyE', KeyCode.KeyE, 'E', 69, 'VK_E', empty, empty], - [36, 0, ScanCode.KeyF, 'KeyF', KeyCode.KeyF, 'F', 70, 'VK_F', empty, empty], - [37, 0, ScanCode.KeyG, 'KeyG', KeyCode.KeyG, 'G', 71, 'VK_G', empty, empty], - [38, 0, ScanCode.KeyH, 'KeyH', KeyCode.KeyH, 'H', 72, 'VK_H', empty, empty], - [39, 0, ScanCode.KeyI, 'KeyI', KeyCode.KeyI, 'I', 73, 'VK_I', empty, empty], - [40, 0, ScanCode.KeyJ, 'KeyJ', KeyCode.KeyJ, 'J', 74, 'VK_J', empty, empty], - [41, 0, ScanCode.KeyK, 'KeyK', KeyCode.KeyK, 'K', 75, 'VK_K', empty, empty], - [42, 0, ScanCode.KeyL, 'KeyL', KeyCode.KeyL, 'L', 76, 'VK_L', empty, empty], - [43, 0, ScanCode.KeyM, 'KeyM', KeyCode.KeyM, 'M', 77, 'VK_M', empty, empty], - [44, 0, ScanCode.KeyN, 'KeyN', KeyCode.KeyN, 'N', 78, 'VK_N', empty, empty], - [45, 0, ScanCode.KeyO, 'KeyO', KeyCode.KeyO, 'O', 79, 'VK_O', empty, empty], - [46, 0, ScanCode.KeyP, 'KeyP', KeyCode.KeyP, 'P', 80, 'VK_P', empty, empty], - [47, 0, ScanCode.KeyQ, 'KeyQ', KeyCode.KeyQ, 'Q', 81, 'VK_Q', empty, empty], - [48, 0, ScanCode.KeyR, 'KeyR', KeyCode.KeyR, 'R', 82, 'VK_R', empty, empty], - [49, 0, ScanCode.KeyS, 'KeyS', KeyCode.KeyS, 'S', 83, 'VK_S', empty, empty], - [50, 0, ScanCode.KeyT, 'KeyT', KeyCode.KeyT, 'T', 84, 'VK_T', empty, empty], - [51, 0, ScanCode.KeyU, 'KeyU', KeyCode.KeyU, 'U', 85, 'VK_U', empty, empty], - [52, 0, ScanCode.KeyV, 'KeyV', KeyCode.KeyV, 'V', 86, 'VK_V', empty, empty], - [53, 0, ScanCode.KeyW, 'KeyW', KeyCode.KeyW, 'W', 87, 'VK_W', empty, empty], - [54, 0, ScanCode.KeyX, 'KeyX', KeyCode.KeyX, 'X', 88, 'VK_X', empty, empty], - [55, 0, ScanCode.KeyY, 'KeyY', KeyCode.KeyY, 'Y', 89, 'VK_Y', empty, empty], - [56, 0, ScanCode.KeyZ, 'KeyZ', KeyCode.KeyZ, 'Z', 90, 'VK_Z', empty, empty], - [22, 0, ScanCode.Digit1, 'Digit1', KeyCode.Digit1, '1', 49, 'VK_1', empty, empty], - [23, 0, ScanCode.Digit2, 'Digit2', KeyCode.Digit2, '2', 50, 'VK_2', empty, empty], - [24, 0, ScanCode.Digit3, 'Digit3', KeyCode.Digit3, '3', 51, 'VK_3', empty, empty], - [25, 0, ScanCode.Digit4, 'Digit4', KeyCode.Digit4, '4', 52, 'VK_4', empty, empty], - [26, 0, ScanCode.Digit5, 'Digit5', KeyCode.Digit5, '5', 53, 'VK_5', empty, empty], - [27, 0, ScanCode.Digit6, 'Digit6', KeyCode.Digit6, '6', 54, 'VK_6', empty, empty], - [28, 0, ScanCode.Digit7, 'Digit7', KeyCode.Digit7, '7', 55, 'VK_7', empty, empty], - [29, 0, ScanCode.Digit8, 'Digit8', KeyCode.Digit8, '8', 56, 'VK_8', empty, empty], - [30, 0, ScanCode.Digit9, 'Digit9', KeyCode.Digit9, '9', 57, 'VK_9', empty, empty], - [21, 0, ScanCode.Digit0, 'Digit0', KeyCode.Digit0, '0', 48, 'VK_0', empty, empty], - [3, 1, ScanCode.Enter, 'Enter', KeyCode.Enter, 'Enter', 13, 'VK_RETURN', empty, empty], - [9, 1, ScanCode.Escape, 'Escape', KeyCode.Escape, 'Escape', 27, 'VK_ESCAPE', empty, empty], - [1, 1, ScanCode.Backspace, 'Backspace', KeyCode.Backspace, 'Backspace', 8, 'VK_BACK', empty, empty], - [2, 1, ScanCode.Tab, 'Tab', KeyCode.Tab, 'Tab', 9, 'VK_TAB', empty, empty], - [10, 1, ScanCode.Space, 'Space', KeyCode.Space, 'Space', 32, 'VK_SPACE', empty, empty], - [83, 0, ScanCode.Minus, 'Minus', KeyCode.Minus, '-', 189, 'VK_OEM_MINUS', '-', 'OEM_MINUS'], - [81, 0, ScanCode.Equal, 'Equal', KeyCode.Equal, '=', 187, 'VK_OEM_PLUS', '=', 'OEM_PLUS'], - [87, 0, ScanCode.BracketLeft, 'BracketLeft', KeyCode.BracketLeft, '[', 219, 'VK_OEM_4', '[', 'OEM_4'], - [89, 0, ScanCode.BracketRight, 'BracketRight', KeyCode.BracketRight, ']', 221, 'VK_OEM_6', ']', 'OEM_6'], - [88, 0, ScanCode.Backslash, 'Backslash', KeyCode.Backslash, '\\', 220, 'VK_OEM_5', '\\', 'OEM_5'], - [0, 0, ScanCode.IntlHash, 'IntlHash', KeyCode.Unknown, empty, 0, empty, empty, empty], // has been dropped from the w3c spec - [80, 0, ScanCode.Semicolon, 'Semicolon', KeyCode.Semicolon, ';', 186, 'VK_OEM_1', ';', 'OEM_1'], - [90, 0, ScanCode.Quote, 'Quote', KeyCode.Quote, '\'', 222, 'VK_OEM_7', '\'', 'OEM_7'], - [86, 0, ScanCode.Backquote, 'Backquote', KeyCode.Backquote, '`', 192, 'VK_OEM_3', '`', 'OEM_3'], - [82, 0, ScanCode.Comma, 'Comma', KeyCode.Comma, ',', 188, 'VK_OEM_COMMA', ',', 'OEM_COMMA'], - [84, 0, ScanCode.Period, 'Period', KeyCode.Period, '.', 190, 'VK_OEM_PERIOD', '.', 'OEM_PERIOD'], - [85, 0, ScanCode.Slash, 'Slash', KeyCode.Slash, '/', 191, 'VK_OEM_2', '/', 'OEM_2'], - [8, 1, ScanCode.CapsLock, 'CapsLock', KeyCode.CapsLock, 'CapsLock', 20, 'VK_CAPITAL', empty, empty], - [59, 1, ScanCode.F1, 'F1', KeyCode.F1, 'F1', 112, 'VK_F1', empty, empty], - [60, 1, ScanCode.F2, 'F2', KeyCode.F2, 'F2', 113, 'VK_F2', empty, empty], - [61, 1, ScanCode.F3, 'F3', KeyCode.F3, 'F3', 114, 'VK_F3', empty, empty], - [62, 1, ScanCode.F4, 'F4', KeyCode.F4, 'F4', 115, 'VK_F4', empty, empty], - [63, 1, ScanCode.F5, 'F5', KeyCode.F5, 'F5', 116, 'VK_F5', empty, empty], - [64, 1, ScanCode.F6, 'F6', KeyCode.F6, 'F6', 117, 'VK_F6', empty, empty], - [65, 1, ScanCode.F7, 'F7', KeyCode.F7, 'F7', 118, 'VK_F7', empty, empty], - [66, 1, ScanCode.F8, 'F8', KeyCode.F8, 'F8', 119, 'VK_F8', empty, empty], - [67, 1, ScanCode.F9, 'F9', KeyCode.F9, 'F9', 120, 'VK_F9', empty, empty], - [68, 1, ScanCode.F10, 'F10', KeyCode.F10, 'F10', 121, 'VK_F10', empty, empty], - [69, 1, ScanCode.F11, 'F11', KeyCode.F11, 'F11', 122, 'VK_F11', empty, empty], - [70, 1, ScanCode.F12, 'F12', KeyCode.F12, 'F12', 123, 'VK_F12', empty, empty], - [0, 1, ScanCode.PrintScreen, 'PrintScreen', KeyCode.Unknown, empty, 0, empty, empty, empty], - [79, 1, ScanCode.ScrollLock, 'ScrollLock', KeyCode.ScrollLock, 'ScrollLock', 145, 'VK_SCROLL', empty, empty], - [7, 1, ScanCode.Pause, 'Pause', KeyCode.PauseBreak, 'PauseBreak', 19, 'VK_PAUSE', empty, empty], - [19, 1, ScanCode.Insert, 'Insert', KeyCode.Insert, 'Insert', 45, 'VK_INSERT', empty, empty], - [14, 1, ScanCode.Home, 'Home', KeyCode.Home, 'Home', 36, 'VK_HOME', empty, empty], - [11, 1, ScanCode.PageUp, 'PageUp', KeyCode.PageUp, 'PageUp', 33, 'VK_PRIOR', empty, empty], - [20, 1, ScanCode.Delete, 'Delete', KeyCode.Delete, 'Delete', 46, 'VK_DELETE', empty, empty], - [13, 1, ScanCode.End, 'End', KeyCode.End, 'End', 35, 'VK_END', empty, empty], - [12, 1, ScanCode.PageDown, 'PageDown', KeyCode.PageDown, 'PageDown', 34, 'VK_NEXT', empty, empty], - [17, 1, ScanCode.ArrowRight, 'ArrowRight', KeyCode.RightArrow, 'RightArrow', 39, 'VK_RIGHT', 'Right', empty], - [15, 1, ScanCode.ArrowLeft, 'ArrowLeft', KeyCode.LeftArrow, 'LeftArrow', 37, 'VK_LEFT', 'Left', empty], - [18, 1, ScanCode.ArrowDown, 'ArrowDown', KeyCode.DownArrow, 'DownArrow', 40, 'VK_DOWN', 'Down', empty], - [16, 1, ScanCode.ArrowUp, 'ArrowUp', KeyCode.UpArrow, 'UpArrow', 38, 'VK_UP', 'Up', empty], - [78, 1, ScanCode.NumLock, 'NumLock', KeyCode.NumLock, 'NumLock', 144, 'VK_NUMLOCK', empty, empty], - [108, 1, ScanCode.NumpadDivide, 'NumpadDivide', KeyCode.NumpadDivide, 'NumPad_Divide', 111, 'VK_DIVIDE', empty, empty], - [103, 1, ScanCode.NumpadMultiply, 'NumpadMultiply', KeyCode.NumpadMultiply, 'NumPad_Multiply', 106, 'VK_MULTIPLY', empty, empty], - [106, 1, ScanCode.NumpadSubtract, 'NumpadSubtract', KeyCode.NumpadSubtract, 'NumPad_Subtract', 109, 'VK_SUBTRACT', empty, empty], - [104, 1, ScanCode.NumpadAdd, 'NumpadAdd', KeyCode.NumpadAdd, 'NumPad_Add', 107, 'VK_ADD', empty, empty], - [3, 1, ScanCode.NumpadEnter, 'NumpadEnter', KeyCode.Enter, empty, 0, empty, empty, empty], - [94, 1, ScanCode.Numpad1, 'Numpad1', KeyCode.Numpad1, 'NumPad1', 97, 'VK_NUMPAD1', empty, empty], - [95, 1, ScanCode.Numpad2, 'Numpad2', KeyCode.Numpad2, 'NumPad2', 98, 'VK_NUMPAD2', empty, empty], - [96, 1, ScanCode.Numpad3, 'Numpad3', KeyCode.Numpad3, 'NumPad3', 99, 'VK_NUMPAD3', empty, empty], - [97, 1, ScanCode.Numpad4, 'Numpad4', KeyCode.Numpad4, 'NumPad4', 100, 'VK_NUMPAD4', empty, empty], - [98, 1, ScanCode.Numpad5, 'Numpad5', KeyCode.Numpad5, 'NumPad5', 101, 'VK_NUMPAD5', empty, empty], - [99, 1, ScanCode.Numpad6, 'Numpad6', KeyCode.Numpad6, 'NumPad6', 102, 'VK_NUMPAD6', empty, empty], - [100, 1, ScanCode.Numpad7, 'Numpad7', KeyCode.Numpad7, 'NumPad7', 103, 'VK_NUMPAD7', empty, empty], - [101, 1, ScanCode.Numpad8, 'Numpad8', KeyCode.Numpad8, 'NumPad8', 104, 'VK_NUMPAD8', empty, empty], - [102, 1, ScanCode.Numpad9, 'Numpad9', KeyCode.Numpad9, 'NumPad9', 105, 'VK_NUMPAD9', empty, empty], - [93, 1, ScanCode.Numpad0, 'Numpad0', KeyCode.Numpad0, 'NumPad0', 96, 'VK_NUMPAD0', empty, empty], - [107, 1, ScanCode.NumpadDecimal, 'NumpadDecimal', KeyCode.NumpadDecimal, 'NumPad_Decimal', 110, 'VK_DECIMAL', empty, empty], - [92, 0, ScanCode.IntlBackslash, 'IntlBackslash', KeyCode.IntlBackslash, 'OEM_102', 226, 'VK_OEM_102', empty, empty], - [58, 1, ScanCode.ContextMenu, 'ContextMenu', KeyCode.ContextMenu, 'ContextMenu', 93, empty, empty, empty], - [0, 1, ScanCode.Power, 'Power', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadEqual, 'NumpadEqual', KeyCode.Unknown, empty, 0, empty, empty, empty], - [71, 1, ScanCode.F13, 'F13', KeyCode.F13, 'F13', 124, 'VK_F13', empty, empty], - [72, 1, ScanCode.F14, 'F14', KeyCode.F14, 'F14', 125, 'VK_F14', empty, empty], - [73, 1, ScanCode.F15, 'F15', KeyCode.F15, 'F15', 126, 'VK_F15', empty, empty], - [74, 1, ScanCode.F16, 'F16', KeyCode.F16, 'F16', 127, 'VK_F16', empty, empty], - [75, 1, ScanCode.F17, 'F17', KeyCode.F17, 'F17', 128, 'VK_F17', empty, empty], - [76, 1, ScanCode.F18, 'F18', KeyCode.F18, 'F18', 129, 'VK_F18', empty, empty], - [77, 1, ScanCode.F19, 'F19', KeyCode.F19, 'F19', 130, 'VK_F19', empty, empty], - [0, 1, ScanCode.F20, 'F20', KeyCode.Unknown, empty, 0, 'VK_F20', empty, empty], - [0, 1, ScanCode.F21, 'F21', KeyCode.Unknown, empty, 0, 'VK_F21', empty, empty], - [0, 1, ScanCode.F22, 'F22', KeyCode.Unknown, empty, 0, 'VK_F22', empty, empty], - [0, 1, ScanCode.F23, 'F23', KeyCode.Unknown, empty, 0, 'VK_F23', empty, empty], - [0, 1, ScanCode.F24, 'F24', KeyCode.Unknown, empty, 0, 'VK_F24', empty, empty], - [0, 1, ScanCode.Open, 'Open', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Help, 'Help', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Select, 'Select', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Again, 'Again', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Undo, 'Undo', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Cut, 'Cut', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Copy, 'Copy', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Paste, 'Paste', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Find, 'Find', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.AudioVolumeMute, 'AudioVolumeMute', KeyCode.AudioVolumeMute, 'AudioVolumeMute', 173, 'VK_VOLUME_MUTE', empty, empty], - [0, 1, ScanCode.AudioVolumeUp, 'AudioVolumeUp', KeyCode.AudioVolumeUp, 'AudioVolumeUp', 175, 'VK_VOLUME_UP', empty, empty], - [0, 1, ScanCode.AudioVolumeDown, 'AudioVolumeDown', KeyCode.AudioVolumeDown, 'AudioVolumeDown', 174, 'VK_VOLUME_DOWN', empty, empty], - [105, 1, ScanCode.NumpadComma, 'NumpadComma', KeyCode.NUMPAD_SEPARATOR, 'NumPad_Separator', 108, 'VK_SEPARATOR', empty, empty], - [110, 0, ScanCode.IntlRo, 'IntlRo', KeyCode.ABNT_C1, 'ABNT_C1', 193, 'VK_ABNT_C1', empty, empty], - [0, 1, ScanCode.KanaMode, 'KanaMode', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 0, ScanCode.IntlYen, 'IntlYen', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Convert, 'Convert', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NonConvert, 'NonConvert', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Lang1, 'Lang1', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Lang2, 'Lang2', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Lang3, 'Lang3', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Lang4, 'Lang4', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Lang5, 'Lang5', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Abort, 'Abort', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.Props, 'Props', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadParenLeft, 'NumpadParenLeft', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadParenRight, 'NumpadParenRight', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadBackspace, 'NumpadBackspace', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadMemoryStore, 'NumpadMemoryStore', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadMemoryRecall, 'NumpadMemoryRecall', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadMemoryClear, 'NumpadMemoryClear', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadMemoryAdd, 'NumpadMemoryAdd', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadMemorySubtract, 'NumpadMemorySubtract', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.NumpadClear, 'NumpadClear', KeyCode.Clear, 'Clear', 12, 'VK_CLEAR', empty, empty], - [0, 1, ScanCode.NumpadClearEntry, 'NumpadClearEntry', KeyCode.Unknown, empty, 0, empty, empty, empty], - [5, 1, ScanCode.None, empty, KeyCode.Ctrl, 'Ctrl', 17, 'VK_CONTROL', empty, empty], - [4, 1, ScanCode.None, empty, KeyCode.Shift, 'Shift', 16, 'VK_SHIFT', empty, empty], - [6, 1, ScanCode.None, empty, KeyCode.Alt, 'Alt', 18, 'VK_MENU', empty, empty], - [57, 1, ScanCode.None, empty, KeyCode.Meta, 'Meta', 0, 'VK_COMMAND', empty, empty], - [5, 1, ScanCode.ControlLeft, 'ControlLeft', KeyCode.Ctrl, empty, 0, 'VK_LCONTROL', empty, empty], - [4, 1, ScanCode.ShiftLeft, 'ShiftLeft', KeyCode.Shift, empty, 0, 'VK_LSHIFT', empty, empty], - [6, 1, ScanCode.AltLeft, 'AltLeft', KeyCode.Alt, empty, 0, 'VK_LMENU', empty, empty], - [57, 1, ScanCode.MetaLeft, 'MetaLeft', KeyCode.Meta, empty, 0, 'VK_LWIN', empty, empty], - [5, 1, ScanCode.ControlRight, 'ControlRight', KeyCode.Ctrl, empty, 0, 'VK_RCONTROL', empty, empty], - [4, 1, ScanCode.ShiftRight, 'ShiftRight', KeyCode.Shift, empty, 0, 'VK_RSHIFT', empty, empty], - [6, 1, ScanCode.AltRight, 'AltRight', KeyCode.Alt, empty, 0, 'VK_RMENU', empty, empty], - [57, 1, ScanCode.MetaRight, 'MetaRight', KeyCode.Meta, empty, 0, 'VK_RWIN', empty, empty], - [0, 1, ScanCode.BrightnessUp, 'BrightnessUp', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.BrightnessDown, 'BrightnessDown', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.MediaPlay, 'MediaPlay', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.MediaRecord, 'MediaRecord', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.MediaFastForward, 'MediaFastForward', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.MediaRewind, 'MediaRewind', KeyCode.Unknown, empty, 0, empty, empty, empty], - [114, 1, ScanCode.MediaTrackNext, 'MediaTrackNext', KeyCode.MediaTrackNext, 'MediaTrackNext', 176, 'VK_MEDIA_NEXT_TRACK', empty, empty], - [115, 1, ScanCode.MediaTrackPrevious, 'MediaTrackPrevious', KeyCode.MediaTrackPrevious, 'MediaTrackPrevious', 177, 'VK_MEDIA_PREV_TRACK', empty, empty], - [116, 1, ScanCode.MediaStop, 'MediaStop', KeyCode.MediaStop, 'MediaStop', 178, 'VK_MEDIA_STOP', empty, empty], - [0, 1, ScanCode.Eject, 'Eject', KeyCode.Unknown, empty, 0, empty, empty, empty], - [117, 1, ScanCode.MediaPlayPause, 'MediaPlayPause', KeyCode.MediaPlayPause, 'MediaPlayPause', 179, 'VK_MEDIA_PLAY_PAUSE', empty, empty], - [0, 1, ScanCode.MediaSelect, 'MediaSelect', KeyCode.LaunchMediaPlayer, 'LaunchMediaPlayer', 181, 'VK_MEDIA_LAUNCH_MEDIA_SELECT', empty, empty], - [0, 1, ScanCode.LaunchMail, 'LaunchMail', KeyCode.LaunchMail, 'LaunchMail', 180, 'VK_MEDIA_LAUNCH_MAIL', empty, empty], - [0, 1, ScanCode.LaunchApp2, 'LaunchApp2', KeyCode.LaunchApp2, 'LaunchApp2', 183, 'VK_MEDIA_LAUNCH_APP2', empty, empty], - [0, 1, ScanCode.LaunchApp1, 'LaunchApp1', KeyCode.Unknown, empty, 0, 'VK_MEDIA_LAUNCH_APP1', empty, empty], - [0, 1, ScanCode.SelectTask, 'SelectTask', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.LaunchScreenSaver, 'LaunchScreenSaver', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.BrowserSearch, 'BrowserSearch', KeyCode.BrowserSearch, 'BrowserSearch', 170, 'VK_BROWSER_SEARCH', empty, empty], - [0, 1, ScanCode.BrowserHome, 'BrowserHome', KeyCode.BrowserHome, 'BrowserHome', 172, 'VK_BROWSER_HOME', empty, empty], - [112, 1, ScanCode.BrowserBack, 'BrowserBack', KeyCode.BrowserBack, 'BrowserBack', 166, 'VK_BROWSER_BACK', empty, empty], - [113, 1, ScanCode.BrowserForward, 'BrowserForward', KeyCode.BrowserForward, 'BrowserForward', 167, 'VK_BROWSER_FORWARD', empty, empty], - [0, 1, ScanCode.BrowserStop, 'BrowserStop', KeyCode.Unknown, empty, 0, 'VK_BROWSER_STOP', empty, empty], - [0, 1, ScanCode.BrowserRefresh, 'BrowserRefresh', KeyCode.Unknown, empty, 0, 'VK_BROWSER_REFRESH', empty, empty], - [0, 1, ScanCode.BrowserFavorites, 'BrowserFavorites', KeyCode.Unknown, empty, 0, 'VK_BROWSER_FAVORITES', empty, empty], - [0, 1, ScanCode.ZoomToggle, 'ZoomToggle', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.MailReply, 'MailReply', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.MailForward, 'MailForward', KeyCode.Unknown, empty, 0, empty, empty, empty], - [0, 1, ScanCode.MailSend, 'MailSend', KeyCode.Unknown, empty, 0, empty, empty, empty], + // immutable, scanCode, scanCodeStr, keyCode, keyCodeStr, eventKeyCode, vkey, usUserSettingsLabel, generalUserSettingsLabel + [1, ScanCode.None, 'None', KeyCode.Unknown, 'unknown', 0, 'VK_UNKNOWN', empty, empty], + [1, ScanCode.Hyper, 'Hyper', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Super, 'Super', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Fn, 'Fn', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.FnLock, 'FnLock', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Suspend, 'Suspend', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Resume, 'Resume', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Turbo, 'Turbo', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Sleep, 'Sleep', KeyCode.Unknown, empty, 0, 'VK_SLEEP', empty, empty], + [1, ScanCode.WakeUp, 'WakeUp', KeyCode.Unknown, empty, 0, empty, empty, empty], + [0, ScanCode.KeyA, 'KeyA', KeyCode.KeyA, 'A', 65, 'VK_A', empty, empty], + [0, ScanCode.KeyB, 'KeyB', KeyCode.KeyB, 'B', 66, 'VK_B', empty, empty], + [0, ScanCode.KeyC, 'KeyC', KeyCode.KeyC, 'C', 67, 'VK_C', empty, empty], + [0, ScanCode.KeyD, 'KeyD', KeyCode.KeyD, 'D', 68, 'VK_D', empty, empty], + [0, ScanCode.KeyE, 'KeyE', KeyCode.KeyE, 'E', 69, 'VK_E', empty, empty], + [0, ScanCode.KeyF, 'KeyF', KeyCode.KeyF, 'F', 70, 'VK_F', empty, empty], + [0, ScanCode.KeyG, 'KeyG', KeyCode.KeyG, 'G', 71, 'VK_G', empty, empty], + [0, ScanCode.KeyH, 'KeyH', KeyCode.KeyH, 'H', 72, 'VK_H', empty, empty], + [0, ScanCode.KeyI, 'KeyI', KeyCode.KeyI, 'I', 73, 'VK_I', empty, empty], + [0, ScanCode.KeyJ, 'KeyJ', KeyCode.KeyJ, 'J', 74, 'VK_J', empty, empty], + [0, ScanCode.KeyK, 'KeyK', KeyCode.KeyK, 'K', 75, 'VK_K', empty, empty], + [0, ScanCode.KeyL, 'KeyL', KeyCode.KeyL, 'L', 76, 'VK_L', empty, empty], + [0, ScanCode.KeyM, 'KeyM', KeyCode.KeyM, 'M', 77, 'VK_M', empty, empty], + [0, ScanCode.KeyN, 'KeyN', KeyCode.KeyN, 'N', 78, 'VK_N', empty, empty], + [0, ScanCode.KeyO, 'KeyO', KeyCode.KeyO, 'O', 79, 'VK_O', empty, empty], + [0, ScanCode.KeyP, 'KeyP', KeyCode.KeyP, 'P', 80, 'VK_P', empty, empty], + [0, ScanCode.KeyQ, 'KeyQ', KeyCode.KeyQ, 'Q', 81, 'VK_Q', empty, empty], + [0, ScanCode.KeyR, 'KeyR', KeyCode.KeyR, 'R', 82, 'VK_R', empty, empty], + [0, ScanCode.KeyS, 'KeyS', KeyCode.KeyS, 'S', 83, 'VK_S', empty, empty], + [0, ScanCode.KeyT, 'KeyT', KeyCode.KeyT, 'T', 84, 'VK_T', empty, empty], + [0, ScanCode.KeyU, 'KeyU', KeyCode.KeyU, 'U', 85, 'VK_U', empty, empty], + [0, ScanCode.KeyV, 'KeyV', KeyCode.KeyV, 'V', 86, 'VK_V', empty, empty], + [0, ScanCode.KeyW, 'KeyW', KeyCode.KeyW, 'W', 87, 'VK_W', empty, empty], + [0, ScanCode.KeyX, 'KeyX', KeyCode.KeyX, 'X', 88, 'VK_X', empty, empty], + [0, ScanCode.KeyY, 'KeyY', KeyCode.KeyY, 'Y', 89, 'VK_Y', empty, empty], + [0, ScanCode.KeyZ, 'KeyZ', KeyCode.KeyZ, 'Z', 90, 'VK_Z', empty, empty], + [0, ScanCode.Digit1, 'Digit1', KeyCode.Digit1, '1', 49, 'VK_1', empty, empty], + [0, ScanCode.Digit2, 'Digit2', KeyCode.Digit2, '2', 50, 'VK_2', empty, empty], + [0, ScanCode.Digit3, 'Digit3', KeyCode.Digit3, '3', 51, 'VK_3', empty, empty], + [0, ScanCode.Digit4, 'Digit4', KeyCode.Digit4, '4', 52, 'VK_4', empty, empty], + [0, ScanCode.Digit5, 'Digit5', KeyCode.Digit5, '5', 53, 'VK_5', empty, empty], + [0, ScanCode.Digit6, 'Digit6', KeyCode.Digit6, '6', 54, 'VK_6', empty, empty], + [0, ScanCode.Digit7, 'Digit7', KeyCode.Digit7, '7', 55, 'VK_7', empty, empty], + [0, ScanCode.Digit8, 'Digit8', KeyCode.Digit8, '8', 56, 'VK_8', empty, empty], + [0, ScanCode.Digit9, 'Digit9', KeyCode.Digit9, '9', 57, 'VK_9', empty, empty], + [0, ScanCode.Digit0, 'Digit0', KeyCode.Digit0, '0', 48, 'VK_0', empty, empty], + [1, ScanCode.Enter, 'Enter', KeyCode.Enter, 'Enter', 13, 'VK_RETURN', empty, empty], + [1, ScanCode.Escape, 'Escape', KeyCode.Escape, 'Escape', 27, 'VK_ESCAPE', empty, empty], + [1, ScanCode.Backspace, 'Backspace', KeyCode.Backspace, 'Backspace', 8, 'VK_BACK', empty, empty], + [1, ScanCode.Tab, 'Tab', KeyCode.Tab, 'Tab', 9, 'VK_TAB', empty, empty], + [1, ScanCode.Space, 'Space', KeyCode.Space, 'Space', 32, 'VK_SPACE', empty, empty], + [0, ScanCode.Minus, 'Minus', KeyCode.Minus, '-', 189, 'VK_OEM_MINUS', '-', 'OEM_MINUS'], + [0, ScanCode.Equal, 'Equal', KeyCode.Equal, '=', 187, 'VK_OEM_PLUS', '=', 'OEM_PLUS'], + [0, ScanCode.BracketLeft, 'BracketLeft', KeyCode.BracketLeft, '[', 219, 'VK_OEM_4', '[', 'OEM_4'], + [0, ScanCode.BracketRight, 'BracketRight', KeyCode.BracketRight, ']', 221, 'VK_OEM_6', ']', 'OEM_6'], + [0, ScanCode.Backslash, 'Backslash', KeyCode.Backslash, '\\', 220, 'VK_OEM_5', '\\', 'OEM_5'], + [0, ScanCode.IntlHash, 'IntlHash', KeyCode.Unknown, empty, 0, empty, empty, empty], // has been dropped from the w3c spec + [0, ScanCode.Semicolon, 'Semicolon', KeyCode.Semicolon, ';', 186, 'VK_OEM_1', ';', 'OEM_1'], + [0, ScanCode.Quote, 'Quote', KeyCode.Quote, '\'', 222, 'VK_OEM_7', '\'', 'OEM_7'], + [0, ScanCode.Backquote, 'Backquote', KeyCode.Backquote, '`', 192, 'VK_OEM_3', '`', 'OEM_3'], + [0, ScanCode.Comma, 'Comma', KeyCode.Comma, ',', 188, 'VK_OEM_COMMA', ',', 'OEM_COMMA'], + [0, ScanCode.Period, 'Period', KeyCode.Period, '.', 190, 'VK_OEM_PERIOD', '.', 'OEM_PERIOD'], + [0, ScanCode.Slash, 'Slash', KeyCode.Slash, '/', 191, 'VK_OEM_2', '/', 'OEM_2'], + [1, ScanCode.CapsLock, 'CapsLock', KeyCode.CapsLock, 'CapsLock', 20, 'VK_CAPITAL', empty, empty], + [1, ScanCode.F1, 'F1', KeyCode.F1, 'F1', 112, 'VK_F1', empty, empty], + [1, ScanCode.F2, 'F2', KeyCode.F2, 'F2', 113, 'VK_F2', empty, empty], + [1, ScanCode.F3, 'F3', KeyCode.F3, 'F3', 114, 'VK_F3', empty, empty], + [1, ScanCode.F4, 'F4', KeyCode.F4, 'F4', 115, 'VK_F4', empty, empty], + [1, ScanCode.F5, 'F5', KeyCode.F5, 'F5', 116, 'VK_F5', empty, empty], + [1, ScanCode.F6, 'F6', KeyCode.F6, 'F6', 117, 'VK_F6', empty, empty], + [1, ScanCode.F7, 'F7', KeyCode.F7, 'F7', 118, 'VK_F7', empty, empty], + [1, ScanCode.F8, 'F8', KeyCode.F8, 'F8', 119, 'VK_F8', empty, empty], + [1, ScanCode.F9, 'F9', KeyCode.F9, 'F9', 120, 'VK_F9', empty, empty], + [1, ScanCode.F10, 'F10', KeyCode.F10, 'F10', 121, 'VK_F10', empty, empty], + [1, ScanCode.F11, 'F11', KeyCode.F11, 'F11', 122, 'VK_F11', empty, empty], + [1, ScanCode.F12, 'F12', KeyCode.F12, 'F12', 123, 'VK_F12', empty, empty], + [1, ScanCode.PrintScreen, 'PrintScreen', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.ScrollLock, 'ScrollLock', KeyCode.ScrollLock, 'ScrollLock', 145, 'VK_SCROLL', empty, empty], + [1, ScanCode.Pause, 'Pause', KeyCode.PauseBreak, 'PauseBreak', 19, 'VK_PAUSE', empty, empty], + [1, ScanCode.Insert, 'Insert', KeyCode.Insert, 'Insert', 45, 'VK_INSERT', empty, empty], + [1, ScanCode.Home, 'Home', KeyCode.Home, 'Home', 36, 'VK_HOME', empty, empty], + [1, ScanCode.PageUp, 'PageUp', KeyCode.PageUp, 'PageUp', 33, 'VK_PRIOR', empty, empty], + [1, ScanCode.Delete, 'Delete', KeyCode.Delete, 'Delete', 46, 'VK_DELETE', empty, empty], + [1, ScanCode.End, 'End', KeyCode.End, 'End', 35, 'VK_END', empty, empty], + [1, ScanCode.PageDown, 'PageDown', KeyCode.PageDown, 'PageDown', 34, 'VK_NEXT', empty, empty], + [1, ScanCode.ArrowRight, 'ArrowRight', KeyCode.RightArrow, 'RightArrow', 39, 'VK_RIGHT', 'Right', empty], + [1, ScanCode.ArrowLeft, 'ArrowLeft', KeyCode.LeftArrow, 'LeftArrow', 37, 'VK_LEFT', 'Left', empty], + [1, ScanCode.ArrowDown, 'ArrowDown', KeyCode.DownArrow, 'DownArrow', 40, 'VK_DOWN', 'Down', empty], + [1, ScanCode.ArrowUp, 'ArrowUp', KeyCode.UpArrow, 'UpArrow', 38, 'VK_UP', 'Up', empty], + [1, ScanCode.NumLock, 'NumLock', KeyCode.NumLock, 'NumLock', 144, 'VK_NUMLOCK', empty, empty], + [1, ScanCode.NumpadDivide, 'NumpadDivide', KeyCode.NumpadDivide, 'NumPad_Divide', 111, 'VK_DIVIDE', empty, empty], + [1, ScanCode.NumpadMultiply, 'NumpadMultiply', KeyCode.NumpadMultiply, 'NumPad_Multiply', 106, 'VK_MULTIPLY', empty, empty], + [1, ScanCode.NumpadSubtract, 'NumpadSubtract', KeyCode.NumpadSubtract, 'NumPad_Subtract', 109, 'VK_SUBTRACT', empty, empty], + [1, ScanCode.NumpadAdd, 'NumpadAdd', KeyCode.NumpadAdd, 'NumPad_Add', 107, 'VK_ADD', empty, empty], + [1, ScanCode.NumpadEnter, 'NumpadEnter', KeyCode.Enter, empty, 0, empty, empty, empty], + [1, ScanCode.Numpad1, 'Numpad1', KeyCode.Numpad1, 'NumPad1', 97, 'VK_NUMPAD1', empty, empty], + [1, ScanCode.Numpad2, 'Numpad2', KeyCode.Numpad2, 'NumPad2', 98, 'VK_NUMPAD2', empty, empty], + [1, ScanCode.Numpad3, 'Numpad3', KeyCode.Numpad3, 'NumPad3', 99, 'VK_NUMPAD3', empty, empty], + [1, ScanCode.Numpad4, 'Numpad4', KeyCode.Numpad4, 'NumPad4', 100, 'VK_NUMPAD4', empty, empty], + [1, ScanCode.Numpad5, 'Numpad5', KeyCode.Numpad5, 'NumPad5', 101, 'VK_NUMPAD5', empty, empty], + [1, ScanCode.Numpad6, 'Numpad6', KeyCode.Numpad6, 'NumPad6', 102, 'VK_NUMPAD6', empty, empty], + [1, ScanCode.Numpad7, 'Numpad7', KeyCode.Numpad7, 'NumPad7', 103, 'VK_NUMPAD7', empty, empty], + [1, ScanCode.Numpad8, 'Numpad8', KeyCode.Numpad8, 'NumPad8', 104, 'VK_NUMPAD8', empty, empty], + [1, ScanCode.Numpad9, 'Numpad9', KeyCode.Numpad9, 'NumPad9', 105, 'VK_NUMPAD9', empty, empty], + [1, ScanCode.Numpad0, 'Numpad0', KeyCode.Numpad0, 'NumPad0', 96, 'VK_NUMPAD0', empty, empty], + [1, ScanCode.NumpadDecimal, 'NumpadDecimal', KeyCode.NumpadDecimal, 'NumPad_Decimal', 110, 'VK_DECIMAL', empty, empty], + [0, ScanCode.IntlBackslash, 'IntlBackslash', KeyCode.IntlBackslash, 'OEM_102', 226, 'VK_OEM_102', empty, empty], + [1, ScanCode.ContextMenu, 'ContextMenu', KeyCode.ContextMenu, 'ContextMenu', 93, empty, empty, empty], + [1, ScanCode.Power, 'Power', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadEqual, 'NumpadEqual', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.F13, 'F13', KeyCode.F13, 'F13', 124, 'VK_F13', empty, empty], + [1, ScanCode.F14, 'F14', KeyCode.F14, 'F14', 125, 'VK_F14', empty, empty], + [1, ScanCode.F15, 'F15', KeyCode.F15, 'F15', 126, 'VK_F15', empty, empty], + [1, ScanCode.F16, 'F16', KeyCode.F16, 'F16', 127, 'VK_F16', empty, empty], + [1, ScanCode.F17, 'F17', KeyCode.F17, 'F17', 128, 'VK_F17', empty, empty], + [1, ScanCode.F18, 'F18', KeyCode.F18, 'F18', 129, 'VK_F18', empty, empty], + [1, ScanCode.F19, 'F19', KeyCode.F19, 'F19', 130, 'VK_F19', empty, empty], + [1, ScanCode.F20, 'F20', KeyCode.F20, 'F20', 131, 'VK_F20', empty, empty], + [1, ScanCode.F21, 'F21', KeyCode.F21, 'F21', 132, 'VK_F21', empty, empty], + [1, ScanCode.F22, 'F22', KeyCode.F22, 'F22', 133, 'VK_F22', empty, empty], + [1, ScanCode.F23, 'F23', KeyCode.F23, 'F23', 134, 'VK_F23', empty, empty], + [1, ScanCode.F24, 'F24', KeyCode.F24, 'F24', 135, 'VK_F24', empty, empty], + [1, ScanCode.Open, 'Open', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Help, 'Help', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Select, 'Select', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Again, 'Again', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Undo, 'Undo', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Cut, 'Cut', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Copy, 'Copy', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Paste, 'Paste', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Find, 'Find', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.AudioVolumeMute, 'AudioVolumeMute', KeyCode.AudioVolumeMute, 'AudioVolumeMute', 173, 'VK_VOLUME_MUTE', empty, empty], + [1, ScanCode.AudioVolumeUp, 'AudioVolumeUp', KeyCode.AudioVolumeUp, 'AudioVolumeUp', 175, 'VK_VOLUME_UP', empty, empty], + [1, ScanCode.AudioVolumeDown, 'AudioVolumeDown', KeyCode.AudioVolumeDown, 'AudioVolumeDown', 174, 'VK_VOLUME_DOWN', empty, empty], + [1, ScanCode.NumpadComma, 'NumpadComma', KeyCode.NUMPAD_SEPARATOR, 'NumPad_Separator', 108, 'VK_SEPARATOR', empty, empty], + [0, ScanCode.IntlRo, 'IntlRo', KeyCode.ABNT_C1, 'ABNT_C1', 193, 'VK_ABNT_C1', empty, empty], + [1, ScanCode.KanaMode, 'KanaMode', KeyCode.Unknown, empty, 0, empty, empty, empty], + [0, ScanCode.IntlYen, 'IntlYen', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Convert, 'Convert', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NonConvert, 'NonConvert', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Lang1, 'Lang1', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Lang2, 'Lang2', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Lang3, 'Lang3', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Lang4, 'Lang4', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Lang5, 'Lang5', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Abort, 'Abort', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.Props, 'Props', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadParenLeft, 'NumpadParenLeft', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadParenRight, 'NumpadParenRight', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadBackspace, 'NumpadBackspace', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadMemoryStore, 'NumpadMemoryStore', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadMemoryRecall, 'NumpadMemoryRecall', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadMemoryClear, 'NumpadMemoryClear', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadMemoryAdd, 'NumpadMemoryAdd', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadMemorySubtract, 'NumpadMemorySubtract', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.NumpadClear, 'NumpadClear', KeyCode.Clear, 'Clear', 12, 'VK_CLEAR', empty, empty], + [1, ScanCode.NumpadClearEntry, 'NumpadClearEntry', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.None, empty, KeyCode.Ctrl, 'Ctrl', 17, 'VK_CONTROL', empty, empty], + [1, ScanCode.None, empty, KeyCode.Shift, 'Shift', 16, 'VK_SHIFT', empty, empty], + [1, ScanCode.None, empty, KeyCode.Alt, 'Alt', 18, 'VK_MENU', empty, empty], + [1, ScanCode.None, empty, KeyCode.Meta, 'Meta', 91, 'VK_COMMAND', empty, empty], + [1, ScanCode.ControlLeft, 'ControlLeft', KeyCode.Ctrl, empty, 0, 'VK_LCONTROL', empty, empty], + [1, ScanCode.ShiftLeft, 'ShiftLeft', KeyCode.Shift, empty, 0, 'VK_LSHIFT', empty, empty], + [1, ScanCode.AltLeft, 'AltLeft', KeyCode.Alt, empty, 0, 'VK_LMENU', empty, empty], + [1, ScanCode.MetaLeft, 'MetaLeft', KeyCode.Meta, empty, 0, 'VK_LWIN', empty, empty], + [1, ScanCode.ControlRight, 'ControlRight', KeyCode.Ctrl, empty, 0, 'VK_RCONTROL', empty, empty], + [1, ScanCode.ShiftRight, 'ShiftRight', KeyCode.Shift, empty, 0, 'VK_RSHIFT', empty, empty], + [1, ScanCode.AltRight, 'AltRight', KeyCode.Alt, empty, 0, 'VK_RMENU', empty, empty], + [1, ScanCode.MetaRight, 'MetaRight', KeyCode.Meta, empty, 0, 'VK_RWIN', empty, empty], + [1, ScanCode.BrightnessUp, 'BrightnessUp', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.BrightnessDown, 'BrightnessDown', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MediaPlay, 'MediaPlay', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MediaRecord, 'MediaRecord', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MediaFastForward, 'MediaFastForward', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MediaRewind, 'MediaRewind', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MediaTrackNext, 'MediaTrackNext', KeyCode.MediaTrackNext, 'MediaTrackNext', 176, 'VK_MEDIA_NEXT_TRACK', empty, empty], + [1, ScanCode.MediaTrackPrevious, 'MediaTrackPrevious', KeyCode.MediaTrackPrevious, 'MediaTrackPrevious', 177, 'VK_MEDIA_PREV_TRACK', empty, empty], + [1, ScanCode.MediaStop, 'MediaStop', KeyCode.MediaStop, 'MediaStop', 178, 'VK_MEDIA_STOP', empty, empty], + [1, ScanCode.Eject, 'Eject', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MediaPlayPause, 'MediaPlayPause', KeyCode.MediaPlayPause, 'MediaPlayPause', 179, 'VK_MEDIA_PLAY_PAUSE', empty, empty], + [1, ScanCode.MediaSelect, 'MediaSelect', KeyCode.LaunchMediaPlayer, 'LaunchMediaPlayer', 181, 'VK_MEDIA_LAUNCH_MEDIA_SELECT', empty, empty], + [1, ScanCode.LaunchMail, 'LaunchMail', KeyCode.LaunchMail, 'LaunchMail', 180, 'VK_MEDIA_LAUNCH_MAIL', empty, empty], + [1, ScanCode.LaunchApp2, 'LaunchApp2', KeyCode.LaunchApp2, 'LaunchApp2', 183, 'VK_MEDIA_LAUNCH_APP2', empty, empty], + [1, ScanCode.LaunchApp1, 'LaunchApp1', KeyCode.Unknown, empty, 0, 'VK_MEDIA_LAUNCH_APP1', empty, empty], + [1, ScanCode.SelectTask, 'SelectTask', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.LaunchScreenSaver, 'LaunchScreenSaver', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.BrowserSearch, 'BrowserSearch', KeyCode.BrowserSearch, 'BrowserSearch', 170, 'VK_BROWSER_SEARCH', empty, empty], + [1, ScanCode.BrowserHome, 'BrowserHome', KeyCode.BrowserHome, 'BrowserHome', 172, 'VK_BROWSER_HOME', empty, empty], + [1, ScanCode.BrowserBack, 'BrowserBack', KeyCode.BrowserBack, 'BrowserBack', 166, 'VK_BROWSER_BACK', empty, empty], + [1, ScanCode.BrowserForward, 'BrowserForward', KeyCode.BrowserForward, 'BrowserForward', 167, 'VK_BROWSER_FORWARD', empty, empty], + [1, ScanCode.BrowserStop, 'BrowserStop', KeyCode.Unknown, empty, 0, 'VK_BROWSER_STOP', empty, empty], + [1, ScanCode.BrowserRefresh, 'BrowserRefresh', KeyCode.Unknown, empty, 0, 'VK_BROWSER_REFRESH', empty, empty], + [1, ScanCode.BrowserFavorites, 'BrowserFavorites', KeyCode.Unknown, empty, 0, 'VK_BROWSER_FAVORITES', empty, empty], + [1, ScanCode.ZoomToggle, 'ZoomToggle', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MailReply, 'MailReply', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MailForward, 'MailForward', KeyCode.Unknown, empty, 0, empty, empty, empty], + [1, ScanCode.MailSend, 'MailSend', KeyCode.Unknown, empty, 0, empty, empty, empty], // See https://lists.w3.org/Archives/Public/www-dom/2010JulSep/att-0182/keyCode-spec.html // If an Input Method Editor is processing key input and the event is keydown, return 229. - [109, 1, ScanCode.None, empty, KeyCode.KEY_IN_COMPOSITION, 'KeyInComposition', 229, empty, empty, empty], - [111, 1, ScanCode.None, empty, KeyCode.ABNT_C2, 'ABNT_C2', 194, 'VK_ABNT_C2', empty, empty], - [91, 1, ScanCode.None, empty, KeyCode.OEM_8, 'OEM_8', 223, 'VK_OEM_8', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_KANA', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_HANGUL', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_JUNJA', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_FINAL', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_HANJA', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_KANJI', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_CONVERT', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_NONCONVERT', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_ACCEPT', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_MODECHANGE', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_SELECT', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PRINT', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_EXECUTE', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_SNAPSHOT', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_HELP', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_APPS', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PROCESSKEY', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PACKET', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_DBE_SBCSCHAR', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_DBE_DBCSCHAR', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_ATTN', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_CRSEL', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_EXSEL', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_EREOF', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PLAY', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_ZOOM', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_NONAME', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PA1', empty, empty], - [0, 1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_OEM_CLEAR', empty, empty], + [1, ScanCode.None, empty, KeyCode.KEY_IN_COMPOSITION, 'KeyInComposition', 229, empty, empty, empty], + [1, ScanCode.None, empty, KeyCode.ABNT_C2, 'ABNT_C2', 194, 'VK_ABNT_C2', empty, empty], + [1, ScanCode.None, empty, KeyCode.OEM_8, 'OEM_8', 223, 'VK_OEM_8', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_KANA', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_HANGUL', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_JUNJA', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_FINAL', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_HANJA', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_KANJI', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_CONVERT', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_NONCONVERT', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_ACCEPT', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_MODECHANGE', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_SELECT', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PRINT', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_EXECUTE', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_SNAPSHOT', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_HELP', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_APPS', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PROCESSKEY', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PACKET', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_DBE_SBCSCHAR', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_DBE_DBCSCHAR', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_ATTN', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_CRSEL', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_EXSEL', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_EREOF', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PLAY', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_ZOOM', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_NONAME', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_PA1', empty, empty], + [1, ScanCode.None, empty, KeyCode.Unknown, empty, 0, 'VK_OEM_CLEAR', empty, empty], ]; const seenKeyCode: boolean[] = []; const seenScanCode: boolean[] = []; for (const mapping of mappings) { - const [_keyCodeOrd, immutable, scanCode, scanCodeStr, keyCode, keyCodeStr, eventKeyCode, vkey, usUserSettingsLabel, generalUserSettingsLabel] = mapping; + const [immutable, scanCode, scanCodeStr, keyCode, keyCodeStr, eventKeyCode, vkey, usUserSettingsLabel, generalUserSettingsLabel] = mapping; if (!seenScanCode[scanCode]) { seenScanCode[scanCode] = true; scanCodeIntToStr[scanCode] = scanCodeStr; diff --git a/src/vs/base/common/keybindingLabels.ts b/src/vs/base/common/keybindingLabels.ts index f0694714fc..e69dea5e36 100644 --- a/src/vs/base/common/keybindingLabels.ts +++ b/src/vs/base/common/keybindingLabels.ts @@ -30,20 +30,20 @@ export class ModifierLabelProvider { this.modifierLabels[OperatingSystem.Linux] = linux; } - public toLabel<T extends Modifiers>(OS: OperatingSystem, parts: T[], keyLabelProvider: KeyLabelProvider<T>): string | null { - if (parts.length === 0) { + public toLabel<T extends Modifiers>(OS: OperatingSystem, chords: readonly T[], keyLabelProvider: KeyLabelProvider<T>): string | null { + if (chords.length === 0) { return null; } const result: string[] = []; - for (let i = 0, len = parts.length; i < len; i++) { - const part = parts[i]; - const keyLabel = keyLabelProvider(part); + for (let i = 0, len = chords.length; i < len; i++) { + const chord = chords[i]; + const keyLabel = keyLabelProvider(chord); if (keyLabel === null) { // this keybinding cannot be expressed... return null; } - result[i] = _simpleAsString(part, keyLabel, this.modifierLabels[OS]); + result[i] = _simpleAsString(chord, keyLabel, this.modifierLabels[OS]); } return result.join(' '); } diff --git a/src/vs/base/common/keybindingParser.ts b/src/vs/base/common/keybindingParser.ts index bfdf54c477..4e0e0f45b7 100644 --- a/src/vs/base/common/keybindingParser.ts +++ b/src/vs/base/common/keybindingParser.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KeyCodeUtils, ScanCodeUtils } from 'vs/base/common/keyCodes'; -import { ChordKeybinding, Keybinding, SimpleKeybinding, ScanCodeBinding } from 'vs/base/common/keybindings'; -import { OperatingSystem } from 'vs/base/common/platform'; +import { KeyCodeChord, ScanCodeChord, Keybinding, Chord } from 'vs/base/common/keybindings'; export class KeybindingParser { @@ -74,51 +73,30 @@ export class KeybindingParser { }; } - private static parseSimpleKeybinding(input: string): [SimpleKeybinding, string] { - const mods = this._readModifiers(input); - const keyCode = KeyCodeUtils.fromUserSettings(mods.key); - return [new SimpleKeybinding(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains]; - } - - public static parseKeybinding(input: string, OS: OperatingSystem): Keybinding | null { - if (!input) { - return null; - } - - const parts: SimpleKeybinding[] = []; - let part: SimpleKeybinding; - - do { - [part, input] = this.parseSimpleKeybinding(input); - parts.push(part); - } while (input.length > 0); - return new ChordKeybinding(parts); - } - - private static parseSimpleUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding, string] { + private static parseChord(input: string): [Chord, string] { const mods = this._readModifiers(input); const scanCodeMatch = mods.key.match(/^\[([^\]]+)\]$/); if (scanCodeMatch) { const strScanCode = scanCodeMatch[1]; const scanCode = ScanCodeUtils.lowerCaseToEnum(strScanCode); - return [new ScanCodeBinding(mods.ctrl, mods.shift, mods.alt, mods.meta, scanCode), mods.remains]; + return [new ScanCodeChord(mods.ctrl, mods.shift, mods.alt, mods.meta, scanCode), mods.remains]; } const keyCode = KeyCodeUtils.fromUserSettings(mods.key); - return [new SimpleKeybinding(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains]; + return [new KeyCodeChord(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains]; } - static parseUserBinding(input: string): (SimpleKeybinding | ScanCodeBinding)[] { + static parseKeybinding(input: string): Keybinding | null { if (!input) { - return []; + return null; } - const parts: (SimpleKeybinding | ScanCodeBinding)[] = []; - let part: SimpleKeybinding | ScanCodeBinding; + const chords: Chord[] = []; + let chord: Chord; while (input.length > 0) { - [part, input] = this.parseSimpleUserBinding(input); - parts.push(part); + [chord, input] = this.parseChord(input); + chords.push(chord); } - return parts; + return (chords.length > 0 ? new Keybinding(chords) : null); } } diff --git a/src/vs/base/common/keybindings.ts b/src/vs/base/common/keybindings.ts index a518612539..ad8055f5d4 100644 --- a/src/vs/base/common/keybindings.ts +++ b/src/vs/base/common/keybindings.ts @@ -28,22 +28,30 @@ const enum BinaryKeybindingsMask { KeyCode = 0x000000FF } -export function createKeybinding(keybinding: number, OS: OperatingSystem): Keybinding | null { - if (keybinding === 0) { - return null; +export function decodeKeybinding(keybinding: number | number[], OS: OperatingSystem): Keybinding | null { + if (typeof keybinding === 'number') { + if (keybinding === 0) { + return null; + } + const firstChord = (keybinding & 0x0000FFFF) >>> 0; + const secondChord = (keybinding & 0xFFFF0000) >>> 16; + if (secondChord !== 0) { + return new Keybinding([ + createSimpleKeybinding(firstChord, OS), + createSimpleKeybinding(secondChord, OS) + ]); + } + return new Keybinding([createSimpleKeybinding(firstChord, OS)]); + } else { + const chords = []; + for (let i = 0; i < keybinding.length; i++) { + chords.push(createSimpleKeybinding(keybinding[i], OS)); + } + return new Keybinding(chords); } - const firstPart = (keybinding & 0x0000FFFF) >>> 0; - const chordPart = (keybinding & 0xFFFF0000) >>> 16; - if (chordPart !== 0) { - return new ChordKeybinding([ - createSimpleKeybinding(firstPart, OS), - createSimpleKeybinding(chordPart, OS) - ]); - } - return new ChordKeybinding([createSimpleKeybinding(firstPart, OS)]); } -export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): SimpleKeybinding { +export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): KeyCodeChord { const ctrlCmd = (keybinding & BinaryKeybindingsMask.CtrlCmd ? true : false); const winCtrl = (keybinding & BinaryKeybindingsMask.WinCtrl ? true : false); @@ -54,7 +62,7 @@ export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): const metaKey = (OS === OperatingSystem.Macintosh ? ctrlCmd : winCtrl); const keyCode = (keybinding & BinaryKeybindingsMask.KeyCode); - return new SimpleKeybinding(ctrlKey, shiftKey, altKey, metaKey, keyCode); + return new KeyCodeChord(ctrlKey, shiftKey, altKey, metaKey, keyCode); } export interface Modifiers { @@ -64,28 +72,24 @@ export interface Modifiers { readonly metaKey: boolean; } -export interface IBaseKeybinding extends Modifiers { - isDuplicateModifierCase(): boolean; -} +/** + * Represents a chord which uses the `keyCode` field of keyboard events. + * A chord is a combination of keys pressed simultaneously. + */ +export class KeyCodeChord implements Modifiers { -export class SimpleKeybinding implements IBaseKeybinding { - public readonly ctrlKey: boolean; - public readonly shiftKey: boolean; - public readonly altKey: boolean; - public readonly metaKey: boolean; - public readonly keyCode: KeyCode; + constructor( + public readonly ctrlKey: boolean, + public readonly shiftKey: boolean, + public readonly altKey: boolean, + public readonly metaKey: boolean, + public readonly keyCode: KeyCode + ) { } - constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, keyCode: KeyCode) { - this.ctrlKey = ctrlKey; - this.shiftKey = shiftKey; - this.altKey = altKey; - this.metaKey = metaKey; - this.keyCode = keyCode; - } - - public equals(other: SimpleKeybinding): boolean { + public equals(other: Chord): boolean { return ( - this.ctrlKey === other.ctrlKey + other instanceof KeyCodeChord + && this.ctrlKey === other.ctrlKey && this.shiftKey === other.shiftKey && this.altKey === other.altKey && this.metaKey === other.metaKey @@ -98,7 +102,7 @@ export class SimpleKeybinding implements IBaseKeybinding { const shift = this.shiftKey ? '1' : '0'; const alt = this.altKey ? '1' : '0'; const meta = this.metaKey ? '1' : '0'; - return `${ctrl}${shift}${alt}${meta}${this.keyCode}`; + return `K${ctrl}${shift}${alt}${meta}${this.keyCode}`; } public isModifierKey(): boolean { @@ -111,8 +115,8 @@ export class SimpleKeybinding implements IBaseKeybinding { ); } - public toChord(): ChordKeybinding { - return new ChordKeybinding([this]); + public toKeybinding(): Keybinding { + return new Keybinding([this]); } /** @@ -128,63 +132,24 @@ export class SimpleKeybinding implements IBaseKeybinding { } } -export class ChordKeybinding { - public readonly parts: SimpleKeybinding[]; +/** + * Represents a chord which uses the `code` field of keyboard events. + * A chord is a combination of keys pressed simultaneously. + */ +export class ScanCodeChord implements Modifiers { - constructor(parts: SimpleKeybinding[]) { - if (parts.length === 0) { - throw illegalArgument(`parts`); - } - this.parts = parts; - } + constructor( + public readonly ctrlKey: boolean, + public readonly shiftKey: boolean, + public readonly altKey: boolean, + public readonly metaKey: boolean, + public readonly scanCode: ScanCode + ) { } - public getHashCode(): string { - let result = ''; - for (let i = 0, len = this.parts.length; i < len; i++) { - if (i !== 0) { - result += ';'; - } - result += this.parts[i].getHashCode(); - } - return result; - } - - public equals(other: ChordKeybinding | null): boolean { - if (other === null) { - return false; - } - if (this.parts.length !== other.parts.length) { - return false; - } - for (let i = 0; i < this.parts.length; i++) { - if (!this.parts[i].equals(other.parts[i])) { - return false; - } - } - return true; - } -} - -export type Keybinding = ChordKeybinding; - -export class ScanCodeBinding implements IBaseKeybinding { - public readonly ctrlKey: boolean; - public readonly shiftKey: boolean; - public readonly altKey: boolean; - public readonly metaKey: boolean; - public readonly scanCode: ScanCode; - - constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, scanCode: ScanCode) { - this.ctrlKey = ctrlKey; - this.shiftKey = shiftKey; - this.altKey = altKey; - this.metaKey = metaKey; - this.scanCode = scanCode; - } - - public equals(other: ScanCodeBinding): boolean { + public equals(other: Chord): boolean { return ( - this.ctrlKey === other.ctrlKey + other instanceof ScanCodeChord + && this.ctrlKey === other.ctrlKey && this.shiftKey === other.shiftKey && this.altKey === other.altKey && this.metaKey === other.metaKey @@ -192,6 +157,14 @@ export class ScanCodeBinding implements IBaseKeybinding { ); } + public getHashCode(): string { + const ctrl = this.ctrlKey ? '1' : '0'; + const shift = this.shiftKey ? '1' : '0'; + const alt = this.altKey ? '1' : '0'; + const meta = this.metaKey ? '1' : '0'; + return `S${ctrl}${shift}${alt}${meta}${this.scanCode}`; + } + /** * Does this keybinding refer to the key code of a modifier and it also has the modifier flag? */ @@ -205,29 +178,64 @@ export class ScanCodeBinding implements IBaseKeybinding { } } -export class ResolvedKeybindingPart { - readonly ctrlKey: boolean; - readonly shiftKey: boolean; - readonly altKey: boolean; - readonly metaKey: boolean; +export type Chord = KeyCodeChord | ScanCodeChord; - readonly keyLabel: string | null; - readonly keyAriaLabel: string | null; +/** + * A keybinding is a sequence of chords. + */ +export class Keybinding { - constructor(ctrlKey: boolean, shiftKey: boolean, altKey: boolean, metaKey: boolean, kbLabel: string | null, kbAriaLabel: string | null) { - this.ctrlKey = ctrlKey; - this.shiftKey = shiftKey; - this.altKey = altKey; - this.metaKey = metaKey; - this.keyLabel = kbLabel; - this.keyAriaLabel = kbAriaLabel; + public readonly chords: Chord[]; + + constructor(chords: Chord[]) { + if (chords.length === 0) { + throw illegalArgument(`chords`); + } + this.chords = chords; + } + + public getHashCode(): string { + let result = ''; + for (let i = 0, len = this.chords.length; i < len; i++) { + if (i !== 0) { + result += ';'; + } + result += this.chords[i].getHashCode(); + } + return result; + } + + public equals(other: Keybinding | null): boolean { + if (other === null) { + return false; + } + if (this.chords.length !== other.chords.length) { + return false; + } + for (let i = 0; i < this.chords.length; i++) { + if (!this.chords[i].equals(other.chords[i])) { + return false; + } + } + return true; } } -export type KeybindingModifier = 'ctrl' | 'shift' | 'alt' | 'meta'; +export class ResolvedChord { + constructor( + public readonly ctrlKey: boolean, + public readonly shiftKey: boolean, + public readonly altKey: boolean, + public readonly metaKey: boolean, + public readonly keyLabel: string | null, + public readonly keyAriaLabel: string | null + ) { } +} + +export type SingleModifierChord = 'ctrl' | 'shift' | 'alt' | 'meta'; /** - * A resolved keybinding. Can be a simple keybinding or a chord keybinding. + * A resolved keybinding. Consists of one or multiple chords. */ export abstract class ResolvedKeybinding { /** @@ -251,31 +259,26 @@ export abstract class ResolvedKeybinding { * Is the user settings label reflecting the label? */ public abstract isWYSIWYG(): boolean; - /** - * Is the binding a chord? + * Does the keybinding consist of more than one chord? */ - public abstract isChord(): boolean; - + public abstract hasMultipleChords(): boolean; /** - * Returns the parts that comprise of the keybinding. - * Simple keybindings return one element. + * Returns the chords that comprise of the keybinding. */ - public abstract getParts(): ResolvedKeybindingPart[]; - + public abstract getChords(): ResolvedChord[]; /** - * Returns the parts that should be used for dispatching. - * Returns null for parts consisting of only modifier keys + * Returns the chords as strings useful for dispatching. + * Returns null for modifier only chords. * @example keybinding "Shift" -> null * @example keybinding ("D" with shift == true) -> "shift+D" */ - public abstract getDispatchParts(): (string | null)[]; - + public abstract getDispatchChords(): (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. + * Returns the modifier only chords as strings useful for dispatching. + * Returns null for chords that contain more than one modifier or a regular key. * @example keybinding "Shift" -> "shift" * @example keybinding ("D" with shift == true") -> null */ - public abstract getSingleModifierDispatchParts(): (KeybindingModifier | null)[]; + public abstract getSingleModifierDispatchChords(): (SingleModifierChord | null)[]; } diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index dd35571a25..bb0ac4220d 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -436,9 +436,21 @@ export function unmnemonicLabel(label: string): string { } /** - * Splits a path in name and parent path, supporting both '/' and '\' + * Splits a recent label in name and parent path, supporting both '/' and '\' and workspace suffixes */ -export function splitName(fullPath: string): { name: string; parentPath: string } { +export function splitRecentLabel(recentLabel: string) { + if (recentLabel.endsWith(']')) { + // label with workspace suffix + const lastIndexOfSquareBracket = recentLabel.lastIndexOf(' [', recentLabel.length - 2); + if (lastIndexOfSquareBracket !== -1) { + const split = splitName(recentLabel.substring(0, lastIndexOfSquareBracket)); + return { name: split.name, parentPath: split.parentPath + recentLabel.substring(lastIndexOfSquareBracket) }; + } + } + return splitName(recentLabel); +} + +function splitName(fullPath: string): { name: string; parentPath: string } { const p = fullPath.indexOf('/') !== -1 ? posix : win32; const name = p.basename(fullPath); const parentPath = p.dirname(fullPath); diff --git a/src/vs/base/common/lazy.ts b/src/vs/base/common/lazy.ts index d11924d963..5299d031cc 100644 --- a/src/vs/base/common/lazy.ts +++ b/src/vs/base/common/lazy.ts @@ -3,20 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/** - * A value that is resolved synchronously when it is first needed. - */ -export interface Lazy<T> { - - hasValue(): boolean; - - - getValue(): T; - - - map<R>(f: (x: T) => R): Lazy<R>; -} - export class Lazy<T> { private _didRun: boolean = false; @@ -30,7 +16,7 @@ export class Lazy<T> { /** * True if the lazy value has been resolved. */ - hasValue() { return this._didRun; } + get hasValue() { return this._didRun; } /** * Get the wrapped value. @@ -38,7 +24,7 @@ export class Lazy<T> { * This will force evaluation of the lazy value if it has not been resolved yet. Lazy values are only * resolved once. `getValue` will re-throw exceptions that are hit while resolving the value */ - getValue(): T { + get value(): T { if (!this._didRun) { try { this._value = this.executor(); @@ -58,13 +44,4 @@ export class Lazy<T> { * Get the wrapped value without forcing evaluation. */ get rawValue(): T | undefined { return this._value; } - - /** - * Create a new lazy value that is the result of applying `f` to the wrapped value. - * - * This does not force the evaluation of the current lazy value. - */ - map<R>(f: (x: T) => R): Lazy<R> { - return new Lazy<R>(() => f(this.getValue())); - } } diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 6bb098754a..4ab00ba3ba 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -6,6 +6,8 @@ import { once } from 'vs/base/common/functional'; import { Iterable } from 'vs/base/common/iterator'; +// #region Disposable Tracking + /** * Enables logging of potentially leaked disposables. * @@ -108,28 +110,37 @@ export function markAsSingleton<T extends IDisposable>(singleton: T): T { return singleton; } -export class MultiDisposeError extends Error { - constructor( - public readonly errors: any[] - ) { - super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`); - } -} +// #endregion +/** + * An object that performs a cleanup operation when `.dispose()` is called. + * + * Some examples of how disposables are used: + * + * - An event listener that removes itself when `.dispose()` is called. + * - A resource such as a file system watcher that cleans up the resource when `.dispose()` is called. + * - The return value from registering a provider. When `.dispose()` is called, the provider is unregistered. + */ export interface IDisposable { dispose(): void; } +/** + * Check if `thing` is {@link IDisposable disposable}. + */ export function isDisposable<E extends object>(thing: E): thing is E & IDisposable { return typeof (<IDisposable>thing).dispose === 'function' && (<IDisposable>thing).dispose.length === 0; } +/** + * Disposes of the value(s) passed in. + */ export function dispose<T extends IDisposable>(disposable: T): T; export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined; -export function dispose<T extends IDisposable, A extends IterableIterator<T> = IterableIterator<T>>(disposables: IterableIterator<T>): A; +export function dispose<T extends IDisposable, A extends Iterable<T> = Iterable<T>>(disposables: A): A; export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>; export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>; -export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | undefined): any { +export function dispose<T extends IDisposable>(arg: T | Iterable<T> | undefined): any { if (Iterable.is(arg)) { const errors: any[] = []; @@ -146,7 +157,7 @@ export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | un if (errors.length === 1) { throw errors[0]; } else if (errors.length > 1) { - throw new MultiDisposeError(errors); + throw new AggregateError(errors, 'Encountered errors while disposing of store'); } return Array.isArray(arg) ? [] : arg; @@ -156,13 +167,27 @@ export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | un } } +export function disposeIfDisposable<T extends IDisposable | object>(disposables: Array<T>): Array<T> { + for (const d of disposables) { + if (isDisposable(d)) { + d.dispose(); + } + } + return []; +} +/** + * Combine multiple disposable values into a single {@link IDisposable}. + */ export function combinedDisposable(...disposables: IDisposable[]): IDisposable { const parent = toDisposable(() => dispose(disposables)); setParentOfDisposables(disposables, parent); return parent; } +/** + * Turn a function that implements dispose into an {@link IDisposable}. + */ export function toDisposable(fn: () => void): IDisposable { const self = trackDisposable({ dispose: once(() => { @@ -173,11 +198,18 @@ export function toDisposable(fn: () => void): IDisposable { return self; } +/** + * Manages a collection of disposable values. + * + * This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an + * `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a + * store that has already been disposed of. + */ export class DisposableStore implements IDisposable { static DISABLE_DISPOSED_WARNING = false; - private _toDispose = new Set<IDisposable>(); + private readonly _toDispose = new Set<IDisposable>(); private _isDisposed = false; constructor() { @@ -200,7 +232,7 @@ export class DisposableStore implements IDisposable { } /** - * Returns `true` if this object has been disposed + * @return `true` if this object has been disposed of. */ public get isDisposed(): boolean { return this._isDisposed; @@ -210,13 +242,20 @@ export class DisposableStore implements IDisposable { * Dispose of all registered disposables but do not mark this object as disposed. */ public clear(): void { + if (this._toDispose.size === 0) { + return; + } + try { - dispose(this._toDispose.values()); + dispose(this._toDispose); } finally { this._toDispose.clear(); } } + /** + * Add a new {@link IDisposable disposable} to the collection. + */ public add<T extends IDisposable>(o: T): T { if (!o) { return o; @@ -238,8 +277,18 @@ export class DisposableStore implements IDisposable { } } +/** + * Abstract base class for a {@link IDisposable disposable} object. + * + * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of. + */ export abstract class Disposable implements IDisposable { + /** + * A disposable that does nothing when it is disposed of. + * + * TODO: This should not be a static property. + */ static readonly None = Object.freeze<IDisposable>({ dispose() { } }); protected readonly _store = new DisposableStore(); @@ -255,6 +304,9 @@ export abstract class Disposable implements IDisposable { this._store.dispose(); } + /** + * Adds `o` to the collection of disposables managed by this object. + */ protected _register<T extends IDisposable>(o: T): T { if ((o as unknown as Disposable) === this) { throw new Error('Cannot register a disposable on itself!'); @@ -293,7 +345,10 @@ export class MutableDisposable<T extends IDisposable> implements IDisposable { this._value = value; } - clear() { + /** + * Resets the stored value and disposed of the previously stored value. + */ + clear(): void { this.value = undefined; } @@ -439,3 +494,74 @@ export function disposeOnReturn(fn: (store: DisposableStore) => void): void { store.dispose(); } } + +/** + * A map the manages the lifecycle of the values that it stores. + */ +export class DisposableMap<K, V extends IDisposable = IDisposable> implements IDisposable { + + private readonly _store = new Map<K, V>(); + private _isDisposed = false; + + constructor() { + trackDisposable(this); + } + + /** + * Disposes of all stored values and mark this object as disposed. + * + * Trying to use this object after it has been disposed of is an error. + */ + dispose(): void { + markAsDisposed(this); + this._isDisposed = true; + this.clearAndDisposeAll(); + } + + /** + * Disposes of all stored values and clear the map, but DO NOT mark this object as disposed. + */ + clearAndDisposeAll(): void { + if (!this._store.size) { + return; + } + + try { + dispose(this._store.values()); + } finally { + this._store.clear(); + } + } + + has(key: K): boolean { + return this._store.has(key); + } + + get(key: K): V | undefined { + return this._store.get(key); + } + + set(key: K, value: V, skipDisposeOnOverwrite = false): void { + if (this._isDisposed) { + console.warn(new Error('Trying to add a disposable to a DisposableMap that has already been disposed of. The added object will be leaked!').stack); + } + + if (!skipDisposeOnOverwrite) { + this._store.get(key)?.dispose(); + } + + this._store.set(key, value); + } + + /** + * Delete the value stored for `key` from this map and also dispose of it. + */ + deleteAndDispose(key: K): void { + this._store.get(key)?.dispose(); + this._store.delete(key); + } + + [Symbol.iterator](): IterableIterator<[K, V]> { + return this._store[Symbol.iterator](); + } +} diff --git a/src/vs/base/common/linkedText.ts b/src/vs/base/common/linkedText.ts index 55e60bb836..35ffc5324e 100644 --- a/src/vs/base/common/linkedText.ts +++ b/src/vs/base/common/linkedText.ts @@ -23,7 +23,7 @@ export class LinkedText { } } -const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:|file:)[^\)\s]+)(?: ("|')([^\3]+)(\3))?\)/gi; +const LINK_REGEX = /\[([^\]]+)\]\(((?:https?:\/\/|command:|file:)[^\)\s]+)(?: (["'])(.+?)(\3))?\)/gi; export function parseLinkedText(text: string): LinkedText { const result: LinkedTextNode[] = []; diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index d50f2b5747..7262fd167c 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -3,9 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { shuffle } from 'vs/base/common/arrays'; -import { CharCode } from 'vs/base/common/charCode'; -import { compare, compareIgnoreCase, compareSubstring, compareSubstringIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V { @@ -36,726 +33,6 @@ export function setToString<K>(set: Set<K>): string { return `Set(${set.size}) {${entries.join(', ')}}`; } -export interface IKeyIterator<K> { - reset(key: K): this; - next(): this; - - hasNext(): boolean; - cmp(a: string): number; - value(): string; -} - -export class StringIterator implements IKeyIterator<string> { - - private _value: string = ''; - private _pos: number = 0; - - reset(key: string): this { - this._value = key; - this._pos = 0; - return this; - } - - next(): this { - this._pos += 1; - return this; - } - - hasNext(): boolean { - return this._pos < this._value.length - 1; - } - - cmp(a: string): number { - const aCode = a.charCodeAt(0); - const thisCode = this._value.charCodeAt(this._pos); - return aCode - thisCode; - } - - value(): string { - return this._value[this._pos]; - } -} - -export class ConfigKeysIterator implements IKeyIterator<string> { - - 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<string> { - - private _value!: string; - private _valueLen!: number; - private _from!: number; - private _to!: number; - - constructor( - private readonly _splitOnBackslash: boolean = true, - private readonly _caseSensitive: boolean = true - ) { } - - reset(key: string): this { - this._from = 0; - this._to = 0; - this._value = key; - this._valueLen = key.length; - for (let pos = key.length - 1; pos >= 0; pos--, this._valueLen--) { - const ch = this._value.charCodeAt(pos); - if (!(ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash)) { - break; - } - } - - return this.next(); - } - - hasNext(): boolean { - return this._to < this._valueLen; - } - - next(): this { - // this._data = key.split(/[\\/]/).filter(s => !!s); - this._from = this._to; - let justSeps = true; - for (; this._to < this._valueLen; this._to++) { - const ch = this._value.charCodeAt(this._to); - if (ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash) { - 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); - } -} - -const enum UriIteratorState { - Scheme = 1, Authority = 2, Path = 3, Query = 4, Fragment = 5 -} - -export class UriIterator implements IKeyIterator<URI> { - - private _pathIterator!: PathIterator; - private _value!: URI; - private _states: UriIteratorState[] = []; - private _stateIdx: number = 0; - - constructor( - private readonly _ignorePathCasing: (uri: URI) => boolean, - private readonly _ignoreQueryAndFragment: (uri: URI) => boolean) { } - - reset(key: URI): this { - this._value = key; - this._states = []; - if (this._value.scheme) { - this._states.push(UriIteratorState.Scheme); - } - if (this._value.authority) { - this._states.push(UriIteratorState.Authority); - } - if (this._value.path) { - this._pathIterator = new PathIterator(false, !this._ignorePathCasing(key)); - this._pathIterator.reset(key.path); - if (this._pathIterator.value()) { - this._states.push(UriIteratorState.Path); - } - } - if (!this._ignoreQueryAndFragment(key)) { - if (this._value.query) { - this._states.push(UriIteratorState.Query); - } - if (this._value.fragment) { - this._states.push(UriIteratorState.Fragment); - } - } - this._stateIdx = 0; - return this; - } - - next(): this { - if (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) { - this._pathIterator.next(); - } else { - this._stateIdx += 1; - } - return this; - } - - hasNext(): boolean { - return (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) - || this._stateIdx < this._states.length - 1; - } - - cmp(a: string): number { - if (this._states[this._stateIdx] === UriIteratorState.Scheme) { - return compareIgnoreCase(a, this._value.scheme); - } else if (this._states[this._stateIdx] === UriIteratorState.Authority) { - return compareIgnoreCase(a, this._value.authority); - } else if (this._states[this._stateIdx] === UriIteratorState.Path) { - return this._pathIterator.cmp(a); - } else if (this._states[this._stateIdx] === UriIteratorState.Query) { - return compare(a, this._value.query); - } else if (this._states[this._stateIdx] === UriIteratorState.Fragment) { - return compare(a, this._value.fragment); - } - throw new Error(); - } - - value(): string { - if (this._states[this._stateIdx] === UriIteratorState.Scheme) { - return this._value.scheme; - } else if (this._states[this._stateIdx] === UriIteratorState.Authority) { - return this._value.authority; - } else if (this._states[this._stateIdx] === UriIteratorState.Path) { - return this._pathIterator.value(); - } else if (this._states[this._stateIdx] === UriIteratorState.Query) { - return this._value.query; - } else if (this._states[this._stateIdx] === UriIteratorState.Fragment) { - return this._value.fragment; - } - throw new Error(); - } -} - -class TernarySearchTreeNode<K, V> { - height: number = 1; - segment!: string; - value: V | undefined; - key: K | undefined; - left: TernarySearchTreeNode<K, V> | undefined; - mid: TernarySearchTreeNode<K, V> | undefined; - right: TernarySearchTreeNode<K, V> | undefined; - - isEmpty(): boolean { - return !this.left && !this.mid && !this.right && !this.value; - } - - rotateLeft() { - const tmp = this.right!; - this.right = tmp.left; - tmp.left = this; - this.updateHeight(); - tmp.updateHeight(); - return tmp; - } - - rotateRight() { - const tmp = this.left!; - this.left = tmp.right; - tmp.right = this; - this.updateHeight(); - tmp.updateHeight(); - return tmp; - } - - updateHeight() { - this.height = 1 + Math.max(this.heightLeft, this.heightRight); - } - - balanceFactor() { - return this.heightRight - this.heightLeft; - } - - get heightLeft() { - return this.left?.height ?? 0; - } - - get heightRight() { - return this.right?.height ?? 0; - } -} - -const enum Dir { - Left = -1, - Mid = 0, - Right = 1, -} - -export class TernarySearchTree<K, V> { - - static forUris<E>(ignorePathCasing: (key: URI) => boolean = () => false, ignoreQueryAndFragment: (key: URI) => boolean = () => false): TernarySearchTree<URI, E> { - return new TernarySearchTree<URI, E>(new UriIterator(ignorePathCasing, ignoreQueryAndFragment)); - } - - static forPaths<E>(ignorePathCasing = false): TernarySearchTree<string, E> { - return new TernarySearchTree<string, E>(new PathIterator(undefined, !ignorePathCasing)); - } - - static forStrings<E>(): TernarySearchTree<string, E> { - return new TernarySearchTree<string, E>(new StringIterator()); - } - - static forConfigKeys<E>(): TernarySearchTree<string, E> { - return new TernarySearchTree<string, E>(new ConfigKeysIterator()); - } - - private _iter: IKeyIterator<K>; - private _root: TernarySearchTreeNode<K, V> | undefined; - - constructor(segments: IKeyIterator<K>) { - this._iter = segments; - } - - clear(): void { - this._root = undefined; - } - - /** - * Fill the tree with the same value of the given keys - */ - fill(element: V, keys: readonly K[]): void; - /** - * Fill the tree with given [key,value]-tuples - */ - fill(values: readonly [K, V][]): void; - fill(values: readonly [K, V][] | V, keys?: readonly K[]): void { - if (keys) { - const arr = keys.slice(0); - shuffle(arr); - for (const k of arr) { - this.set(k, (<V>values)); - } - } else { - const arr = (<[K, V][]>values).slice(0); - shuffle(arr); - for (const entry of arr) { - this.set(entry[0], entry[1]); - } - } - } - - set(key: K, element: V): V | undefined { - const iter = this._iter.reset(key); - let node: TernarySearchTreeNode<K, V>; - - if (!this._root) { - this._root = new TernarySearchTreeNode<K, V>(); - this._root.segment = iter.value(); - } - const stack: [Dir, TernarySearchTreeNode<K, V>][] = []; - - // find insert_node - node = this._root; - while (true) { - const val = iter.cmp(node.segment); - if (val > 0) { - // left - if (!node.left) { - node.left = new TernarySearchTreeNode<K, V>(); - node.left.segment = iter.value(); - } - stack.push([Dir.Left, node]); - node = node.left; - - } else if (val < 0) { - // right - if (!node.right) { - node.right = new TernarySearchTreeNode<K, V>(); - node.right.segment = iter.value(); - } - stack.push([Dir.Right, node]); - node = node.right; - - } else if (iter.hasNext()) { - // mid - iter.next(); - if (!node.mid) { - node.mid = new TernarySearchTreeNode<K, V>(); - node.mid.segment = iter.value(); - } - stack.push([Dir.Mid, node]); - node = node.mid; - } else { - break; - } - } - - // set value - const oldElement = node.value; - node.value = element; - node.key = key; - - // balance - for (let i = stack.length - 1; i >= 0; i--) { - const node = stack[i][1]; - - node.updateHeight(); - const bf = node.balanceFactor(); - - if (bf < -1 || bf > 1) { - // needs rotate - const d1 = stack[i][0]; - const d2 = stack[i + 1][0]; - - if (d1 === Dir.Right && d2 === Dir.Right) { - //right, right -> rotate left - stack[i][1] = node.rotateLeft(); - - } else if (d1 === Dir.Left && d2 === Dir.Left) { - // left, left -> rotate right - stack[i][1] = node.rotateRight(); - - } else if (d1 === Dir.Right && d2 === Dir.Left) { - // right, left -> double rotate right, left - node.right = stack[i + 1][1] = stack[i + 1][1].rotateRight(); - stack[i][1] = node.rotateLeft(); - - } else if (d1 === Dir.Left && d2 === Dir.Right) { - // left, right -> double rotate left, right - node.left = stack[i + 1][1] = stack[i + 1][1].rotateLeft(); - stack[i][1] = node.rotateRight(); - - } else { - throw new Error(); - } - - // patch path to parent - if (i > 0) { - switch (stack[i - 1][0]) { - case Dir.Left: - stack[i - 1][1].left = stack[i][1]; - break; - case Dir.Right: - stack[i - 1][1].right = stack[i][1]; - break; - case Dir.Mid: - stack[i - 1][1].mid = stack[i][1]; - break; - } - } else { - this._root = stack[0][1]; - } - } - } - - return oldElement; - } - - 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) { - const val = iter.cmp(node.segment); - if (val > 0) { - // left - node = node.left; - } else if (val < 0) { - // right - node = node.right; - } else if (iter.hasNext()) { - // mid - iter.next(); - node = node.mid; - } else { - break; - } - } - return node; - } - - has(key: K): boolean { - const node = this._getNode(key); - return !(node?.value === undefined && node?.mid === undefined); - } - - delete(key: K): void { - return this._delete(key, false); - } - - deleteSuperstr(key: K): void { - return this._delete(key, true); - } - - private _delete(key: K, superStr: boolean): void { - const iter = this._iter.reset(key); - const stack: [Dir, TernarySearchTreeNode<K, V>][] = []; - let node = this._root; - - // find node - while (node) { - const val = iter.cmp(node.segment); - if (val > 0) { - // left - stack.push([Dir.Left, node]); - node = node.left; - } else if (val < 0) { - // right - stack.push([Dir.Right, node]); - node = node.right; - } else if (iter.hasNext()) { - // mid - iter.next(); - stack.push([Dir.Mid, node]); - node = node.mid; - } else { - break; - } - } - - if (!node) { - // node not found - return; - } - - if (superStr) { - // removing children, reset height - node.left = undefined; - node.mid = undefined; - node.right = undefined; - node.height = 1; - } else { - // removing element - node.key = undefined; - node.value = undefined; - } - - // BST node removal - if (!node.mid && !node.value) { - if (node.left && node.right) { - // full node - const min = this._min(node.right); - const { key, value, segment } = min; - this._delete(min.key!, false); - node.key = key; - node.value = value; - node.segment = segment; - - } else { - // empty or half empty - const newChild = node.left ?? node.right; - if (stack.length > 0) { - const [dir, parent] = stack[stack.length - 1]; - switch (dir) { - case Dir.Left: parent.left = newChild; break; - case Dir.Mid: parent.mid = newChild; break; - case Dir.Right: parent.right = newChild; break; - } - } else { - this._root = newChild; - } - } - } - - // AVL balance - for (let i = stack.length - 1; i >= 0; i--) { - const node = stack[i][1]; - - node.updateHeight(); - const bf = node.balanceFactor(); - if (bf > 1) { - // right heavy - if (node.right!.balanceFactor() >= 0) { - // right, right -> rotate left - stack[i][1] = node.rotateLeft(); - } else { - // right, left -> double rotate - node.right = node.right!.rotateRight(); - stack[i][1] = node.rotateLeft(); - } - - } else if (bf < -1) { - // left heavy - if (node.left!.balanceFactor() <= 0) { - // left, left -> rotate right - stack[i][1] = node.rotateRight(); - } else { - // left, right -> double rotate - node.left = node.left!.rotateLeft(); - stack[i][1] = node.rotateRight(); - } - } - - // patch path to parent - if (i > 0) { - switch (stack[i - 1][0]) { - case Dir.Left: - stack[i - 1][1].left = stack[i][1]; - break; - case Dir.Right: - stack[i - 1][1].right = stack[i][1]; - break; - case Dir.Mid: - stack[i - 1][1].mid = stack[i][1]; - break; - } - } else { - this._root = stack[0][1]; - } - } - } - - private _min(node: TernarySearchTreeNode<K, V>): TernarySearchTreeNode<K, V> { - while (node.left) { - node = node.left; - } - return node; - } - - findSubstr(key: K): V | undefined { - const iter = this._iter.reset(key); - let node = this._root; - let candidate: V | undefined = undefined; - while (node) { - const val = iter.cmp(node.segment); - if (val > 0) { - // left - node = node.left; - } else if (val < 0) { - // right - node = node.right; - } else if (iter.hasNext()) { - // mid - iter.next(); - candidate = node.value || candidate; - node = node.mid; - } else { - break; - } - } - return node && node.value || candidate; - } - - findSuperstr(key: K): IterableIterator<[K, V]> | undefined { - const iter = this._iter.reset(key); - let node = this._root; - while (node) { - const val = iter.cmp(node.segment); - if (val > 0) { - // left - node = node.left; - } else if (val < 0) { - // right - node = node.right; - } else if (iter.hasNext()) { - // mid - iter.next(); - node = node.mid; - } else { - // collect - if (!node.mid) { - return undefined; - } else { - return this._entries(node.mid); - } - } - } - return undefined; - } - - forEach(callback: (value: V, index: K) => any): void { - for (const [key, value] of this) { - callback(value, key); - } - } - - *[Symbol.iterator](): IterableIterator<[K, V]> { - yield* this._entries(this._root); - } - - private _entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> { - const result: [K, V][] = []; - this._dfsEntries(node, result); - return result[Symbol.iterator](); - } - - private _dfsEntries(node: TernarySearchTreeNode<K, V> | undefined, bucket: [K, V][]) { - // DFS - if (!node) { - return; - } - if (node.left) { - this._dfsEntries(node.left, bucket); - } - if (node.value) { - bucket.push([node.key!, node.value]); - } - if (node.mid) { - this._dfsEntries(node.mid, bucket); - } - if (node.right) { - this._dfsEntries(node.right, bucket); - } - } - - // for debug/testing - _isBalanced(): boolean { - const nodeIsBalanced = (node: TernarySearchTreeNode<any, any> | undefined): boolean => { - if (!node) { - return true; - } - const bf = node.balanceFactor(); - if (bf < -1 || bf > 1) { - return false; - } - return nodeIsBalanced(node.left) && nodeIsBalanced(node.right); - }; - return nodeIsBalanced(this._root); - } -} - interface ResourceMapKeyFn { (resource: URI): string; } @@ -1356,46 +633,34 @@ export class LRUCache<K, V> extends LinkedMap<K, V> { } } -/** - * Wraps the map in type that only implements readonly properties. Useful - * in the extension host to prevent the consumer from making any mutations. - */ -export class ReadonlyMapView<K, V> implements ReadonlyMap<K, V>{ - readonly #source: ReadonlyMap<K, V>; +export class CounterSet<T> { - public get size() { - return this.#source.size; + private map = new Map<T, number>(); + + add(value: T): CounterSet<T> { + this.map.set(value, (this.map.get(value) || 0) + 1); + return this; } - constructor(source: ReadonlyMap<K, V>) { - this.#source = source; + delete(value: T): boolean { + let counter = this.map.get(value) || 0; + + if (counter === 0) { + return false; + } + + counter--; + + if (counter === 0) { + this.map.delete(value); + } else { + this.map.set(value, counter); + } + + return true; } - forEach(callbackfn: (value: V, key: K, map: ReadonlyMap<K, V>) => void, thisArg?: any): void { - this.#source.forEach(callbackfn, thisArg); - } - - get(key: K): V | undefined { - return this.#source.get(key); - } - - has(key: K): boolean { - return this.#source.has(key); - } - - entries(): IterableIterator<[K, V]> { - return this.#source.entries(); - } - - keys(): IterableIterator<K> { - return this.#source.keys(); - } - - values(): IterableIterator<V> { - return this.#source.values(); - } - - [Symbol.iterator](): IterableIterator<[K, V]> { - return this.#source.entries(); + has(value: T): boolean { + return this.map.has(value); } } diff --git a/src/vs/base/common/marked/cgmanifest.json b/src/vs/base/common/marked/cgmanifest.json index 60e11b4144..4dffc3ff23 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": "2002557d004139ca2208c910d9ca999829b65406" + "commitHash": "7e2ef307846427650114591f9257b5545868e928" } }, "license": "MIT", - "version": "4.0.16" + "version": "4.1.0" } ], "version": 1 diff --git a/src/vs/base/common/marked/marked.d.ts b/src/vs/base/common/marked/marked.d.ts index 5b7014ec2f..7aeab2e864 100644 --- a/src/vs/base/common/marked/marked.d.ts +++ b/src/vs/base/common/marked/marked.d.ts @@ -10,6 +10,8 @@ // Sarun Intaralawan <https://github.com/sarunint> // Tony Brix <https://github.com/UziTech> // Anatolii Titov <https://github.com/Toliak> +// Jean-Francois Cere <https://github.com/jfcere> +// Mykhaylo Stolyarchuk <https://github.com/MykSto> // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped /** @@ -75,11 +77,7 @@ export namespace marked { * @param callback Function called when the markdownString has been fully parsed when using async highlighting * @return String of compiled HTML */ - function parse( - src: string, - options?: MarkedOptions, - callback?: (error: any, parseResult: string) => void, - ): string; + function parse(src: string, options?: MarkedOptions, callback?: (error: any, parseResult: string) => void): string; /** * @param src Tokenized source as array of tokens @@ -126,34 +124,39 @@ export namespace marked { class Tokenizer<T = never> { constructor(options?: MarkedOptions); options: MarkedOptions; - space(this: TokenizerThis, src: string): Tokens.Space | T; - code(this: TokenizerThis, src: string): Tokens.Code | T; - fences(this: TokenizerThis, src: string): Tokens.Code | T; - heading(this: TokenizerThis, src: string): Tokens.Heading | T; - hr(this: TokenizerThis, src: string): Tokens.Hr | T; - blockquote(this: TokenizerThis, src: string): Tokens.Blockquote | T; - list(this: TokenizerThis, src: string): Tokens.List | T; - html(this: TokenizerThis, src: string): Tokens.HTML | T; - def(this: TokenizerThis, src: string): Tokens.Def | T; - table(this: TokenizerThis, src: string): Tokens.Table | T; - lheading(this: TokenizerThis, src: string): Tokens.Heading | T; - paragraph(this: TokenizerThis, src: string): Tokens.Paragraph | T; - text(this: TokenizerThis, src: string): Tokens.Text | T; - escape(this: TokenizerThis, src: string): Tokens.Escape | T; - tag(this: TokenizerThis, src: string): Tokens.Tag | T; - link(this: TokenizerThis, src: string): Tokens.Image | Tokens.Link | T; + space(this: Tokenizer & TokenizerThis, src: string): Tokens.Space | T; + code(this: Tokenizer & TokenizerThis, src: string): Tokens.Code | T; + fences(this: Tokenizer & TokenizerThis, src: string): Tokens.Code | T; + heading(this: Tokenizer & TokenizerThis, src: string): Tokens.Heading | T; + hr(this: Tokenizer & TokenizerThis, src: string): Tokens.Hr | T; + blockquote(this: Tokenizer & TokenizerThis, src: string): Tokens.Blockquote | T; + list(this: Tokenizer & TokenizerThis, src: string): Tokens.List | T; + html(this: Tokenizer & TokenizerThis, src: string): Tokens.HTML | T; + def(this: Tokenizer & TokenizerThis, src: string): Tokens.Def | T; + table(this: Tokenizer & TokenizerThis, src: string): Tokens.Table | T; + lheading(this: Tokenizer & TokenizerThis, src: string): Tokens.Heading | T; + paragraph(this: Tokenizer & TokenizerThis, src: string): Tokens.Paragraph | T; + text(this: Tokenizer & TokenizerThis, src: string): Tokens.Text | T; + escape(this: Tokenizer & TokenizerThis, src: string): Tokens.Escape | T; + tag(this: Tokenizer & TokenizerThis, src: string): Tokens.Tag | T; + link(this: Tokenizer & TokenizerThis, src: string): Tokens.Image | Tokens.Link | T; reflink( - this: TokenizerThis, + this: Tokenizer & TokenizerThis, src: string, links: Tokens.Link[] | Tokens.Image[], ): Tokens.Link | Tokens.Image | Tokens.Text | T; - emStrong(this: TokenizerThis, src: string, maskedSrc: string, prevChar: string): Tokens.Em | Tokens.Strong | T; - codespan(this: TokenizerThis, src: string): Tokens.Codespan | T; - br(this: TokenizerThis, src: string): Tokens.Br | T; - del(this: TokenizerThis, src: string): Tokens.Del | T; - autolink(this: TokenizerThis, src: string, mangle: (cap: string) => string): Tokens.Link | T; - url(this: TokenizerThis, src: string, mangle: (cap: string) => string): Tokens.Link | T; - inlineText(this: TokenizerThis, src: string, smartypants: (cap: string) => string): Tokens.Text | T; + emStrong( + this: Tokenizer & TokenizerThis, + src: string, + maskedSrc: string, + prevChar: string, + ): Tokens.Em | Tokens.Strong | T; + codespan(this: Tokenizer & TokenizerThis, src: string): Tokens.Codespan | T; + br(this: Tokenizer & TokenizerThis, src: string): Tokens.Br | T; + del(this: Tokenizer & TokenizerThis, src: string): Tokens.Del | T; + autolink(this: Tokenizer & TokenizerThis, src: string, mangle: (cap: string) => string): Tokens.Link | T; + url(this: Tokenizer & TokenizerThis, src: string, mangle: (cap: string) => string): Tokens.Link | T; + inlineText(this: Tokenizer & TokenizerThis, src: string, smartypants: (cap: string) => string): Tokens.Text | T; } type TokenizerObject = Partial<Omit<Tokenizer<false>, 'constructor' | 'options'>>; @@ -161,39 +164,39 @@ export namespace marked { class Renderer<T = never> { constructor(options?: MarkedOptions); options: MarkedOptions; - code(this: RendererThis, code: string, language: string | undefined, isEscaped: boolean): string | T; - blockquote(this: RendererThis, quote: string): string | T; - html(this: RendererThis, html: string): string | T; + code(this: Renderer | RendererThis, code: string, language: string | undefined, isEscaped: boolean): string | T; + blockquote(this: Renderer | RendererThis, quote: string): string | T; + html(this: Renderer | RendererThis, html: string): string | T; heading( - this: RendererThis, + this: Renderer | RendererThis, text: string, level: 1 | 2 | 3 | 4 | 5 | 6, raw: string, slugger: Slugger, ): string | T; - hr(this: RendererThis): string | T; - list(this: RendererThis, body: string, ordered: boolean, start: number): string | T; - listitem(this: RendererThis, text: string, task: boolean, checked: boolean): string | T; - checkbox(this: RendererThis, checked: boolean): string | T; - paragraph(this: RendererThis, text: string): string | T; - table(this: RendererThis, header: string, body: string): string | T; - tablerow(this: RendererThis, content: string): string | T; + hr(this: Renderer | RendererThis): string | T; + list(this: Renderer | RendererThis, body: string, ordered: boolean, start: number): string | T; + listitem(this: Renderer | RendererThis, text: string, task: boolean, checked: boolean): string | T; + checkbox(this: Renderer | RendererThis, checked: boolean): string | T; + paragraph(this: Renderer | RendererThis, text: string): string | T; + table(this: Renderer | RendererThis, header: string, body: string): string | T; + tablerow(this: Renderer | RendererThis, content: string): string | T; tablecell( - this: RendererThis, + this: Renderer | RendererThis, content: string, flags: { header: boolean; align: 'center' | 'left' | 'right' | null; }, ): string | T; - strong(this: RendererThis, text: string): string | T; - em(this: RendererThis, text: string): string | T; - codespan(this: RendererThis, code: string): string | T; - br(this: RendererThis): string | T; - del(this: RendererThis, text: string): string | T; - link(this: RendererThis, href: string | null, title: string | null, text: string): string | T; - image(this: RendererThis, href: string | null, title: string | null, text: string): string | T; - text(this: RendererThis, text: string): string | T; + strong(this: Renderer | RendererThis, text: string): string | T; + em(this: Renderer | RendererThis, text: string): string | T; + codespan(this: Renderer | RendererThis, code: string): string | T; + br(this: Renderer | RendererThis): string | T; + del(this: Renderer | RendererThis, text: string): string | T; + link(this: Renderer | RendererThis, href: string | null, title: string | null, text: string): string | T; + image(this: Renderer | RendererThis, href: string | null, title: string | null, text: string): string | T; + text(this: Renderer | RendererThis, text: string): string | T; } type RendererObject = Partial<Omit<Renderer<false>, 'constructor' | 'options'>>; @@ -221,7 +224,7 @@ export namespace marked { static parse(src: Token[] | TokensList, options?: MarkedOptions): string; static parseInline(src: Token[], options?: MarkedOptions): string; parse(src: Token[] | TokensList): string; - parseInline(src: Token[], renderer: Renderer): string; + parseInline(src: Token[], renderer?: Renderer): string; next(): Token; } @@ -236,8 +239,8 @@ export namespace marked { lex(src: string): TokensList; blockTokens(src: string, tokens: Token[]): Token[]; blockTokens(src: string, tokens: TokensList): TokensList; - inline(src: string, tokens: Token[]): void; - inlineTokens(src: string, tokens: Token[]): Token[]; + inline(src: string, tokens?: Token[]): Token[]; + inlineTokens(src: string, tokens?: Token[]): Token[]; state: { inLink: boolean; inRawBlock: boolean; @@ -461,7 +464,7 @@ export namespace marked { interface TokenizerExtension { name: string; level: 'block' | 'inline'; - start?: ((this: TokenizerThis, src: string) => number) | undefined; + start?: ((this: TokenizerThis, src: string) => number | void) | undefined; tokenizer: (this: TokenizerThis, src: string, tokens: Token[] | TokensList) => Tokens.Generic | void; childTokens?: string[] | undefined; } @@ -514,11 +517,7 @@ export namespace marked { * with an error if any occurred during highlighting and a string * if highlighting was successful) */ - highlight?( - code: string, - lang: string, - callback?: (error: any, code?: string) => void, - ): string | void; + highlight?(code: string, lang: string, callback?: (error: any, code?: string) => void): string | void; /** * Set the prefix for code block classes. diff --git a/src/vs/base/common/marked/marked.js b/src/vs/base/common/marked/marked.js index b7d8fe40e5..eb8cde82ab 100644 --- a/src/vs/base/common/marked/marked.js +++ b/src/vs/base/common/marked/marked.js @@ -19,8 +19,8 @@ // ESM-uncomment-end (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.marked = {})); })(this, (function (exports) { 'use strict'; @@ -83,6 +83,7 @@ function getDefaults() { return { + async: false, baseUrl: null, breaks: false, extensions: null, @@ -423,7 +424,7 @@ href: href, title: title, text: text, - tokens: lexer.inlineTokens(text, []) + tokens: lexer.inlineTokens(text) }; lexer.state.inLink = false; return token; @@ -531,15 +532,13 @@ } } - var token = { + return { type: 'heading', raw: cap[0], depth: cap[1].length, text: text, - tokens: [] + tokens: this.lexer.inline(text) }; - this.lexer.inline(token.text, token.tokens); - return token; } }; @@ -632,7 +631,9 @@ if (!endEarly) { var nextBulletRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:[*+-]|\\d{1,9}[.)])((?: [^\\n]*)?(?:\\n|$))"); - var hrRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)"); // Check if following lines should be included in List Item + var hrRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)"); + var fencesBeginRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}(?:```|~~~)"); + var headingBeginRegex = new RegExp("^ {0," + Math.min(3, indent - 1) + "}#"); // Check if following lines should be included in List Item while (src) { rawLine = src.split('\n', 1)[0]; @@ -640,6 +641,16 @@ if (this.options.pedantic) { line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, ' '); + } // End list item if found code fences + + + if (fencesBeginRegex.test(line)) { + break; + } // End list item if found start of new heading + + + if (headingBeginRegex.test(line)) { + break; } // End list item if found start of new bullet @@ -757,10 +768,10 @@ }; if (this.options.sanitize) { + var text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]); token.type = 'paragraph'; - token.text = this.options.sanitizer ? this.options.sanitizer(cap[0]) : escape(cap[0]); - token.tokens = []; - this.lexer.inline(token.text, token.tokens); + token.text = text; + token.tokens = this.lexer.inline(text); } return token; @@ -830,8 +841,7 @@ l = item.header.length; for (j = 0; j < l; j++) { - item.header[j].tokens = []; - this.lexer.inline(item.header[j].text, item.header[j].tokens); + item.header[j].tokens = this.lexer.inline(item.header[j].text); } // cell child tokens @@ -841,8 +851,7 @@ row = item.rows[j]; for (k = 0; k < row.length; k++) { - row[k].tokens = []; - this.lexer.inline(row[k].text, row[k].tokens); + row[k].tokens = this.lexer.inline(row[k].text); } } @@ -855,15 +864,13 @@ var cap = this.rules.block.lheading.exec(src); if (cap) { - var token = { + return { type: 'heading', raw: cap[0], depth: cap[2].charAt(0) === '=' ? 1 : 2, text: cap[1], - tokens: [] + tokens: this.lexer.inline(cap[1]) }; - this.lexer.inline(token.text, token.tokens); - return token; } }; @@ -871,14 +878,13 @@ var cap = this.rules.block.paragraph.exec(src); if (cap) { - var token = { + var text = cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1]; + return { type: 'paragraph', raw: cap[0], - text: cap[1].charAt(cap[1].length - 1) === '\n' ? cap[1].slice(0, -1) : cap[1], - tokens: [] + text: text, + tokens: this.lexer.inline(text) }; - this.lexer.inline(token.text, token.tokens); - return token; } }; @@ -886,14 +892,12 @@ var cap = this.rules.block.text.exec(src); if (cap) { - var token = { + return { type: 'text', raw: cap[0], text: cap[0], - tokens: [] + tokens: this.lexer.inline(cap[0]) }; - this.lexer.inline(token.text, token.tokens); - return token; } }; @@ -1072,7 +1076,7 @@ type: 'em', raw: src.slice(0, lLength + match.index + rLength + 1), text: _text, - tokens: this.lexer.inlineTokens(_text, []) + tokens: this.lexer.inlineTokens(_text) }; } // Create 'strong' if smallest delimiter has even char count. **a*** @@ -1082,7 +1086,7 @@ type: 'strong', raw: src.slice(0, lLength + match.index + rLength + 1), text: text, - tokens: this.lexer.inlineTokens(text, []) + tokens: this.lexer.inlineTokens(text) }; } } @@ -1128,7 +1132,7 @@ type: 'del', raw: cap[0], text: cap[2], - tokens: this.lexer.inlineTokens(cap[2], []) + tokens: this.lexer.inlineTokens(cap[2]) }; } }; @@ -1746,10 +1750,15 @@ }; _proto.inline = function inline(src, tokens) { + if (tokens === void 0) { + tokens = []; + } + this.inlineQueue.push({ src: src, tokens: tokens }); + return tokens; } /** * Lexing/Compiling @@ -2722,15 +2731,7 @@ return; } - try { - var _tokens = Lexer.lex(src, opt); - - if (opt.walkTokens) { - marked.walkTokens(_tokens, opt.walkTokens); - } - - return Parser.parse(_tokens, opt); - } catch (e) { + function onError(e) { e.message += '\nPlease report this to https://github.com/markedjs/marked.'; if (opt.silent) { @@ -2739,6 +2740,24 @@ throw e; } + + try { + var _tokens = Lexer.lex(src, opt); + + if (opt.walkTokens) { + if (opt.async) { + return Promise.all(marked.walkTokens(_tokens, opt.walkTokens)).then(function () { + return Parser.parse(_tokens, opt); + })["catch"](onError); + } + + marked.walkTokens(_tokens, opt.walkTokens); + } + + return Parser.parse(_tokens, opt); + } catch (e) { + onError(e); + } } /** * Options @@ -2903,11 +2922,14 @@ var _walkTokens = marked.defaults.walkTokens; opts.walkTokens = function (token) { - pack.walkTokens.call(this, token); + var values = []; + values.push(pack.walkTokens.call(this, token)); if (_walkTokens) { - _walkTokens.call(this, token); + values = values.concat(_walkTokens.call(this, token)); } + + return values; }; } @@ -2924,16 +2946,18 @@ marked.walkTokens = function (tokens, callback) { + var values = []; + var _loop3 = function _loop3() { var token = _step.value; - callback.call(marked, token); + values = values.concat(callback.call(marked, token)); switch (token.type) { case 'table': { for (var _iterator2 = _createForOfIteratorHelperLoose(token.header), _step2; !(_step2 = _iterator2()).done;) { var cell = _step2.value; - marked.walkTokens(cell.tokens, callback); + values = values.concat(marked.walkTokens(cell.tokens, callback)); } for (var _iterator3 = _createForOfIteratorHelperLoose(token.rows), _step3; !(_step3 = _iterator3()).done;) { @@ -2941,7 +2965,7 @@ for (var _iterator4 = _createForOfIteratorHelperLoose(row), _step4; !(_step4 = _iterator4()).done;) { var _cell = _step4.value; - marked.walkTokens(_cell.tokens, callback); + values = values.concat(marked.walkTokens(_cell.tokens, callback)); } } @@ -2950,7 +2974,7 @@ case 'list': { - marked.walkTokens(token.items, callback); + values = values.concat(marked.walkTokens(token.items, callback)); break; } @@ -2959,10 +2983,10 @@ if (marked.defaults.extensions && marked.defaults.extensions.childTokens && marked.defaults.extensions.childTokens[token.type]) { // Walk any extensions marked.defaults.extensions.childTokens[token.type].forEach(function (childTokens) { - marked.walkTokens(token[childTokens], callback); + values = values.concat(marked.walkTokens(token[childTokens], callback)); }); } else if (token.tokens) { - marked.walkTokens(token.tokens, callback); + values = values.concat(marked.walkTokens(token.tokens, callback)); } } } @@ -2971,6 +2995,8 @@ for (var _iterator = _createForOfIteratorHelperLoose(tokens), _step; !(_step = _iterator()).done;) { _loop3(); } + + return values; }; /** * Parse Inline @@ -3055,5 +3081,20 @@ // ESM-uncomment-begin // })(); -// export var marked = (__marked_exports || exports); +// export var Lexer = (__marked_exports.Lexer || exports.Lexer); +// export var Parser = (__marked_exports.Parser || exports.Parser); +// export var Renderer = (__marked_exports.Renderer || exports.Renderer); +// export var Slugger = (__marked_exports.Slugger || exports.Slugger); +// export var TextRenderer = (__marked_exports.TextRenderer || exports.TextRenderer); +// export var Tokenizer = (__marked_exports.Tokenizer || exports.Tokenizer); +// export var getDefaults = (__marked_exports.getDefaults || exports.getDefaults); +// export var lexer = (__marked_exports.lexer || exports.lexer); +// export var marked = (__marked_exports.marked || exports.marked); +// export var options = (__marked_exports.options || exports.options); +// export var parse = (__marked_exports.parse || exports.parse); +// export var parseInline = (__marked_exports.parseInline || exports.parseInline); +// export var parser = (__marked_exports.parser || exports.parser); +// export var setOptions = (__marked_exports.setOptions || exports.setOptions); +// export var use = (__marked_exports.use || exports.use); +// export var walkTokens = (__marked_exports.walkTokens || exports.walkTokens); // ESM-uncomment-end diff --git a/src/vs/base/common/marshallingIds.ts b/src/vs/base/common/marshallingIds.ts index a79a2fa2e3..45b9698b19 100644 --- a/src/vs/base/common/marshallingIds.ts +++ b/src/vs/base/common/marshallingIds.ts @@ -12,11 +12,13 @@ export const enum MarshalledId { ScmProvider, CommentController, CommentThread, + CommentThreadInstance, CommentThreadReply, CommentNode, CommentThreadNode, TimelineActionContext, NotebookCellActionContext, + NotebookActionContext, TestItemContext, Date } diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index a6c0747626..23b7292164 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as errors from 'vs/base/common/errors'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -58,10 +59,7 @@ export namespace Schemas { export const vscodeCustomEditor = 'vscode-custom-editor'; - export const vscodeNotebook = 'vscode-notebook'; - export const vscodeNotebookCell = 'vscode-notebook-cell'; - export const vscodeNotebookCellMetadata = 'vscode-notebook-cell-metadata'; export const vscodeNotebookCellOutput = 'vscode-notebook-cell-output'; export const vscodeInteractive = 'vscode-interactive'; @@ -73,6 +71,8 @@ export namespace Schemas { export const vscodeTerminal = 'vscode-terminal'; + export const vscodeChatSesssion = 'vscode-chat-editor'; + /** * Scheme used internally for webviews that aren't linked to a resource (i.e. not custom editors) */ @@ -150,11 +150,16 @@ class RemoteAuthoritiesImpl { rewrite(uri: URI): URI { if (this._delegate) { - return this._delegate(uri); + try { + return this._delegate(uri); + } catch (err) { + errors.onUnexpectedError(err); + return uri; + } } const authority = uri.authority; let host = this._hosts[authority]; - if (host && host.indexOf(':') !== -1) { + if (host && host.indexOf(':') !== -1 && host.indexOf('[') === -1) { host = `[${host}]`; } const port = this._ports[authority]; @@ -174,6 +179,22 @@ class RemoteAuthoritiesImpl { export const RemoteAuthorities = new RemoteAuthoritiesImpl(); +/** + * A string pointing to a path inside the app. It should not begin with ./ or ../ + */ +export type AppResourcePath = ( + `a${string}` | `b${string}` | `c${string}` | `d${string}` | `e${string}` | `f${string}` + | `g${string}` | `h${string}` | `i${string}` | `j${string}` | `k${string}` | `l${string}` + | `m${string}` | `n${string}` | `o${string}` | `p${string}` | `q${string}` | `r${string}` + | `s${string}` | `t${string}` | `u${string}` | `v${string}` | `w${string}` | `x${string}` + | `y${string}` | `z${string}` +); + +export const builtinExtensionsPath: AppResourcePath = 'vs/../../extensions'; +export const nodeModulesPath: AppResourcePath = 'vs/../../node_modules'; +export const nodeModulesAsarPath: AppResourcePath = 'vs/../../node_modules.asar'; +export const nodeModulesAsarUnpackedPath: AppResourcePath = 'vs/../../node_modules.asar.unpacked'; + class FileAccessImpl { private static readonly FALLBACK_AUTHORITY = 'vscode-app'; @@ -184,11 +205,18 @@ class FileAccessImpl { * * **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context. */ - asBrowserUri(uri: URI): URI; - asBrowserUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI; - asBrowserUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { - const uri = this.toUri(uriOrModule, moduleIdToUrl); + asBrowserUri(resourcePath: AppResourcePath | ''): URI { + const uri = this.toUri(resourcePath, require); + return this.uriToBrowserUri(uri); + } + /** + * Returns a URI to use in contexts where the browser is responsible + * for loading (e.g. fetch()) or when used within the DOM. + * + * **Note:** use `dom.ts#asCSSUrl` whenever the URL is to be used in CSS context. + */ + uriToBrowserUri(uri: URI): URI { // Handle remote URIs via `RemoteAuthorities` if (uri.scheme === Schemas.vscodeRemote) { return RemoteAuthorities.rewrite(uri); @@ -224,11 +252,16 @@ class FileAccessImpl { * Returns the `file` URI to use in contexts where node.js * is responsible for loading. */ - asFileUri(uri: URI): URI; - asFileUri(moduleId: string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI; - asFileUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { - const uri = this.toUri(uriOrModule, moduleIdToUrl); + asFileUri(resourcePath: AppResourcePath | ''): URI { + const uri = this.toUri(resourcePath, require); + return this.uriToFileUri(uri); + } + /** + * Returns the `file` URI to use in contexts where node.js + * is responsible for loading. + */ + uriToFileUri(uri: URI): URI { // Only convert the URI if it is `vscode-file:` scheme if (uri.scheme === Schemas.vscodeFileResource) { return uri.with({ @@ -245,13 +278,63 @@ class FileAccessImpl { return uri; } - private toUri(uriOrModule: URI | string, moduleIdToUrl?: { toUrl(moduleId: string): string }): URI { + private toUri(uriOrModule: URI | string, moduleIdToUrl: { toUrl(moduleId: string): string }): URI { if (URI.isUri(uriOrModule)) { return uriOrModule; } - return URI.parse(moduleIdToUrl!.toUrl(uriOrModule)); + return URI.parse(moduleIdToUrl.toUrl(uriOrModule)); } } export const FileAccess = new FileAccessImpl(); + + +export namespace COI { + + const coiHeaders = new Map<'3' | '2' | '1' | string, Record<string, string>>([ + ['1', { 'Cross-Origin-Opener-Policy': 'same-origin' }], + ['2', { 'Cross-Origin-Embedder-Policy': 'require-corp' }], + ['3', { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' }], + ]); + + export const CoopAndCoep = Object.freeze(coiHeaders.get('3')); + + const coiSearchParamName = 'vscode-coi'; + + /** + * Extract desired headers from `vscode-coi` invocation + */ + export function getHeadersFromQuery(url: string | URI | URL): Record<string, string> | undefined { + let params: URLSearchParams | undefined; + if (typeof url === 'string') { + params = new URL(url).searchParams; + } else if (url instanceof URL) { + params = url.searchParams; + } else if (URI.isUri(url)) { + params = new URL(url.toString(true)).searchParams; + } + const value = params?.get(coiSearchParamName); + if (!value) { + return undefined; + } + return coiHeaders.get(value); + } + + /** + * Add the `vscode-coi` query attribute based on wanting `COOP` and `COEP`. Will be a noop when `crossOriginIsolated` + * isn't enabled the current context + */ + export function addSearchParam(urlOrSearch: URLSearchParams | Record<string, string>, coop: boolean, coep: boolean): void { + if (!(<any>globalThis).crossOriginIsolated) { + // depends on the current context being COI + return; + } + const value = coop && coep ? '3' : coep ? '2' : '1'; + if (urlOrSearch instanceof URLSearchParams) { + urlOrSearch.set(coiSearchParamName, value); + } else { + (<Record<string, string>>urlOrSearch)[coiSearchParamName] = value; + } + } +} diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index b87b079948..a17573ff6b 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -3,23 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isArray, isTypedArray, isObject, isUndefinedOrNull } from 'vs/base/common/types'; +import { isTypedArray, isObject, isUndefinedOrNull } from 'vs/base/common/types'; export function deepClone<T>(obj: T): T { if (!obj || typeof obj !== 'object') { return obj; } if (obj instanceof RegExp) { - // See https://github.com/microsoft/TypeScript/issues/10990 - return obj as any; + return obj; } const result: any = Array.isArray(obj) ? [] : {}; - Object.keys(<any>obj).forEach((key: string) => { - if ((<any>obj)[key] && typeof (<any>obj)[key] === 'object') { - result[key] = deepClone((<any>obj)[key]); - } else { - result[key] = (<any>obj)[key]; - } + Object.entries(obj).forEach(([key, value]) => { + result[key] = value && typeof value === 'object' ? deepClone(value) : value; }); return result; } @@ -61,7 +56,7 @@ function _cloneAndChange(obj: any, changer: (orig: any) => any, seen: Set<any>): return changed; } - if (isArray(obj)) { + if (Array.isArray(obj)) { const r1: any[] = []; for (const e of obj) { r1.push(_cloneAndChange(e, changer, seen)); @@ -238,3 +233,38 @@ export function filter(obj: obj, predicate: (key: string, value: any) => boolean } return result; } + +export function getAllPropertyNames(obj: object): string[] { + let res: string[] = []; + let proto = Object.getPrototypeOf(obj); + while (Object.prototype !== proto) { + res = res.concat(Object.getOwnPropertyNames(proto)); + proto = Object.getPrototypeOf(proto); + } + return res; +} + +export function getAllMethodNames(obj: object): string[] { + const methods: string[] = []; + for (const prop of getAllPropertyNames(obj)) { + if (typeof (obj as any)[prop] === 'function') { + methods.push(prop); + } + } + return methods; +} + +export function createProxyObject<T extends object>(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); + }; + }; + + const result = {} as T; + for (const methodName of methodNames) { + (<any>result)[methodName] = createProxyMethod(methodName); + } + return result; +} diff --git a/src/vs/base/common/observableImpl/autorun.ts b/src/vs/base/common/observableImpl/autorun.ts index ca250de13e..93776e7785 100644 --- a/src/vs/base/common/observableImpl/autorun.ts +++ b/src/vs/base/common/observableImpl/autorun.ts @@ -3,34 +3,61 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { assertFn } from 'vs/base/common/assert'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IReader, IObservable, IObserver } from 'vs/base/common/observableImpl/base'; +import { IReader, IObservable, IObserver, IChangeContext } from 'vs/base/common/observableImpl/base'; import { getLogger } from 'vs/base/common/observableImpl/logging'; export function autorun(debugName: string, fn: (reader: IReader) => void): IDisposable { - return new AutorunObserver(debugName, fn, undefined); + return new AutorunObserver(debugName, fn, undefined, undefined); } -interface IChangeContext { - readonly changedObservable: IObservable<any, any>; - readonly change: unknown; - - didChange<T, TChange>(observable: IObservable<T, TChange>): this is { change: TChange }; -} - -export function autorunHandleChanges( +export function autorunHandleChanges<TChangeSummary>( debugName: string, options: { - /** - * Returns if this change should cause a re-run of the autorun. - */ - handleChange: (context: IChangeContext) => boolean; + createEmptyChangeSummary?: () => TChangeSummary; + handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; }, - fn: (reader: IReader) => void + fn: (reader: IReader, changeSummary: TChangeSummary) => void ): IDisposable { - return new AutorunObserver(debugName, fn, options.handleChange); + return new AutorunObserver(debugName, fn, options.createEmptyChangeSummary, options.handleChange); } +// TODO@hediet rename to autorunWithStore +export function autorunWithStore2( + debugName: string, + fn: (reader: IReader, store: DisposableStore) => void, +): IDisposable { + return autorunWithStore(fn, debugName); +} + +export function autorunWithStoreHandleChanges<TChangeSummary>( + debugName: string, + options: { + createEmptyChangeSummary?: () => TChangeSummary; + handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; + }, + fn: (reader: IReader, changeSummary: TChangeSummary, store: DisposableStore) => void +): IDisposable { + const store = new DisposableStore(); + const disposable = autorunHandleChanges( + debugName, + { + createEmptyChangeSummary: options.createEmptyChangeSummary, + handleChange: options.handleChange, + }, + (reader, changeSummary) => { + store.clear(); + fn(reader, changeSummary, store); + } + ); + return toDisposable(() => { + disposable.dispose(); + store.dispose(); + }); +} + +// TODO@hediet deprecate, rename to autorunWithStoreEx export function autorunWithStore( fn: (reader: IReader, store: DisposableStore) => void, debugName: string @@ -49,104 +76,140 @@ export function autorunWithStore( }); } -export class AutorunObserver implements IObserver, IReader, IDisposable { - public needsToRun = true; +const enum AutorunState { + /** + * A dependency could have changed. + * We need to explicitly ask them if at least one dependency changed. + */ + dependenciesMightHaveChanged = 1, + + /** + * A dependency changed and we need to recompute. + */ + stale = 2, + upToDate = 3, +} + +export class AutorunObserver<TChangeSummary = any> implements IObserver, IReader, IDisposable { + private state = AutorunState.stale; private updateCount = 0; private disposed = false; - - /** - * The actual dependencies. - */ - private _dependencies = new Set<IObservable<any>>(); - public get dependencies() { - return this._dependencies; - } - - /** - * Dependencies that have to be removed when {@link runFn} ran through. - */ - private staleDependencies = new Set<IObservable<any>>(); + private dependencies = new Set<IObservable<any>>(); + private dependenciesToBeRemoved = new Set<IObservable<any>>(); + private changeSummary: TChangeSummary | undefined; constructor( public readonly debugName: string, - private readonly runFn: (reader: IReader) => void, - private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined + private readonly runFn: (reader: IReader, changeSummary: TChangeSummary) => void, + private readonly createChangeSummary: (() => TChangeSummary) | undefined, + private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, ) { + this.changeSummary = this.createChangeSummary?.(); getLogger()?.handleAutorunCreated(this); - this.runIfNeeded(); - } - - public subscribeTo<T>(observable: IObservable<T>) { - // In case the run action disposes the autorun - if (this.disposed) { - return; - } - this._dependencies.add(observable); - if (!this.staleDependencies.delete(observable)) { - observable.addObserver(this); - } - } - - public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void { - const shouldReact = this._handleChange ? this._handleChange({ - changedObservable: observable, - change, - didChange: o => o === observable as any, - }) : true; - this.needsToRun = this.needsToRun || shouldReact; - - if (this.updateCount === 0) { - this.runIfNeeded(); - } - } - - public beginUpdate(): void { - this.updateCount++; - } - - public endUpdate(): void { - this.updateCount--; - if (this.updateCount === 0) { - this.runIfNeeded(); - } - } - - private runIfNeeded(): void { - if (!this.needsToRun) { - return; - } - // Assert: this.staleDependencies is an empty set. - const emptySet = this.staleDependencies; - this.staleDependencies = this._dependencies; - this._dependencies = emptySet; - - this.needsToRun = false; - - getLogger()?.handleAutorunTriggered(this); - - try { - this.runFn(this); - } finally { - // We don't want our observed observables to think that they are (not even temporarily) not being observed. - // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.staleDependencies) { - o.removeObserver(this); - } - this.staleDependencies.clear(); - } + this._runIfNeeded(); } public dispose(): void { this.disposed = true; - for (const o of this._dependencies) { + for (const o of this.dependencies) { o.removeObserver(this); } - this._dependencies.clear(); + this.dependencies.clear(); + } + + private _runIfNeeded() { + if (this.state === AutorunState.upToDate) { + return; + } + + const emptySet = this.dependenciesToBeRemoved; + this.dependenciesToBeRemoved = this.dependencies; + this.dependencies = emptySet; + + this.state = AutorunState.upToDate; + + getLogger()?.handleAutorunTriggered(this); + + try { + const changeSummary = this.changeSummary!; + this.changeSummary = this.createChangeSummary?.(); + this.runFn(this, changeSummary); + } finally { + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.dependenciesToBeRemoved) { + o.removeObserver(this); + } + this.dependenciesToBeRemoved.clear(); + } } public toString(): string { return `Autorun<${this.debugName}>`; } + + // IObserver implementation + public beginUpdate(): void { + if (this.state === AutorunState.upToDate) { + this.state = AutorunState.dependenciesMightHaveChanged; + } + this.updateCount++; + } + + public endUpdate(): void { + if (this.updateCount === 1) { + do { + if (this.state === AutorunState.dependenciesMightHaveChanged) { + this.state = AutorunState.upToDate; + for (const d of this.dependencies) { + d.reportChanges(); + if (this.state as AutorunState === AutorunState.stale) { + // The other dependencies will refresh on demand + break; + } + } + } + + this._runIfNeeded(); + } while (this.state !== AutorunState.upToDate); + } + this.updateCount--; + + assertFn(() => this.updateCount >= 0); + } + + public handlePossibleChange(observable: IObservable<any>): void { + if (this.state === AutorunState.upToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + this.state = AutorunState.dependenciesMightHaveChanged; + } + } + + public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void { + if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + const shouldReact = this._handleChange ? this._handleChange({ + changedObservable: observable, + change, + didChange: o => o === observable as any, + }, this.changeSummary!) : true; + if (shouldReact) { + this.state = AutorunState.stale; + } + } + } + + // IReader implementation + public readObservable<T>(observable: IObservable<T>): T { + // In case the run action disposes the autorun + if (this.disposed) { + return observable.get(); + } + + observable.addObserver(this); + const value = observable.get(); + this.dependencies.add(observable); + this.dependenciesToBeRemoved.delete(observable); + return value; + } } export namespace autorun { diff --git a/src/vs/base/common/observableImpl/base.ts b/src/vs/base/common/observableImpl/base.ts index 02448c47ff..a502cd05ad 100644 --- a/src/vs/base/common/observableImpl/base.ts +++ b/src/vs/base/common/observableImpl/base.ts @@ -3,69 +3,118 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IDisposable } from 'vs/base/common/lifecycle'; import type { derived } from 'vs/base/common/observableImpl/derived'; import { getLogger } from 'vs/base/common/observableImpl/logging'; -export interface IObservable<T, TChange = void> { - readonly TChange: TChange; - +/** + * Represents an observable value. + * @template T The type of the value. + * @template TChange The type of delta information (usually `void` and only used in advanced scenarios). + */ +export interface IObservable<T, TChange = unknown> { /** - * Reads the current value. + * Returns the current value. * - * Must not be called from {@link IObserver.handleChange}. + * Calls {@link IObserver.handleChange} if the observable notices that the value changed. + * Must not be called from {@link IObserver.handleChange}! */ get(): T; /** - * Adds an observer. + * Forces the observable to check for and report changes. + * + * Has the same effect as calling {@link IObservable.get}, but does not force the observable + * to actually construct the value, e.g. if change deltas are used. + * Calls {@link IObserver.handleChange} if the observable notices that the value changed. + * Must not be called from {@link IObserver.handleChange}! + */ + reportChanges(): void; + + /** + * Adds the observer to the set of subscribed observers. + * This method is idempotent. */ addObserver(observer: IObserver): void; + + /** + * Removes the observer from the set of subscribed observers. + * This method is idempotent. + */ removeObserver(observer: IObserver): void; /** - * Subscribes the reader to this observable and returns the current value of this observable. + * Reads the current value and subscribes to this observable. + * + * Just calls {@link IReader.readObservable} if a reader is given, otherwise {@link IObservable.get} + * (see {@link ConvenientObservable.read}). */ - read(reader: IReader): T; + read(reader: IReader | undefined): T; - map<TNew>(fn: (value: T) => TNew): IObservable<TNew>; + /** + * Creates a derived observable that depends on this observable. + * Use the reader to read other observables + * (see {@link ConvenientObservable.map}). + */ + map<TNew>(fn: (value: T, reader: IReader) => TNew): IObservable<TNew>; + /** + * A human-readable name for debugging purposes. + */ readonly debugName: string; + + /** + * This property captures the type of the change object. Do not use it at runtime! + */ + readonly TChange: TChange; } export interface IReader { /** - * Reports an observable that was read. - * - * Is called by {@link IObservable.read}. + * Reads the value of an observable and subscribes to it. */ - subscribeTo<T>(observable: IObservable<T, any>): void; + readObservable<T>(observable: IObservable<T, any>): T; } +/** + * Represents an observer that can be subscribed to an observable. + * + * If an observer is subscribed to an observable and that observable didn't signal + * a change through one of the observer methods, the observer can assume that the + * observable didn't change. + * If an observable reported a possible change, {@link IObservable.reportChanges} forces + * the observable to report an actual change if there was one. + */ export interface IObserver { /** - * Indicates that an update operation is about to begin. + * Signals that the given observable might have changed and a transaction potentially modifying that observable started. + * Before the given observable can call this method again, is must call {@link IObserver.endUpdate}. * - * During an update, invariants might not hold for subscribed observables and - * change events might be delayed. - * However, all changes must be reported before all update operations are over. + * The method {@link IObservable.reportChanges} can be used to force the observable to report the changes. */ beginUpdate<T>(observable: IObservable<T>): void; /** - * Is called by a subscribed observable immediately after it notices a change. - * - * When {@link IObservable.get} returns and no change has been reported, - * there has been no change for that observable. - * - * Implementations must not call into other observables! - * The change should be processed when {@link IObserver.endUpdate} is called. - */ - handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void; - - /** - * Indicates that an update operation has completed. + * Signals that the transaction that potentially modified the given observable ended. */ endUpdate<T>(observable: IObservable<T>): void; + + /** + * Signals that the given observable might have changed. + * The method {@link IObservable.reportChanges} can be used to force the observable to report the changes. + * + * Implementations must not call into other observables, as they might not have received this event yet! + * The change should be processed lazily or in {@link IObserver.endUpdate}. + */ + handlePossibleChange<T>(observable: IObservable<T>): void; + + /** + * Signals that the given observable changed. + * + * Implementations must not call into other observables, as they might not have received this event yet! + * The change should be processed lazily or in {@link IObserver.endUpdate}. + */ + handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void; } export interface ISettable<T, TChange = void> { @@ -74,13 +123,10 @@ export interface ISettable<T, TChange = void> { export interface ITransaction { /** - * Calls `Observer.beginUpdate` immediately - * and `Observer.endUpdate` when the transaction is complete. + * Calls {@link Observer.beginUpdate} immediately + * and {@link Observer.endUpdate} when the transaction ends. */ - updateObserver( - observer: IObserver, - observable: IObservable<any, any> - ): void; + updateObserver(observer: IObserver, observable: IObservable<any, any>): void; } let _derived: typeof derived; @@ -96,23 +142,31 @@ export abstract class ConvenientObservable<T, TChange> implements IObservable<T, get TChange(): TChange { return null!; } public abstract get(): T; + + public reportChanges(): void { + this.get(); + } + public abstract addObserver(observer: IObserver): void; public abstract removeObserver(observer: IObserver): void; /** @sealed */ - public read(reader: IReader): T { - reader.subscribeTo(this); - return this.get(); + public read(reader: IReader | undefined): T { + if (reader) { + return reader.readObservable(this); + } else { + return this.get(); + } } /** @sealed */ - public map<TNew>(fn: (value: T) => TNew): IObservable<TNew> { + public map<TNew>(fn: (value: T, reader: IReader) => TNew): IObservable<TNew> { return _derived( () => { const name = getFunctionName(fn); return name !== undefined ? name : `${this.debugName} (mapped)`; }, - (reader) => fn(this.read(reader)) + (reader) => fn(this.read(reader), reader) ); } @@ -122,7 +176,6 @@ export abstract class ConvenientObservable<T, TChange> implements IObservable<T, export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> { protected readonly observers = new Set<IObserver>(); - /** @sealed */ public addObserver(observer: IObserver): void { const len = this.observers.size; this.observers.add(observer); @@ -131,7 +184,6 @@ export abstract class BaseObservable<T, TChange = void> extends ConvenientObserv } } - /** @sealed */ public removeObserver(observer: IObserver): void { const deleted = this.observers.delete(observer); if (deleted && this.observers.size === 0) { @@ -154,13 +206,12 @@ export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => } } -export function getFunctionName(fn: Function): string | undefined { - const fnSrc = fn.toString(); - // Pattern: /** @description ... */ - const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; - const match = regexp.exec(fnSrc); - const result = match ? match[1] : undefined; - return result?.trim(); +export function subtransaction(tx: ITransaction | undefined, fn: (tx: ITransaction) => void, getDebugName?: () => string): void { + if (!tx) { + transaction(fn, getDebugName); + } else { + fn(tx); + } } export class TransactionImpl implements ITransaction { @@ -175,10 +226,7 @@ export class TransactionImpl implements ITransaction { return getFunctionName(this.fn); } - public updateObserver( - observer: IObserver, - observable: IObservable<any> - ): void { + public updateObserver(observer: IObserver, observable: IObservable<any>): void { this.updatingObservers!.push({ observer, observable }); observer.beginUpdate(observable); } @@ -193,9 +241,22 @@ export class TransactionImpl implements ITransaction { } } +export function getFunctionName(fn: Function): string | undefined { + const fnSrc = fn.toString(); + // Pattern: /** @description ... */ + const regexp = /\/\*\*\s*@description\s*([^*]*)\*\//; + const match = regexp.exec(fnSrc); + const result = match ? match[1] : undefined; + return result?.trim(); +} + export interface ISettableObservable<T, TChange = void> extends IObservable<T, TChange>, ISettable<T, TChange> { } +/** + * Creates an observable value. + * Observers get informed when the value changes. + */ export function observableValue<T, TChange = void>(name: string, initialValue: T): ISettableObservable<T, TChange> { return new ObservableValue(name, initialValue); } @@ -204,41 +265,81 @@ export class ObservableValue<T, TChange = void> extends BaseObservable<T, TChange> implements ISettableObservable<T, TChange> { - private value: T; + protected _value: T; constructor(public readonly debugName: string, initialValue: T) { super(); - this.value = initialValue; + this._value = initialValue; } - public get(): T { - return this.value; + return this._value; } public set(value: T, tx: ITransaction | undefined, change: TChange): void { - if (this.value === value) { + if (this._value === value) { return; } + let _tx: TransactionImpl | undefined; if (!tx) { - transaction((tx) => { - this.set(value, tx, change); - }, () => `Setting ${this.debugName}`); - return; + tx = _tx = new TransactionImpl(() => { }, () => `Setting ${this.debugName}`); } + try { + const oldValue = this._value; + this._setValue(value); + getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true }); - const oldValue = this.value; - this.value = value; - getLogger()?.handleObservableChanged(this, { oldValue, newValue: value, change, didChange: true }); - - for (const observer of this.observers) { - tx.updateObserver(observer, this); - observer.handleChange(this, change); + for (const observer of this.observers) { + tx.updateObserver(observer, this); + observer.handleChange(this, change); + } + } finally { + if (_tx) { + _tx.finish(); + } } } override toString(): string { - return `${this.debugName}: ${this.value}`; + return `${this.debugName}: ${this._value}`; + } + + protected _setValue(newValue: T): void { + this._value = newValue; } } +export function disposableObservableValue<T extends IDisposable | undefined, TChange = void>(name: string, initialValue: T): ISettableObservable<T, TChange> & IDisposable { + return new DisposableObservableValue(name, initialValue); +} + +export class DisposableObservableValue<T extends IDisposable | undefined, TChange = void> extends ObservableValue<T, TChange> implements IDisposable { + protected override _setValue(newValue: T): void { + if (this._value === newValue) { + return; + } + if (this._value) { + this._value.dispose(); + } + this._value = newValue; + } + + public dispose(): void { + this._value?.dispose(); + } +} + +export interface IChangeContext { + readonly changedObservable: IObservable<any, any>; + readonly change: unknown; + + didChange<T, TChange>(observable: IObservable<T, TChange>): this is { change: TChange }; +} + +export interface IChangeTracker { + /** + * Returns if this change should cause an invalidation. + * Can record the changes to just process deltas. + */ + handleChange(context: IChangeContext): boolean; +} diff --git a/src/vs/base/common/observableImpl/derived.ts b/src/vs/base/common/observableImpl/derived.ts index cf8237d59b..d7ec76d26b 100644 --- a/src/vs/base/common/observableImpl/derived.ts +++ b/src/vs/base/common/observableImpl/derived.ts @@ -3,30 +3,55 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IReader, IObservable, BaseObservable, IObserver, _setDerived } from 'vs/base/common/observableImpl/base'; +import { BugIndicatingError } from 'vs/base/common/errors'; +import { IReader, IObservable, BaseObservable, IObserver, _setDerived, IChangeContext } from 'vs/base/common/observableImpl/base'; import { getLogger } from 'vs/base/common/observableImpl/logging'; export function derived<T>(debugName: string | (() => string), computeFn: (reader: IReader) => T): IObservable<T> { - return new Derived(debugName, computeFn); + return new Derived(debugName, computeFn, undefined, undefined); +} + +export function derivedHandleChanges<T, TChangeSummary>( + debugName: string | (() => string), + options: { + createEmptyChangeSummary: () => TChangeSummary; + handleChange: (context: IChangeContext, changeSummary: TChangeSummary) => boolean; + }, + computeFn: (reader: IReader, changeSummary: TChangeSummary) => T): IObservable<T> { + return new Derived(debugName, computeFn, options.createEmptyChangeSummary, options.handleChange); } _setDerived(derived); -export class Derived<T> extends BaseObservable<T, void> implements IReader, IObserver { - private hadValue = false; - private hasValue = false; - private value: T | undefined = undefined; - private updateCount = 0; - - private _dependencies = new Set<IObservable<any>>(); - public get dependencies(): ReadonlySet<IObservable<any>> { - return this._dependencies; - } +const enum DerivedState { + /** Initial state, no previous value, recomputation needed */ + initial = 0, /** - * Dependencies that have to be removed when {@link runFn} ran through. + * A dependency could have changed. + * We need to explicitly ask them if at least one dependency changed. */ - private staleDependencies = new Set<IObservable<any>>(); + dependenciesMightHaveChanged = 1, + + /** + * A dependency changed and we need to recompute. + * After recomputation, we need to check the previous value to see if we changed as well. + */ + stale = 2, + + /** + * No change reported, our cached value is up to date. + */ + upToDate = 3, +} + +export class Derived<T, TChangeSummary = any> extends BaseObservable<T, void> implements IReader, IObserver { + private state = DerivedState.initial; + private value: T | undefined = undefined; + private updateCount = 0; + private dependencies = new Set<IObservable<any>>(); + private dependenciesToBeRemoved = new Set<IObservable<any>>(); + private changeSummary: TChangeSummary | undefined = undefined; public override get debugName(): string { return typeof this._debugName === 'function' ? this._debugName() : this._debugName; @@ -34,10 +59,12 @@ export class Derived<T> extends BaseObservable<T, void> implements IReader, IObs constructor( private readonly _debugName: string | (() => string), - private readonly computeFn: (reader: IReader) => T + private readonly computeFn: (reader: IReader, changeSummary: TChangeSummary) => T, + private readonly createChangeSummary: (() => TChangeSummary) | undefined, + private readonly _handleChange: ((context: IChangeContext, summary: TChangeSummary) => boolean) | undefined, ) { super(); - + this.changeSummary = this.createChangeSummary?.(); getLogger()?.handleDerivedCreated(this); } @@ -46,122 +73,184 @@ export class Derived<T> extends BaseObservable<T, void> implements IReader, IObs * We are not tracking changes anymore, thus we have to assume * that our cache is invalid. */ - this.hasValue = false; - this.hadValue = false; + this.state = DerivedState.initial; this.value = undefined; - for (const d of this._dependencies) { + for (const d of this.dependencies) { d.removeObserver(this); } - this._dependencies.clear(); + this.dependencies.clear(); } - public get(): T { + public override get(): T { if (this.observers.size === 0) { - // Cache is not valid and don't refresh the cache. - // Observables should not be read in non-reactive contexts. - const result = this.computeFn(this); + // Without observers, we don't know when to clean up stuff. + // Thus, we don't cache anything to prevent memory leaks. + const result = this.computeFn(this, this.createChangeSummary?.()!); // Clear new dependencies this.onLastObserverRemoved(); return result; + } else { + do { + if (this.state === DerivedState.dependenciesMightHaveChanged) { + // We might not get a notification for a dependency that changed while it is updating, + // thus we also have to ask all our depedencies if they changed in this case. + this.state = DerivedState.upToDate; + + for (const d of this.dependencies) { + /** might call {@link handleChange} indirectly, which could invalidate us */ + d.reportChanges(); + + if (this.state as DerivedState === DerivedState.stale) { + // The other dependencies will refresh on demand, so early break + break; + } + } + } + + this._recomputeIfNeeded(); + // In case recomputation changed one of our dependencies, we need to recompute again. + } while (this.state !== DerivedState.upToDate); + return this.value!; + } + } + + private _recomputeIfNeeded() { + if (this.state === DerivedState.upToDate) { + return; + } + const emptySet = this.dependenciesToBeRemoved; + this.dependenciesToBeRemoved = this.dependencies; + this.dependencies = emptySet; + + const hadValue = this.state !== DerivedState.initial; + const oldValue = this.value; + this.state = DerivedState.upToDate; + + const changeSummary = this.changeSummary!; + this.changeSummary = this.createChangeSummary?.(); + try { + /** might call {@link handleChange} indirectly, which could invalidate us */ + this.value = this.computeFn(this, changeSummary); + } finally { + // We don't want our observed observables to think that they are (not even temporarily) not being observed. + // Thus, we only unsubscribe from observables that are definitely not read anymore. + for (const o of this.dependenciesToBeRemoved) { + o.removeObserver(this); + } + this.dependenciesToBeRemoved.clear(); } - if (this.updateCount > 0 && this.hasValue) { - // Refresh dependencies - for (const d of this._dependencies) { - // Maybe `.get()` triggers `handleChange`? - d.get(); - if (!this.hasValue) { - // The other dependencies will refresh on demand - break; - } + const didChange = hadValue && oldValue !== this.value; + + getLogger()?.handleDerivedRecomputed(this, { + oldValue, + newValue: this.value, + change: undefined, + didChange + }); + + if (didChange) { + for (const r of this.observers) { + r.handleChange(this, undefined); } } + } - if (!this.hasValue) { - const emptySet = this.staleDependencies; - this.staleDependencies = this._dependencies; - this._dependencies = emptySet; - - const oldValue = this.value; - try { - this.value = this.computeFn(this); - } finally { - // We don't want our observed observables to think that they are (not even temporarily) not being observed. - // Thus, we only unsubscribe from observables that are definitely not read anymore. - for (const o of this.staleDependencies) { - o.removeObserver(this); - } - this.staleDependencies.clear(); - } - - this.hasValue = true; - const didChange = this.hadValue && oldValue !== this.value; - getLogger()?.handleDerivedRecomputed(this, { - oldValue, - newValue: this.value, - change: undefined, - didChange - }); - if (didChange) { - for (const r of this.observers) { - r.handleChange(this, undefined); - } - } - } - return this.value!; + public override toString(): string { + return `LazyDerived<${this.debugName}>`; } // IObserver Implementation - public beginUpdate(): void { - if (this.updateCount === 0) { - for (const r of this.observers) { - r.beginUpdate(this); + public beginUpdate<T>(_observable: IObservable<T>): void { + this.updateCount++; + const propagateBeginUpdate = this.updateCount === 1; + if (this.state === DerivedState.upToDate) { + this.state = DerivedState.dependenciesMightHaveChanged; + // If we propagate begin update, that will already signal a possible change. + if (!propagateBeginUpdate) { + for (const r of this.observers) { + r.handlePossibleChange(this); + } + } + } + if (propagateBeginUpdate) { + for (const r of this.observers) { + r.beginUpdate(this); // This signals a possible change } } - this.updateCount++; } - public handleChange<T, TChange>( - _observable: IObservable<T, TChange>, - _change: TChange - ): void { - if (this.hasValue) { - this.hadValue = true; - this.hasValue = false; - } - - // Not in transaction: Recompute & inform observers immediately - if (this.updateCount === 0 && this.observers.size > 0) { - this.get(); - } - - // Otherwise, recompute in `endUpdate` or on demand. - } - - public endUpdate(): void { + public endUpdate<T>(_observable: IObservable<T>): void { this.updateCount--; if (this.updateCount === 0) { - if (this.observers.size > 0) { - // Propagate invalidation - this.get(); - } - - for (const r of this.observers) { + // End update could change the observer list. + const observers = [...this.observers]; + for (const r of observers) { r.endUpdate(this); } } + if (this.updateCount < 0) { + throw new BugIndicatingError(); + } + } + + public handlePossibleChange<T>(observable: IObservable<T, unknown>): void { + // In all other states, observers already know that we might have changed. + if (this.state === DerivedState.upToDate && this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + this.state = DerivedState.dependenciesMightHaveChanged; + for (const r of this.observers) { + r.handlePossibleChange(this); + } + } + } + + public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void { + if (this.dependencies.has(observable) && !this.dependenciesToBeRemoved.has(observable)) { + const shouldReact = this._handleChange ? this._handleChange({ + changedObservable: observable, + change, + didChange: o => o === observable as any, + }, this.changeSummary!) : true; + const wasUpToDate = this.state === DerivedState.upToDate; + if (shouldReact && (this.state === DerivedState.dependenciesMightHaveChanged || wasUpToDate)) { + this.state = DerivedState.stale; + if (wasUpToDate) { + for (const r of this.observers) { + r.handlePossibleChange(this); + } + } + } + } } // IReader Implementation - public subscribeTo<T>(observable: IObservable<T>) { - this._dependencies.add(observable); - // We are already added as observer for stale dependencies. - if (!this.staleDependencies.delete(observable)) { - observable.addObserver(this); + public readObservable<T>(observable: IObservable<T>): T { + // Subscribe before getting the value to enable caching + observable.addObserver(this); + /** This might call {@link handleChange} indirectly, which could invalidate us */ + const value = observable.get(); + // Which is why we only add the observable to the dependencies now. + this.dependencies.add(observable); + this.dependenciesToBeRemoved.delete(observable); + return value; + } + + public override addObserver(observer: IObserver): void { + const shouldCallBeginUpdate = !this.observers.has(observer) && this.updateCount > 0; + super.addObserver(observer); + + if (shouldCallBeginUpdate) { + observer.beginUpdate(this); } } - override toString(): string { - return `LazyDerived<${this.debugName}>`; + public override removeObserver(observer: IObserver): void { + const shouldCallEndUpdate = this.observers.has(observer) && this.updateCount > 0; + super.removeObserver(observer); + + if (shouldCallEndUpdate) { + // Calling end update after removing the observer makes sure endUpdate cannot be called twice here. + observer.endUpdate(this); + } } } diff --git a/src/vs/base/common/observableImpl/utils.ts b/src/vs/base/common/observableImpl/utils.ts index 242594e7eb..36f8b1f6a9 100644 --- a/src/vs/base/common/observableImpl/utils.ts +++ b/src/vs/base/common/observableImpl/utils.ts @@ -51,13 +51,23 @@ export function waitForState<T, TState extends T>(observable: IObservable<T>, pr export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T>; export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T> { return new Promise(resolve => { + let didRun = false; + let shouldDispose = false; const d = autorun('waitForState', reader => { const currentState = observable.read(reader); if (predicate(currentState)) { - d.dispose(); + if (!didRun) { + shouldDispose = true; + } else { + d.dispose(); + } resolve(currentState); } }); + didRun = true; + if (shouldDispose) { + d.dispose(); + } }); } @@ -96,7 +106,7 @@ export class FromEventObservable<TArgs, T> extends BaseObservable<T> { private readonly handleEvent = (args: TArgs | undefined) => { const newValue = this.getValue(args); - const didChange = this.value !== newValue; + const didChange = !this.hasValue || this.value !== newValue; getLogger()?.handleFromEventObservableTriggered(this, { oldValue: this.value, newValue, change: undefined, didChange }); @@ -188,6 +198,45 @@ class FromEventObservableSignal extends BaseObservable<void> { } } +/** + * Creates a signal that can be triggered to invalidate observers. + */ +export function observableSignal<TDelta = void>( + debugName: string +): IObservableSignal<TDelta> { + return new ObservableSignal<TDelta>(debugName); +} + +export interface IObservableSignal<TChange> extends IObservable<void, TChange> { + trigger(tx: ITransaction | undefined, change: TChange): void; +} + +class ObservableSignal<TChange> extends BaseObservable<void, TChange> implements IObservableSignal<TChange> { + constructor( + public readonly debugName: string + ) { + super(); + } + + public trigger(tx: ITransaction | undefined, change: TChange): void { + if (!tx) { + transaction(tx => { + this.trigger(tx, change); + }, () => `Trigger signal ${this.debugName}`); + return; + } + + for (const o of this.observers) { + tx.updateObserver(o, this); + o.handleChange(this, change); + } + } + + public override get(): void { + // NO OP + } +} + export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: number, disposableStore: DisposableStore): IObservable<T | undefined> { const debouncedObservable = observableValue<T | undefined>('debounced', undefined); @@ -230,29 +279,49 @@ export function wasEventTriggeredRecently(event: Event<any>, timeoutMs: number, } /** - * This ensures the observable is kept up-to-date. - * This is useful when the observables `get` method is used. + * This ensures the observable is being observed. + * Observed observables (such as {@link derived}s) can maintain a cache, as they receive invalidation events. + * Unobserved observables are forced to recompute their value from scratch every time they are read. + * + * @param observable the observable to keep alive + * @param forceRecompute if true, the observable will be eagerly recomputed after it changed. + * Use this if recomputing the observables causes side-effects. */ -export function keepAlive(observable: IObservable<any>): IDisposable { - const o = new KeepAliveObserver(); +export function keepAlive(observable: IObservable<any>, forceRecompute?: boolean): IDisposable { + const o = new KeepAliveObserver(forceRecompute ?? false); observable.addObserver(o); + if (forceRecompute) { + observable.reportChanges(); + } + return toDisposable(() => { observable.removeObserver(o); }); } class KeepAliveObserver implements IObserver { + private counter = 0; + + constructor(private readonly forceRecompute: boolean) { } + beginUpdate<T>(observable: IObservable<T, void>): void { + this.counter++; + } + + endUpdate<T>(observable: IObservable<T, void>): void { + this.counter--; + if (this.counter === 0 && this.forceRecompute) { + observable.reportChanges(); + } + } + + handlePossibleChange<T>(observable: IObservable<T, unknown>): void { // NO OP } handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void { // NO OP } - - endUpdate<T>(observable: IObservable<T, void>): void { - // NO OP - } } export function derivedObservableWithCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> { diff --git a/src/vs/base/common/observableValue.ts b/src/vs/base/common/observableValue.ts index 5d7130bb43..9afe5f10f1 100644 --- a/src/vs/base/common/observableValue.ts +++ b/src/vs/base/common/observableValue.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; //@ts-ignore import type { IObservable } from 'vs/base/common/observable'; @@ -16,34 +15,3 @@ export interface IObservableValue<T> { readonly value: T; } -/** - * @deprecated Use {@link IObservable} instead. - */ -export const staticObservableValue = <T>(value: T): IObservableValue<T> => ({ - onDidChange: Event.None, - value, -}); - -/** - * @deprecated Use {@link IObservable} instead. - */ -export class MutableObservableValue<T> extends Disposable implements IObservableValue<T> { - private readonly changeEmitter = this._register(new Emitter<T>()); - - public readonly onDidChange = this.changeEmitter.event; - - public get value() { - return this._value; - } - - public set value(v: T) { - if (v !== this._value) { - this._value = v; - this.changeEmitter.fire(v); - } - } - - constructor(private _value: T) { - super(); - } -} diff --git a/src/vs/base/common/paging.ts b/src/vs/base/common/paging.ts index efb8c2260a..82b55cbc67 100644 --- a/src/vs/base/common/paging.ts +++ b/src/vs/base/common/paging.ts @@ -6,7 +6,6 @@ import { range } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; -import { isArray } from 'vs/base/common/types'; /** * A Pager is a stateless abstraction over a paged collection. @@ -65,7 +64,7 @@ export class PagedModel<T> implements IPagedModel<T> { get length(): number { return this.pager.total; } constructor(arg: IPager<T> | T[]) { - this.pager = isArray(arg) ? singlePagePager<T>(arg) : arg; + this.pager = Array.isArray(arg) ? singlePagePager<T>(arg) : arg; const totalPages = Math.ceil(this.pager.total / this.pager.pageSize); diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts index 1ea5fef30c..fe927890be 100644 --- a/src/vs/base/common/path.ts +++ b/src/vs/base/common/path.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace -// Copied from: https://github.com/nodejs/node/blob/v14.16.0/lib/path.js +// Copied from: https://github.com/nodejs/node/blob/v16.14.2/lib/path.js /** * Copyright Joyent, Inc. and other Node contributors. @@ -63,12 +63,20 @@ class ErrorInvalidArgType extends Error { } } +function validateObject(pathObject: object, name: string) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new ErrorInvalidArgType(name, 'Object', pathObject); + } +} + function validateString(value: string, name: string) { if (typeof value !== 'string') { throw new ErrorInvalidArgType(name, 'string', value); } } +const platformIsWin32 = (process.platform === 'win32'); + function isPathSeparator(code: number | undefined) { return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; } @@ -152,9 +160,7 @@ function normalizeString(path: string, allowAboveRoot: boolean, separator: strin } function _format(sep: string, pathObject: ParsedPath) { - if (pathObject === null || typeof pathObject !== 'object') { - throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); - } + validateObject(pathObject, 'pathObject'); const dir = pathObject.dir || pathObject.root; const base = pathObject.base || `${pathObject.name || ''}${pathObject.ext || ''}`; @@ -634,14 +640,10 @@ export const win32: IPath = { toNamespacedPath(path: string): string { // Note: this will *probably* throw somewhere. - if (typeof path !== 'string') { + if (typeof path !== 'string' || path.length === 0) { return path; } - if (path.length === 0) { - return ''; - } - const resolvedPath = win32.resolve(path); if (resolvedPath.length <= 2) { @@ -1069,6 +1071,21 @@ export const win32: IPath = { posix: null }; +const posixCwd = (() => { + if (platformIsWin32) { + // Converts Windows' backslash path separators to POSIX forward slashes + // and truncates any drive indicator + const regexp = /\\/g; + return () => { + const cwd = process.cwd().replace(regexp, '/'); + return cwd.slice(cwd.indexOf('/')); + }; + } + + // We're already on POSIX, no need for any transformations + return () => process.cwd(); +})(); + export const posix: IPath = { // path.resolve([from ...], to) resolve(...pathSegments: string[]): string { @@ -1076,7 +1093,7 @@ export const posix: IPath = { let resolvedAbsolute = false; for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { - const path = i >= 0 ? pathSegments[i] : process.cwd(); + const path = i >= 0 ? pathSegments[i] : posixCwd(); validateString(path, 'path'); @@ -1491,16 +1508,16 @@ export const posix: IPath = { posix.win32 = win32.win32 = win32; posix.posix = win32.posix = posix; -export const normalize = (process.platform === 'win32' ? win32.normalize : posix.normalize); -export const isAbsolute = (process.platform === 'win32' ? win32.isAbsolute : posix.isAbsolute); -export const join = (process.platform === 'win32' ? win32.join : posix.join); -export const resolve = (process.platform === 'win32' ? win32.resolve : posix.resolve); -export const relative = (process.platform === 'win32' ? win32.relative : posix.relative); -export const dirname = (process.platform === 'win32' ? win32.dirname : posix.dirname); -export const basename = (process.platform === 'win32' ? win32.basename : posix.basename); -export const extname = (process.platform === 'win32' ? win32.extname : posix.extname); -export const format = (process.platform === 'win32' ? win32.format : posix.format); -export const parse = (process.platform === 'win32' ? win32.parse : posix.parse); -export const toNamespacedPath = (process.platform === 'win32' ? win32.toNamespacedPath : posix.toNamespacedPath); -export const sep = (process.platform === 'win32' ? win32.sep : posix.sep); -export const delimiter = (process.platform === 'win32' ? win32.delimiter : posix.delimiter); +export const normalize = (platformIsWin32 ? win32.normalize : posix.normalize); +export const isAbsolute = (platformIsWin32 ? win32.isAbsolute : posix.isAbsolute); +export const join = (platformIsWin32 ? win32.join : posix.join); +export const resolve = (platformIsWin32 ? win32.resolve : posix.resolve); +export const relative = (platformIsWin32 ? win32.relative : posix.relative); +export const dirname = (platformIsWin32 ? win32.dirname : posix.dirname); +export const basename = (platformIsWin32 ? win32.basename : posix.basename); +export const extname = (platformIsWin32 ? win32.extname : posix.extname); +export const format = (platformIsWin32 ? win32.format : posix.format); +export const parse = (platformIsWin32 ? win32.parse : posix.parse); +export const toNamespacedPath = (platformIsWin32 ? win32.toNamespacedPath : posix.toNamespacedPath); +export const sep = (platformIsWin32 ? win32.sep : posix.sep); +export const delimiter = (platformIsWin32 ? win32.delimiter : posix.delimiter); diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js index 0f6ff53d27..54266150df 100644 --- a/src/vs/base/common/performance.js +++ b/src/vs/base/common/performance.js @@ -78,7 +78,7 @@ } else if (typeof process === 'object') { // node.js: use the normal polyfill but add the timeOrigin // from the node perf_hooks API as very first mark - const timeOrigin = Math.round((require.nodeRequire || require)('perf_hooks').performance.timeOrigin); + const timeOrigin = performance?.timeOrigin ?? Math.round((require.__$__nodeRequire || require)('perf_hooks').performance.timeOrigin); return _definePolyfillMarks(timeOrigin); } else { diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index e3b2cc6b66..0f9d4045ba 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -const LANGUAGE_DEFAULT = 'en'; +export const LANGUAGE_DEFAULT = 'en'; let _isWindows = false; let _isMacintosh = false; @@ -15,13 +15,16 @@ let _isWeb = false; let _isElectron = false; let _isIOS = false; let _isCI = false; +let _isMobile = false; let _locale: string | undefined = undefined; let _language: string = LANGUAGE_DEFAULT; +let _platformLocale: string = LANGUAGE_DEFAULT; let _translationsConfigFile: string | undefined = undefined; let _userAgent: string | undefined = undefined; interface NLSConfig { locale: string; + osLocale: string; availableLanguages: { [key: string]: string }; _translationsConfigFile: string; } @@ -43,6 +46,7 @@ export interface INodeProcess { env: IProcessEnvironment; versions?: { electron?: string; + chrome?: string; }; type?: string; cwd: () => string; @@ -52,6 +56,9 @@ declare const process: INodeProcess; declare const global: unknown; declare const self: unknown; +/** + * @deprecated use `globalThis` instead + */ export const globals: any = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); let nodeProcess: INodeProcess | undefined = undefined; @@ -69,6 +76,7 @@ const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer' interface INavigator { userAgent: string; maxTouchPoints?: number; + language: string; } declare const navigator: INavigator; @@ -79,6 +87,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) { _isMacintosh = _userAgent.indexOf('Macintosh') >= 0; _isIOS = (_userAgent.indexOf('Macintosh') >= 0 || _userAgent.indexOf('iPad') >= 0 || _userAgent.indexOf('iPhone') >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0; _isLinux = _userAgent.indexOf('Linux') >= 0; + _isMobile = _userAgent?.indexOf('Mobi') >= 0; _isWeb = true; const configuredLocale = nls.getConfiguredDefaultLocale( @@ -90,8 +99,8 @@ if (typeof navigator === 'object' && !isElectronRenderer) { ); _locale = configuredLocale || LANGUAGE_DEFAULT; - _language = _locale; + _platformLocale = navigator.language; } // Native environment @@ -110,6 +119,7 @@ else if (typeof nodeProcess === 'object') { const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); const resolved = nlsConfig.availableLanguages['*']; _locale = nlsConfig.locale; + _platformLocale = nlsConfig.osLocale; // VSCode's default language is 'en' _language = resolved ? resolved : LANGUAGE_DEFAULT; _translationsConfigFile = nlsConfig._translationsConfigFile; @@ -130,7 +140,9 @@ export const enum Platform { Linux, Windows } -export function PlatformToString(platform: Platform) { +export type PlatformName = 'Web' | 'Windows' | 'Mac' | 'Linux'; + +export function PlatformToString(platform: Platform): PlatformName { switch (platform) { case Platform.Web: return 'Web'; case Platform.Mac: return 'Mac'; @@ -157,6 +169,7 @@ export const isElectron = _isElectron; export const isWeb = _isWeb; export const isWebWorker = (_isWeb && typeof globals.importScripts === 'function'); export const isIOS = _isIOS; +export const isMobile = _isMobile; /** * Whether we run inside a CI environment, such as * GH actions or Azure Pipelines. @@ -200,6 +213,14 @@ export namespace Language { */ export const locale = _locale; +/** + * This will always be set to the OS/browser's locale regardless of + * what was specified by --locale. The format of the string is all + * lower case (e.g. zh-tw for Traditional Chinese). The UI is not + * necessarily shown in the provided locale. + */ +export const platformLocale = _platformLocale; + /** * The translations that are available through language packs. */ diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts index 3044b0c759..0051b65b6b 100644 --- a/src/vs/base/common/processes.ts +++ b/src/vs/base/common/processes.ts @@ -108,7 +108,7 @@ export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve }, {} as Record<string, boolean>); const keysToRemove = [ /^ELECTRON_.+$/, - /^VSCODE_(?!SHELL_LOGIN).+$/, + /^VSCODE_(?!(PORTABLE|SHELL_LOGIN|ENV_REPLACE|ENV_APPEND|ENV_PREPEND)).+$/, /^SNAP(|_.*)$/, /^GDK_PIXBUF_.+$/, ]; diff --git a/src/vs/base/common/product.ts b/src/vs/base/common/product.ts index 2ce50e78fa..9137884e75 100644 --- a/src/vs/base/common/product.ts +++ b/src/vs/base/common/product.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IStringDictionary } from 'vs/base/common/collections'; +import { PlatformName } from 'vs/base/common/platform'; export interface IBuiltInExtension { readonly name: string; @@ -12,6 +13,29 @@ export interface IBuiltInExtension { readonly metadata: any; } +export interface IProductWalkthrough { + id: string; + steps: IProductWalkthroughStep[]; +} + +export interface IProductWalkthroughStep { + id: string; + title: string; + when: string; + description: string; + media: + | { type: 'image'; path: string | { hc: string; hcLight?: string; light: string; dark: string }; altText: string } + | { type: 'svg'; path: string; altText: string } + | { type: 'markdown'; path: string }; +} + +export interface IFeaturedExtension { + readonly id: string; + readonly title: string; + readonly description: string; + readonly imagePath: string; +} + export type ConfigurationSyncStore = { url: string; insidersUrl: string; @@ -49,9 +73,12 @@ export interface IProductConfiguration { readonly dataFolderName: string; // location for extensions (e.g. ~/.vscode-insiders) readonly builtInExtensions?: IBuiltInExtension[]; + readonly walkthroughMetadata?: IProductWalkthrough[]; + readonly featuredExtensions?: IFeaturedExtension[]; readonly downloadUrl?: string; readonly updateUrl?: string; + readonly webUrl?: string; readonly webEndpointUrlTemplate?: string; readonly webviewContentExternalBaseUrlTemplate?: string; readonly target?: string; @@ -62,7 +89,6 @@ export interface IProductConfiguration { readonly tasConfig?: { endpoint: string; telemetryEventName: string; - featuresTelemetryPropertyName: string; assignmentContextTelemetryPropertyName: string; }; @@ -70,24 +96,28 @@ export interface IProductConfiguration { readonly extensionsGallery?: { readonly serviceUrl: string; + readonly servicePPEUrl?: string; + readonly searchUrl?: string; readonly itemUrl: string; readonly publisherUrl: string; readonly resourceUrlTemplate: string; readonly controlUrl: string; - readonly recommendationsUrl: string; readonly nlsBaseUrl: string; }; - readonly extensionTips?: { [id: string]: string }; - readonly extensionImportantTips?: IStringDictionary<ImportantExtensionTip>; - readonly configBasedExtensionTips?: { [id: string]: IConfigBasedExtensionTip }; - readonly exeBasedExtensionTips?: { [id: string]: IExeBasedExtensionTip }; - readonly remoteExtensionTips?: { [remoteName: string]: IRemoteExtensionTip }; - readonly extensionKeywords?: { [extension: string]: readonly string[] }; + readonly extensionRecommendations?: IStringDictionary<IExtensionRecommendations>; + readonly configBasedExtensionTips?: IStringDictionary<IConfigBasedExtensionTip>; + readonly exeBasedExtensionTips?: IStringDictionary<IExeBasedExtensionTip>; + readonly remoteExtensionTips?: IStringDictionary<IRemoteExtensionTip>; + readonly virtualWorkspaceExtensionTips?: IStringDictionary<IVirtualWorkspaceExtensionTip>; + readonly extensionKeywords?: IStringDictionary<string[]>; readonly keymapExtensionTips?: readonly string[]; readonly webExtensionTips?: readonly string[]; readonly languageExtensionTips?: readonly string[]; - readonly trustedExtensionUrlPublicKeys?: { [id: string]: string[] }; + readonly trustedExtensionUrlPublicKeys?: IStringDictionary<string[]>; + readonly trustedExtensionAuthAccess?: readonly string[]; + + readonly commandPaletteSuggestedCommandIds?: string[]; readonly recommendedExtensions: string[]; // {{SQL CARBON EDIT}} readonly recommendedExtensionsByScenario: { [area: string]: Array<string> }; // {{SQL CARBON EDIT}} @@ -114,6 +144,7 @@ export interface IProductConfiguration { }; readonly documentationUrl?: string; + readonly serverDocumentationUrl?: string; readonly releaseNotesUrl?: string; readonly keyboardShortcutsUrlMac?: string; readonly keyboardShortcutsUrlLinux?: string; @@ -126,6 +157,7 @@ export interface IProductConfiguration { readonly reportIssueUrl?: string; readonly reportMarketplaceIssueUrl?: string; readonly licenseUrl?: string; + readonly serverLicenseUrl?: string; readonly privacyStatementUrl?: string; readonly telemetryOptOutUrl?: string; // {{SQL CARBON EDIT}} add back readonly showTelemetryOptOut?: boolean; @@ -136,6 +168,9 @@ export interface IProductConfiguration { readonly serverApplicationName: string; readonly serverDataFolderName?: string; + readonly tunnelApplicationName?: string; + readonly tunnelApplicationConfig?: ITunnelApplicationConfig; + readonly npsSurveyUrl?: string; readonly cesSurveyUrl?: string; readonly surveys?: readonly ISurveyData[]; @@ -161,11 +196,44 @@ export interface IProductConfiguration { readonly 'configurationSync.store'?: ConfigurationSyncStore; readonly 'editSessions.store'?: Omit<ConfigurationSyncStore, 'insidersUrl' | 'stableUrl'>; - readonly darwinUniversalAssetId?: string; + readonly profileTemplatesUrl?: string; + + readonly commonlyUsedSettings?: string[]; } -export type ImportantExtensionTip = { name: string; languages?: string[]; pattern?: string; isExtensionPack?: boolean; whenNotInstalled?: string[] }; +export interface ITunnelApplicationConfig { + authenticationProviders: IStringDictionary<{ scopes: string[] }>; + editorWebUrl: string; + extension: IRemoteExtensionTip; +} + +export interface IExtensionRecommendations { + readonly onFileOpen: IFileOpenCondition[]; + readonly onSettingsEditorOpen?: ISettingsEditorOpenCondition; +} + +export interface ISettingsEditorOpenCondition { + readonly prerelease: boolean | string; +} + +export interface IExtensionRecommendationCondition { + readonly important?: boolean; + readonly whenInstalled?: string[]; + readonly whenNotInstalled?: string[]; +} + +export type IFileOpenCondition = IFileLanguageCondition | IFilePathCondition | IFileContentCondition; + +export interface IFileLanguageCondition extends IExtensionRecommendationCondition { + readonly languages: string[]; +} + +export interface IFilePathCondition extends IExtensionRecommendationCondition { + readonly pathGlob: string; +} + +export type IFileContentCondition = (IFileLanguageCondition | IFilePathCondition) & { readonly contentPattern: string }; export interface IAppCenterConfiguration { readonly 'win32-ia32': string; @@ -178,7 +246,13 @@ export interface IConfigBasedExtensionTip { configPath: string; configName: string; configScheme?: string; - recommendations: IStringDictionary<{ name: string; remotes?: string[]; important?: boolean; isExtensionPack?: boolean; whenNotInstalled?: string[] }>; + recommendations: IStringDictionary<{ + name: string; + contentPattern?: string; + important?: boolean; + isExtensionPack?: boolean; + whenNotInstalled?: string[]; + }>; } export interface IExeBasedExtensionTip { @@ -191,6 +265,25 @@ export interface IExeBasedExtensionTip { export interface IRemoteExtensionTip { friendlyName: string; extensionId: string; + supportedPlatforms?: PlatformName[]; + startEntry?: { + helpLink: string; + startConnectLabel: string; + startCommand: string; + priority: number; + }; +} + +export interface IVirtualWorkspaceExtensionTip { + friendlyName: string; + extensionId: string; + supportedPlatforms?: PlatformName[]; + startEntry: { + helpLink: string; + startConnectLabel: string; + startCommand: string; + priority: number; + }; } export interface ISurveyData { diff --git a/src/vs/base/common/resourceTree.ts b/src/vs/base/common/resourceTree.ts index 8854e47a06..cd1ab75717 100644 --- a/src/vs/base/common/resourceTree.ts +++ b/src/vs/base/common/resourceTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { memoize } from 'vs/base/common/decorators'; -import { PathIterator } from 'vs/base/common/map'; +import { PathIterator } from 'vs/base/common/ternarySearchTree'; import * as paths from 'vs/base/common/path'; import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/base/common/scrollable.ts b/src/vs/base/common/scrollable.ts index 576d463636..0f7f58e72c 100644 --- a/src/vs/base/common/scrollable.ts +++ b/src/vs/base/common/scrollable.ts @@ -348,6 +348,10 @@ export class Scrollable extends Disposable { }); } + public hasPendingScrollAnimation(): boolean { + return Boolean(this._smoothScrolling); + } + private _performSmoothScrolling(): void { if (!this._smoothScrolling) { return; @@ -404,7 +408,7 @@ export class SmoothScrollingUpdate { } -export interface IAnimation { +interface IAnimation { (completion: number): number; } diff --git a/src/vs/base/common/sequence.ts b/src/vs/base/common/sequence.ts index f0e49ba360..d70f9d66e7 100644 --- a/src/vs/base/common/sequence.ts +++ b/src/vs/base/common/sequence.ts @@ -4,16 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; export interface ISplice<T> { readonly start: number; readonly deleteCount: number; - readonly toInsert: T[]; + readonly toInsert: readonly T[]; } export interface ISpliceable<T> { - splice(start: number, deleteCount: number, toInsert: T[]): void; + splice(start: number, deleteCount: number, toInsert: readonly T[]): void; } export interface ISequence<T> { @@ -28,31 +27,8 @@ export class Sequence<T> implements ISequence<T>, ISpliceable<T> { private readonly _onDidSplice = new Emitter<ISplice<T>>(); readonly onDidSplice: Event<ISplice<T>> = this._onDidSplice.event; - splice(start: number, deleteCount: number, toInsert: T[] = []): void { + splice(start: number, deleteCount: number, toInsert: readonly T[] = []): void { this.elements.splice(start, deleteCount, ...toInsert); this._onDidSplice.fire({ start, deleteCount, toInsert }); } } - -export class SimpleSequence<T> implements ISequence<T> { - - private _elements: T[]; - get elements(): T[] { return this._elements; } - - readonly onDidSplice: Event<ISplice<T>>; - private disposable: IDisposable; - - constructor(elements: T[], onDidAdd: Event<T>, onDidRemove: Event<T>) { - this._elements = [...elements]; - this.onDidSplice = Event.any( - Event.map(onDidAdd, e => ({ start: this.elements.length, deleteCount: 0, toInsert: [e] })), - Event.map(Event.filter(Event.map(onDidRemove, e => this.elements.indexOf(e)), i => i > -1), i => ({ start: i, deleteCount: 1, toInsert: [] })) - ); - - this.disposable = this.onDidSplice(({ start, deleteCount, toInsert }) => this._elements.splice(start, deleteCount, ...toInsert)); - } - - dispose(): void { - this.disposable.dispose(); - } -} diff --git a/src/vs/base/common/stopwatch.ts b/src/vs/base/common/stopwatch.ts index f6fe509128..fd357f2c9e 100644 --- a/src/vs/base/common/stopwatch.ts +++ b/src/vs/base/common/stopwatch.ts @@ -27,6 +27,11 @@ export class StopWatch { this._stopTime = this._now(); } + public reset(): void { + this._startTime = this._now(); + this._stopTime = -1; + } + public elapsed(): number { if (this._stopTime !== -1) { return this._stopTime - this._startTime; diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 6d8d9e06fc..a7f7d0658f 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -600,15 +600,31 @@ export function getCharContainingOffset(str: string, offset: number): [number, n return [startOffset, endOffset]; } -/** - * Generated using https://github.com/alexdima/unicode-utils/blob/main/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\u07FE-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u088E\u08A0-\u08C9\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDC7\uFDF0-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE35\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDD23\uDE80-\uDEA9\uDEAD-\uDF45\uDF51-\uDF81\uDF86-\uDFF6]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD4B-\uDFFF]|\uD83B[\uDC00-\uDEBB])/; +export function charCount(str: string): number { + const iterator = new GraphemeIterator(str); + let length = 0; + while (!iterator.eol()) { + length++; + iterator.nextGraphemeLength(); + } + return length; +} + +let CONTAINS_RTL: RegExp | undefined = undefined; + +function makeContainsRtl() { + // Generated using https://github.com/alexdima/unicode-utils/blob/main/rtl-test.js + return /(?:[\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\u07FE-\u0815\u081A\u0824\u0828\u0830-\u0858\u085E-\u088E\u08A0-\u08C9\u200F\uFB1D\uFB1F-\uFB28\uFB2A-\uFD3D\uFD50-\uFDC7\uFDF0-\uFDFC\uFE70-\uFEFC]|\uD802[\uDC00-\uDD1B\uDD20-\uDE00\uDE10-\uDE35\uDE40-\uDEE4\uDEEB-\uDF35\uDF40-\uDFFF]|\uD803[\uDC00-\uDD23\uDE80-\uDEA9\uDEAD-\uDF45\uDF51-\uDF81\uDF86-\uDFF6]|\uD83A[\uDC00-\uDCCF\uDD00-\uDD43\uDD4B-\uDFFF]|\uD83B[\uDC00-\uDEBB])/; +} /** * Returns true if `str` contains any Unicode character that is classified as "R" or "AL". */ export function containsRTL(str: string): boolean { + if (!CONTAINS_RTL) { + CONTAINS_RTL = makeContainsRtl(); + } + return CONTAINS_RTL.test(str); } @@ -711,17 +727,15 @@ export function lcut(text: string, n: number) { return text.substring(i).replace(/^\s/, ''); } -// Escape codes -// http://en.wikipedia.org/wiki/ANSI_escape_code -const EL = /\x1B\x5B[12]?K/g; // Erase in line -const COLOR_START = /\x1b\[\d+m/g; // Color -const COLOR_END = /\x1b\[0?m/g; // Color +// Escape codes, compiled from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ +const CSI_SEQUENCE = /(:?\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~]/g; + +// Plus additional markers for custom `\x1b]...\x07` instructions. +const CSI_CUSTOM_SEQUENCE = /\x1b\].*?\x07/g; export function removeAnsiEscapeCodes(str: string): string { if (str) { - str = str.replace(EL, ''); - str = str.replace(COLOR_START, ''); - str = str.replace(COLOR_END, ''); + str = str.replace(CSI_SEQUENCE, '').replace(CSI_CUSTOM_SEQUENCE, ''); } return str; @@ -1087,7 +1101,7 @@ export class AmbiguousCharacters { // Generated using https://github.com/hediet/vscode-unicode-data // Stored as key1, value1, key2, value2, ... return JSON.parse( - '{\"_common\":[8232,32,8233,32,5760,32,8192,32,8193,32,8194,32,8195,32,8196,32,8197,32,8198,32,8200,32,8201,32,8202,32,8287,32,8199,32,8239,32,2042,95,65101,95,65102,95,65103,95,8208,45,8209,45,8210,45,65112,45,1748,45,8259,45,727,45,8722,45,10134,45,11450,45,1549,44,1643,44,8218,44,184,44,42233,44,894,59,2307,58,2691,58,1417,58,1795,58,1796,58,5868,58,65072,58,6147,58,6153,58,8282,58,1475,58,760,58,42889,58,8758,58,720,58,42237,58,451,33,11601,33,660,63,577,63,2429,63,5038,63,42731,63,119149,46,8228,46,1793,46,1794,46,42510,46,68176,46,1632,46,1776,46,42232,46,1373,96,65287,96,8219,96,8242,96,1370,96,1523,96,8175,96,65344,96,900,96,8189,96,8125,96,8127,96,8190,96,697,96,884,96,712,96,714,96,715,96,756,96,699,96,701,96,700,96,702,96,42892,96,1497,96,2036,96,2037,96,5194,96,5836,96,94033,96,94034,96,65339,91,10088,40,10098,40,12308,40,64830,40,65341,93,10089,41,10099,41,12309,41,64831,41,10100,123,119060,123,10101,125,65342,94,8270,42,1645,42,8727,42,66335,42,5941,47,8257,47,8725,47,8260,47,9585,47,10187,47,10744,47,119354,47,12755,47,12339,47,11462,47,20031,47,12035,47,65340,92,65128,92,8726,92,10189,92,10741,92,10745,92,119311,92,119355,92,12756,92,20022,92,12034,92,42872,38,708,94,710,94,5869,43,10133,43,66203,43,8249,60,10094,60,706,60,119350,60,5176,60,5810,60,5120,61,11840,61,12448,61,42239,61,8250,62,10095,62,707,62,119351,62,5171,62,94015,62,8275,126,732,126,8128,126,8764,126,65372,124,65293,45,120784,50,120794,50,120804,50,120814,50,120824,50,130034,50,42842,50,423,50,1000,50,42564,50,5311,50,42735,50,119302,51,120785,51,120795,51,120805,51,120815,51,120825,51,130035,51,42923,51,540,51,439,51,42858,51,11468,51,1248,51,94011,51,71882,51,120786,52,120796,52,120806,52,120816,52,120826,52,130036,52,5070,52,71855,52,120787,53,120797,53,120807,53,120817,53,120827,53,130037,53,444,53,71867,53,120788,54,120798,54,120808,54,120818,54,120828,54,130038,54,11474,54,5102,54,71893,54,119314,55,120789,55,120799,55,120809,55,120819,55,120829,55,130039,55,66770,55,71878,55,2819,56,2538,56,2666,56,125131,56,120790,56,120800,56,120810,56,120820,56,120830,56,130040,56,547,56,546,56,66330,56,2663,57,2920,57,2541,57,3437,57,120791,57,120801,57,120811,57,120821,57,120831,57,130041,57,42862,57,11466,57,71884,57,71852,57,71894,57,9082,97,65345,97,119834,97,119886,97,119938,97,119990,97,120042,97,120094,97,120146,97,120198,97,120250,97,120302,97,120354,97,120406,97,120458,97,593,97,945,97,120514,97,120572,97,120630,97,120688,97,120746,97,65313,65,119808,65,119860,65,119912,65,119964,65,120016,65,120068,65,120120,65,120172,65,120224,65,120276,65,120328,65,120380,65,120432,65,913,65,120488,65,120546,65,120604,65,120662,65,120720,65,5034,65,5573,65,42222,65,94016,65,66208,65,119835,98,119887,98,119939,98,119991,98,120043,98,120095,98,120147,98,120199,98,120251,98,120303,98,120355,98,120407,98,120459,98,388,98,5071,98,5234,98,5551,98,65314,66,8492,66,119809,66,119861,66,119913,66,120017,66,120069,66,120121,66,120173,66,120225,66,120277,66,120329,66,120381,66,120433,66,42932,66,914,66,120489,66,120547,66,120605,66,120663,66,120721,66,5108,66,5623,66,42192,66,66178,66,66209,66,66305,66,65347,99,8573,99,119836,99,119888,99,119940,99,119992,99,120044,99,120096,99,120148,99,120200,99,120252,99,120304,99,120356,99,120408,99,120460,99,7428,99,1010,99,11429,99,43951,99,66621,99,128844,67,71922,67,71913,67,65315,67,8557,67,8450,67,8493,67,119810,67,119862,67,119914,67,119966,67,120018,67,120174,67,120226,67,120278,67,120330,67,120382,67,120434,67,1017,67,11428,67,5087,67,42202,67,66210,67,66306,67,66581,67,66844,67,8574,100,8518,100,119837,100,119889,100,119941,100,119993,100,120045,100,120097,100,120149,100,120201,100,120253,100,120305,100,120357,100,120409,100,120461,100,1281,100,5095,100,5231,100,42194,100,8558,68,8517,68,119811,68,119863,68,119915,68,119967,68,120019,68,120071,68,120123,68,120175,68,120227,68,120279,68,120331,68,120383,68,120435,68,5024,68,5598,68,5610,68,42195,68,8494,101,65349,101,8495,101,8519,101,119838,101,119890,101,119942,101,120046,101,120098,101,120150,101,120202,101,120254,101,120306,101,120358,101,120410,101,120462,101,43826,101,1213,101,8959,69,65317,69,8496,69,119812,69,119864,69,119916,69,120020,69,120072,69,120124,69,120176,69,120228,69,120280,69,120332,69,120384,69,120436,69,917,69,120492,69,120550,69,120608,69,120666,69,120724,69,11577,69,5036,69,42224,69,71846,69,71854,69,66182,69,119839,102,119891,102,119943,102,119995,102,120047,102,120099,102,120151,102,120203,102,120255,102,120307,102,120359,102,120411,102,120463,102,43829,102,42905,102,383,102,7837,102,1412,102,119315,70,8497,70,119813,70,119865,70,119917,70,120021,70,120073,70,120125,70,120177,70,120229,70,120281,70,120333,70,120385,70,120437,70,42904,70,988,70,120778,70,5556,70,42205,70,71874,70,71842,70,66183,70,66213,70,66853,70,65351,103,8458,103,119840,103,119892,103,119944,103,120048,103,120100,103,120152,103,120204,103,120256,103,120308,103,120360,103,120412,103,120464,103,609,103,7555,103,397,103,1409,103,119814,71,119866,71,119918,71,119970,71,120022,71,120074,71,120126,71,120178,71,120230,71,120282,71,120334,71,120386,71,120438,71,1292,71,5056,71,5107,71,42198,71,65352,104,8462,104,119841,104,119945,104,119997,104,120049,104,120101,104,120153,104,120205,104,120257,104,120309,104,120361,104,120413,104,120465,104,1211,104,1392,104,5058,104,65320,72,8459,72,8460,72,8461,72,119815,72,119867,72,119919,72,120023,72,120179,72,120231,72,120283,72,120335,72,120387,72,120439,72,919,72,120494,72,120552,72,120610,72,120668,72,120726,72,11406,72,5051,72,5500,72,42215,72,66255,72,731,105,9075,105,65353,105,8560,105,8505,105,8520,105,119842,105,119894,105,119946,105,119998,105,120050,105,120102,105,120154,105,120206,105,120258,105,120310,105,120362,105,120414,105,120466,105,120484,105,618,105,617,105,953,105,8126,105,890,105,120522,105,120580,105,120638,105,120696,105,120754,105,1110,105,42567,105,1231,105,43893,105,5029,105,71875,105,65354,106,8521,106,119843,106,119895,106,119947,106,119999,106,120051,106,120103,106,120155,106,120207,106,120259,106,120311,106,120363,106,120415,106,120467,106,1011,106,1112,106,65322,74,119817,74,119869,74,119921,74,119973,74,120025,74,120077,74,120129,74,120181,74,120233,74,120285,74,120337,74,120389,74,120441,74,42930,74,895,74,1032,74,5035,74,5261,74,42201,74,119844,107,119896,107,119948,107,120000,107,120052,107,120104,107,120156,107,120208,107,120260,107,120312,107,120364,107,120416,107,120468,107,8490,75,65323,75,119818,75,119870,75,119922,75,119974,75,120026,75,120078,75,120130,75,120182,75,120234,75,120286,75,120338,75,120390,75,120442,75,922,75,120497,75,120555,75,120613,75,120671,75,120729,75,11412,75,5094,75,5845,75,42199,75,66840,75,1472,108,8739,73,9213,73,65512,73,1633,108,1777,73,66336,108,125127,108,120783,73,120793,73,120803,73,120813,73,120823,73,130033,73,65321,73,8544,73,8464,73,8465,73,119816,73,119868,73,119920,73,120024,73,120128,73,120180,73,120232,73,120284,73,120336,73,120388,73,120440,73,65356,108,8572,73,8467,108,119845,108,119897,108,119949,108,120001,108,120053,108,120105,73,120157,73,120209,73,120261,73,120313,73,120365,73,120417,73,120469,73,448,73,120496,73,120554,73,120612,73,120670,73,120728,73,11410,73,1030,73,1216,73,1493,108,1503,108,1575,108,126464,108,126592,108,65166,108,65165,108,1994,108,11599,73,5825,73,42226,73,93992,73,66186,124,66313,124,119338,76,8556,76,8466,76,119819,76,119871,76,119923,76,120027,76,120079,76,120131,76,120183,76,120235,76,120287,76,120339,76,120391,76,120443,76,11472,76,5086,76,5290,76,42209,76,93974,76,71843,76,71858,76,66587,76,66854,76,65325,77,8559,77,8499,77,119820,77,119872,77,119924,77,120028,77,120080,77,120132,77,120184,77,120236,77,120288,77,120340,77,120392,77,120444,77,924,77,120499,77,120557,77,120615,77,120673,77,120731,77,1018,77,11416,77,5047,77,5616,77,5846,77,42207,77,66224,77,66321,77,119847,110,119899,110,119951,110,120003,110,120055,110,120107,110,120159,110,120211,110,120263,110,120315,110,120367,110,120419,110,120471,110,1400,110,1404,110,65326,78,8469,78,119821,78,119873,78,119925,78,119977,78,120029,78,120081,78,120185,78,120237,78,120289,78,120341,78,120393,78,120445,78,925,78,120500,78,120558,78,120616,78,120674,78,120732,78,11418,78,42208,78,66835,78,3074,111,3202,111,3330,111,3458,111,2406,111,2662,111,2790,111,3046,111,3174,111,3302,111,3430,111,3664,111,3792,111,4160,111,1637,111,1781,111,65359,111,8500,111,119848,111,119900,111,119952,111,120056,111,120108,111,120160,111,120212,111,120264,111,120316,111,120368,111,120420,111,120472,111,7439,111,7441,111,43837,111,959,111,120528,111,120586,111,120644,111,120702,111,120760,111,963,111,120532,111,120590,111,120648,111,120706,111,120764,111,11423,111,4351,111,1413,111,1505,111,1607,111,126500,111,126564,111,126596,111,65259,111,65260,111,65258,111,65257,111,1726,111,64428,111,64429,111,64427,111,64426,111,1729,111,64424,111,64425,111,64423,111,64422,111,1749,111,3360,111,4125,111,66794,111,71880,111,71895,111,66604,111,1984,79,2534,79,2918,79,12295,79,70864,79,71904,79,120782,79,120792,79,120802,79,120812,79,120822,79,130032,79,65327,79,119822,79,119874,79,119926,79,119978,79,120030,79,120082,79,120134,79,120186,79,120238,79,120290,79,120342,79,120394,79,120446,79,927,79,120502,79,120560,79,120618,79,120676,79,120734,79,11422,79,1365,79,11604,79,4816,79,2848,79,66754,79,42227,79,71861,79,66194,79,66219,79,66564,79,66838,79,9076,112,65360,112,119849,112,119901,112,119953,112,120005,112,120057,112,120109,112,120161,112,120213,112,120265,112,120317,112,120369,112,120421,112,120473,112,961,112,120530,112,120544,112,120588,112,120602,112,120646,112,120660,112,120704,112,120718,112,120762,112,120776,112,11427,112,65328,80,8473,80,119823,80,119875,80,119927,80,119979,80,120031,80,120083,80,120187,80,120239,80,120291,80,120343,80,120395,80,120447,80,929,80,120504,80,120562,80,120620,80,120678,80,120736,80,11426,80,5090,80,5229,80,42193,80,66197,80,119850,113,119902,113,119954,113,120006,113,120058,113,120110,113,120162,113,120214,113,120266,113,120318,113,120370,113,120422,113,120474,113,1307,113,1379,113,1382,113,8474,81,119824,81,119876,81,119928,81,119980,81,120032,81,120084,81,120188,81,120240,81,120292,81,120344,81,120396,81,120448,81,11605,81,119851,114,119903,114,119955,114,120007,114,120059,114,120111,114,120163,114,120215,114,120267,114,120319,114,120371,114,120423,114,120475,114,43847,114,43848,114,7462,114,11397,114,43905,114,119318,82,8475,82,8476,82,8477,82,119825,82,119877,82,119929,82,120033,82,120189,82,120241,82,120293,82,120345,82,120397,82,120449,82,422,82,5025,82,5074,82,66740,82,5511,82,42211,82,94005,82,65363,115,119852,115,119904,115,119956,115,120008,115,120060,115,120112,115,120164,115,120216,115,120268,115,120320,115,120372,115,120424,115,120476,115,42801,115,445,115,1109,115,43946,115,71873,115,66632,115,65331,83,119826,83,119878,83,119930,83,119982,83,120034,83,120086,83,120138,83,120190,83,120242,83,120294,83,120346,83,120398,83,120450,83,1029,83,1359,83,5077,83,5082,83,42210,83,94010,83,66198,83,66592,83,119853,116,119905,116,119957,116,120009,116,120061,116,120113,116,120165,116,120217,116,120269,116,120321,116,120373,116,120425,116,120477,116,8868,84,10201,84,128872,84,65332,84,119827,84,119879,84,119931,84,119983,84,120035,84,120087,84,120139,84,120191,84,120243,84,120295,84,120347,84,120399,84,120451,84,932,84,120507,84,120565,84,120623,84,120681,84,120739,84,11430,84,5026,84,42196,84,93962,84,71868,84,66199,84,66225,84,66325,84,119854,117,119906,117,119958,117,120010,117,120062,117,120114,117,120166,117,120218,117,120270,117,120322,117,120374,117,120426,117,120478,117,42911,117,7452,117,43854,117,43858,117,651,117,965,117,120534,117,120592,117,120650,117,120708,117,120766,117,1405,117,66806,117,71896,117,8746,85,8899,85,119828,85,119880,85,119932,85,119984,85,120036,85,120088,85,120140,85,120192,85,120244,85,120296,85,120348,85,120400,85,120452,85,1357,85,4608,85,66766,85,5196,85,42228,85,94018,85,71864,85,8744,118,8897,118,65366,118,8564,118,119855,118,119907,118,119959,118,120011,118,120063,118,120115,118,120167,118,120219,118,120271,118,120323,118,120375,118,120427,118,120479,118,7456,118,957,118,120526,118,120584,118,120642,118,120700,118,120758,118,1141,118,1496,118,71430,118,43945,118,71872,118,119309,86,1639,86,1783,86,8548,86,119829,86,119881,86,119933,86,119985,86,120037,86,120089,86,120141,86,120193,86,120245,86,120297,86,120349,86,120401,86,120453,86,1140,86,11576,86,5081,86,5167,86,42719,86,42214,86,93960,86,71840,86,66845,86,623,119,119856,119,119908,119,119960,119,120012,119,120064,119,120116,119,120168,119,120220,119,120272,119,120324,119,120376,119,120428,119,120480,119,7457,119,1121,119,1309,119,1377,119,71434,119,71438,119,71439,119,43907,119,71919,87,71910,87,119830,87,119882,87,119934,87,119986,87,120038,87,120090,87,120142,87,120194,87,120246,87,120298,87,120350,87,120402,87,120454,87,1308,87,5043,87,5076,87,42218,87,5742,120,10539,120,10540,120,10799,120,65368,120,8569,120,119857,120,119909,120,119961,120,120013,120,120065,120,120117,120,120169,120,120221,120,120273,120,120325,120,120377,120,120429,120,120481,120,5441,120,5501,120,5741,88,9587,88,66338,88,71916,88,65336,88,8553,88,119831,88,119883,88,119935,88,119987,88,120039,88,120091,88,120143,88,120195,88,120247,88,120299,88,120351,88,120403,88,120455,88,42931,88,935,88,120510,88,120568,88,120626,88,120684,88,120742,88,11436,88,11613,88,5815,88,42219,88,66192,88,66228,88,66327,88,66855,88,611,121,7564,121,65369,121,119858,121,119910,121,119962,121,120014,121,120066,121,120118,121,120170,121,120222,121,120274,121,120326,121,120378,121,120430,121,120482,121,655,121,7935,121,43866,121,947,121,8509,121,120516,121,120574,121,120632,121,120690,121,120748,121,1199,121,4327,121,71900,121,65337,89,119832,89,119884,89,119936,89,119988,89,120040,89,120092,89,120144,89,120196,89,120248,89,120300,89,120352,89,120404,89,120456,89,933,89,978,89,120508,89,120566,89,120624,89,120682,89,120740,89,11432,89,1198,89,5033,89,5053,89,42220,89,94019,89,71844,89,66226,89,119859,122,119911,122,119963,122,120015,122,120067,122,120119,122,120171,122,120223,122,120275,122,120327,122,120379,122,120431,122,120483,122,7458,122,43923,122,71876,122,66293,90,71909,90,65338,90,8484,90,8488,90,119833,90,119885,90,119937,90,119989,90,120041,90,120197,90,120249,90,120301,90,120353,90,120405,90,120457,90,918,90,120493,90,120551,90,120609,90,120667,90,120725,90,5059,90,42204,90,71849,90,65282,34,65284,36,65285,37,65286,38,65290,42,65291,43,65294,46,65295,47,65296,48,65297,49,65298,50,65299,51,65300,52,65301,53,65302,54,65303,55,65304,56,65305,57,65308,60,65309,61,65310,62,65312,64,65316,68,65318,70,65319,71,65324,76,65329,81,65330,82,65333,85,65334,86,65335,87,65343,95,65346,98,65348,100,65350,102,65355,107,65357,109,65358,110,65361,113,65362,114,65364,116,65365,117,65367,119,65370,122,65371,123,65373,125],\"_default\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"cs\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"de\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"es\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"fr\":[65374,126,65306,58,65281,33,8216,96,8245,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"it\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ja\":[8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65292,44,65307,59],\"ko\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pl\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pt-BR\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"qps-ploc\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ru\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,305,105,921,73,1009,112,215,120,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"tr\":[160,32,8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"zh-hans\":[65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41],\"zh-hant\":[8211,45,65374,126,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65307,59]}' + '{\"_common\":[8232,32,8233,32,5760,32,8192,32,8193,32,8194,32,8195,32,8196,32,8197,32,8198,32,8200,32,8201,32,8202,32,8287,32,8199,32,8239,32,2042,95,65101,95,65102,95,65103,95,8208,45,8209,45,8210,45,65112,45,1748,45,8259,45,727,45,8722,45,10134,45,11450,45,1549,44,1643,44,8218,44,184,44,42233,44,894,59,2307,58,2691,58,1417,58,1795,58,1796,58,5868,58,65072,58,6147,58,6153,58,8282,58,1475,58,760,58,42889,58,8758,58,720,58,42237,58,451,33,11601,33,660,63,577,63,2429,63,5038,63,42731,63,119149,46,8228,46,1793,46,1794,46,42510,46,68176,46,1632,46,1776,46,42232,46,1373,96,65287,96,8219,96,8242,96,1370,96,1523,96,8175,96,65344,96,900,96,8189,96,8125,96,8127,96,8190,96,697,96,884,96,712,96,714,96,715,96,756,96,699,96,701,96,700,96,702,96,42892,96,1497,96,2036,96,2037,96,5194,96,5836,96,94033,96,94034,96,65339,91,10088,40,10098,40,12308,40,64830,40,65341,93,10089,41,10099,41,12309,41,64831,41,10100,123,119060,123,10101,125,65342,94,8270,42,1645,42,8727,42,66335,42,5941,47,8257,47,8725,47,8260,47,9585,47,10187,47,10744,47,119354,47,12755,47,12339,47,11462,47,20031,47,12035,47,65340,92,65128,92,8726,92,10189,92,10741,92,10745,92,119311,92,119355,92,12756,92,20022,92,12034,92,42872,38,708,94,710,94,5869,43,10133,43,66203,43,8249,60,10094,60,706,60,119350,60,5176,60,5810,60,5120,61,11840,61,12448,61,42239,61,8250,62,10095,62,707,62,119351,62,5171,62,94015,62,8275,126,732,126,8128,126,8764,126,65372,124,65293,45,120784,50,120794,50,120804,50,120814,50,120824,50,130034,50,42842,50,423,50,1000,50,42564,50,5311,50,42735,50,119302,51,120785,51,120795,51,120805,51,120815,51,120825,51,130035,51,42923,51,540,51,439,51,42858,51,11468,51,1248,51,94011,51,71882,51,120786,52,120796,52,120806,52,120816,52,120826,52,130036,52,5070,52,71855,52,120787,53,120797,53,120807,53,120817,53,120827,53,130037,53,444,53,71867,53,120788,54,120798,54,120808,54,120818,54,120828,54,130038,54,11474,54,5102,54,71893,54,119314,55,120789,55,120799,55,120809,55,120819,55,120829,55,130039,55,66770,55,71878,55,2819,56,2538,56,2666,56,125131,56,120790,56,120800,56,120810,56,120820,56,120830,56,130040,56,547,56,546,56,66330,56,2663,57,2920,57,2541,57,3437,57,120791,57,120801,57,120811,57,120821,57,120831,57,130041,57,42862,57,11466,57,71884,57,71852,57,71894,57,9082,97,65345,97,119834,97,119886,97,119938,97,119990,97,120042,97,120094,97,120146,97,120198,97,120250,97,120302,97,120354,97,120406,97,120458,97,593,97,945,97,120514,97,120572,97,120630,97,120688,97,120746,97,65313,65,119808,65,119860,65,119912,65,119964,65,120016,65,120068,65,120120,65,120172,65,120224,65,120276,65,120328,65,120380,65,120432,65,913,65,120488,65,120546,65,120604,65,120662,65,120720,65,5034,65,5573,65,42222,65,94016,65,66208,65,119835,98,119887,98,119939,98,119991,98,120043,98,120095,98,120147,98,120199,98,120251,98,120303,98,120355,98,120407,98,120459,98,388,98,5071,98,5234,98,5551,98,65314,66,8492,66,119809,66,119861,66,119913,66,120017,66,120069,66,120121,66,120173,66,120225,66,120277,66,120329,66,120381,66,120433,66,42932,66,914,66,120489,66,120547,66,120605,66,120663,66,120721,66,5108,66,5623,66,42192,66,66178,66,66209,66,66305,66,65347,99,8573,99,119836,99,119888,99,119940,99,119992,99,120044,99,120096,99,120148,99,120200,99,120252,99,120304,99,120356,99,120408,99,120460,99,7428,99,1010,99,11429,99,43951,99,66621,99,128844,67,71922,67,71913,67,65315,67,8557,67,8450,67,8493,67,119810,67,119862,67,119914,67,119966,67,120018,67,120174,67,120226,67,120278,67,120330,67,120382,67,120434,67,1017,67,11428,67,5087,67,42202,67,66210,67,66306,67,66581,67,66844,67,8574,100,8518,100,119837,100,119889,100,119941,100,119993,100,120045,100,120097,100,120149,100,120201,100,120253,100,120305,100,120357,100,120409,100,120461,100,1281,100,5095,100,5231,100,42194,100,8558,68,8517,68,119811,68,119863,68,119915,68,119967,68,120019,68,120071,68,120123,68,120175,68,120227,68,120279,68,120331,68,120383,68,120435,68,5024,68,5598,68,5610,68,42195,68,8494,101,65349,101,8495,101,8519,101,119838,101,119890,101,119942,101,120046,101,120098,101,120150,101,120202,101,120254,101,120306,101,120358,101,120410,101,120462,101,43826,101,1213,101,8959,69,65317,69,8496,69,119812,69,119864,69,119916,69,120020,69,120072,69,120124,69,120176,69,120228,69,120280,69,120332,69,120384,69,120436,69,917,69,120492,69,120550,69,120608,69,120666,69,120724,69,11577,69,5036,69,42224,69,71846,69,71854,69,66182,69,119839,102,119891,102,119943,102,119995,102,120047,102,120099,102,120151,102,120203,102,120255,102,120307,102,120359,102,120411,102,120463,102,43829,102,42905,102,383,102,7837,102,1412,102,119315,70,8497,70,119813,70,119865,70,119917,70,120021,70,120073,70,120125,70,120177,70,120229,70,120281,70,120333,70,120385,70,120437,70,42904,70,988,70,120778,70,5556,70,42205,70,71874,70,71842,70,66183,70,66213,70,66853,70,65351,103,8458,103,119840,103,119892,103,119944,103,120048,103,120100,103,120152,103,120204,103,120256,103,120308,103,120360,103,120412,103,120464,103,609,103,7555,103,397,103,1409,103,119814,71,119866,71,119918,71,119970,71,120022,71,120074,71,120126,71,120178,71,120230,71,120282,71,120334,71,120386,71,120438,71,1292,71,5056,71,5107,71,42198,71,65352,104,8462,104,119841,104,119945,104,119997,104,120049,104,120101,104,120153,104,120205,104,120257,104,120309,104,120361,104,120413,104,120465,104,1211,104,1392,104,5058,104,65320,72,8459,72,8460,72,8461,72,119815,72,119867,72,119919,72,120023,72,120179,72,120231,72,120283,72,120335,72,120387,72,120439,72,919,72,120494,72,120552,72,120610,72,120668,72,120726,72,11406,72,5051,72,5500,72,42215,72,66255,72,731,105,9075,105,65353,105,8560,105,8505,105,8520,105,119842,105,119894,105,119946,105,119998,105,120050,105,120102,105,120154,105,120206,105,120258,105,120310,105,120362,105,120414,105,120466,105,120484,105,618,105,617,105,953,105,8126,105,890,105,120522,105,120580,105,120638,105,120696,105,120754,105,1110,105,42567,105,1231,105,43893,105,5029,105,71875,105,65354,106,8521,106,119843,106,119895,106,119947,106,119999,106,120051,106,120103,106,120155,106,120207,106,120259,106,120311,106,120363,106,120415,106,120467,106,1011,106,1112,106,65322,74,119817,74,119869,74,119921,74,119973,74,120025,74,120077,74,120129,74,120181,74,120233,74,120285,74,120337,74,120389,74,120441,74,42930,74,895,74,1032,74,5035,74,5261,74,42201,74,119844,107,119896,107,119948,107,120000,107,120052,107,120104,107,120156,107,120208,107,120260,107,120312,107,120364,107,120416,107,120468,107,8490,75,65323,75,119818,75,119870,75,119922,75,119974,75,120026,75,120078,75,120130,75,120182,75,120234,75,120286,75,120338,75,120390,75,120442,75,922,75,120497,75,120555,75,120613,75,120671,75,120729,75,11412,75,5094,75,5845,75,42199,75,66840,75,1472,108,8739,73,9213,73,65512,73,1633,108,1777,73,66336,108,125127,108,120783,73,120793,73,120803,73,120813,73,120823,73,130033,73,65321,73,8544,73,8464,73,8465,73,119816,73,119868,73,119920,73,120024,73,120128,73,120180,73,120232,73,120284,73,120336,73,120388,73,120440,73,65356,108,8572,73,8467,108,119845,108,119897,108,119949,108,120001,108,120053,108,120105,73,120157,73,120209,73,120261,73,120313,73,120365,73,120417,73,120469,73,448,73,120496,73,120554,73,120612,73,120670,73,120728,73,11410,73,1030,73,1216,73,1493,108,1503,108,1575,108,126464,108,126592,108,65166,108,65165,108,1994,108,11599,73,5825,73,42226,73,93992,73,66186,124,66313,124,119338,76,8556,76,8466,76,119819,76,119871,76,119923,76,120027,76,120079,76,120131,76,120183,76,120235,76,120287,76,120339,76,120391,76,120443,76,11472,76,5086,76,5290,76,42209,76,93974,76,71843,76,71858,76,66587,76,66854,76,65325,77,8559,77,8499,77,119820,77,119872,77,119924,77,120028,77,120080,77,120132,77,120184,77,120236,77,120288,77,120340,77,120392,77,120444,77,924,77,120499,77,120557,77,120615,77,120673,77,120731,77,1018,77,11416,77,5047,77,5616,77,5846,77,42207,77,66224,77,66321,77,119847,110,119899,110,119951,110,120003,110,120055,110,120107,110,120159,110,120211,110,120263,110,120315,110,120367,110,120419,110,120471,110,1400,110,1404,110,65326,78,8469,78,119821,78,119873,78,119925,78,119977,78,120029,78,120081,78,120185,78,120237,78,120289,78,120341,78,120393,78,120445,78,925,78,120500,78,120558,78,120616,78,120674,78,120732,78,11418,78,42208,78,66835,78,3074,111,3202,111,3330,111,3458,111,2406,111,2662,111,2790,111,3046,111,3174,111,3302,111,3430,111,3664,111,3792,111,4160,111,1637,111,1781,111,65359,111,8500,111,119848,111,119900,111,119952,111,120056,111,120108,111,120160,111,120212,111,120264,111,120316,111,120368,111,120420,111,120472,111,7439,111,7441,111,43837,111,959,111,120528,111,120586,111,120644,111,120702,111,120760,111,963,111,120532,111,120590,111,120648,111,120706,111,120764,111,11423,111,4351,111,1413,111,1505,111,1607,111,126500,111,126564,111,126596,111,65259,111,65260,111,65258,111,65257,111,1726,111,64428,111,64429,111,64427,111,64426,111,1729,111,64424,111,64425,111,64423,111,64422,111,1749,111,3360,111,4125,111,66794,111,71880,111,71895,111,66604,111,1984,79,2534,79,2918,79,12295,79,70864,79,71904,79,120782,79,120792,79,120802,79,120812,79,120822,79,130032,79,65327,79,119822,79,119874,79,119926,79,119978,79,120030,79,120082,79,120134,79,120186,79,120238,79,120290,79,120342,79,120394,79,120446,79,927,79,120502,79,120560,79,120618,79,120676,79,120734,79,11422,79,1365,79,11604,79,4816,79,2848,79,66754,79,42227,79,71861,79,66194,79,66219,79,66564,79,66838,79,9076,112,65360,112,119849,112,119901,112,119953,112,120005,112,120057,112,120109,112,120161,112,120213,112,120265,112,120317,112,120369,112,120421,112,120473,112,961,112,120530,112,120544,112,120588,112,120602,112,120646,112,120660,112,120704,112,120718,112,120762,112,120776,112,11427,112,65328,80,8473,80,119823,80,119875,80,119927,80,119979,80,120031,80,120083,80,120187,80,120239,80,120291,80,120343,80,120395,80,120447,80,929,80,120504,80,120562,80,120620,80,120678,80,120736,80,11426,80,5090,80,5229,80,42193,80,66197,80,119850,113,119902,113,119954,113,120006,113,120058,113,120110,113,120162,113,120214,113,120266,113,120318,113,120370,113,120422,113,120474,113,1307,113,1379,113,1382,113,8474,81,119824,81,119876,81,119928,81,119980,81,120032,81,120084,81,120188,81,120240,81,120292,81,120344,81,120396,81,120448,81,11605,81,119851,114,119903,114,119955,114,120007,114,120059,114,120111,114,120163,114,120215,114,120267,114,120319,114,120371,114,120423,114,120475,114,43847,114,43848,114,7462,114,11397,114,43905,114,119318,82,8475,82,8476,82,8477,82,119825,82,119877,82,119929,82,120033,82,120189,82,120241,82,120293,82,120345,82,120397,82,120449,82,422,82,5025,82,5074,82,66740,82,5511,82,42211,82,94005,82,65363,115,119852,115,119904,115,119956,115,120008,115,120060,115,120112,115,120164,115,120216,115,120268,115,120320,115,120372,115,120424,115,120476,115,42801,115,445,115,1109,115,43946,115,71873,115,66632,115,65331,83,119826,83,119878,83,119930,83,119982,83,120034,83,120086,83,120138,83,120190,83,120242,83,120294,83,120346,83,120398,83,120450,83,1029,83,1359,83,5077,83,5082,83,42210,83,94010,83,66198,83,66592,83,119853,116,119905,116,119957,116,120009,116,120061,116,120113,116,120165,116,120217,116,120269,116,120321,116,120373,116,120425,116,120477,116,8868,84,10201,84,128872,84,65332,84,119827,84,119879,84,119931,84,119983,84,120035,84,120087,84,120139,84,120191,84,120243,84,120295,84,120347,84,120399,84,120451,84,932,84,120507,84,120565,84,120623,84,120681,84,120739,84,11430,84,5026,84,42196,84,93962,84,71868,84,66199,84,66225,84,66325,84,119854,117,119906,117,119958,117,120010,117,120062,117,120114,117,120166,117,120218,117,120270,117,120322,117,120374,117,120426,117,120478,117,42911,117,7452,117,43854,117,43858,117,651,117,965,117,120534,117,120592,117,120650,117,120708,117,120766,117,1405,117,66806,117,71896,117,8746,85,8899,85,119828,85,119880,85,119932,85,119984,85,120036,85,120088,85,120140,85,120192,85,120244,85,120296,85,120348,85,120400,85,120452,85,1357,85,4608,85,66766,85,5196,85,42228,85,94018,85,71864,85,8744,118,8897,118,65366,118,8564,118,119855,118,119907,118,119959,118,120011,118,120063,118,120115,118,120167,118,120219,118,120271,118,120323,118,120375,118,120427,118,120479,118,7456,118,957,118,120526,118,120584,118,120642,118,120700,118,120758,118,1141,118,1496,118,71430,118,43945,118,71872,118,119309,86,1639,86,1783,86,8548,86,119829,86,119881,86,119933,86,119985,86,120037,86,120089,86,120141,86,120193,86,120245,86,120297,86,120349,86,120401,86,120453,86,1140,86,11576,86,5081,86,5167,86,42719,86,42214,86,93960,86,71840,86,66845,86,623,119,119856,119,119908,119,119960,119,120012,119,120064,119,120116,119,120168,119,120220,119,120272,119,120324,119,120376,119,120428,119,120480,119,7457,119,1121,119,1309,119,1377,119,71434,119,71438,119,71439,119,43907,119,71919,87,71910,87,119830,87,119882,87,119934,87,119986,87,120038,87,120090,87,120142,87,120194,87,120246,87,120298,87,120350,87,120402,87,120454,87,1308,87,5043,87,5076,87,42218,87,5742,120,10539,120,10540,120,10799,120,65368,120,8569,120,119857,120,119909,120,119961,120,120013,120,120065,120,120117,120,120169,120,120221,120,120273,120,120325,120,120377,120,120429,120,120481,120,5441,120,5501,120,5741,88,9587,88,66338,88,71916,88,65336,88,8553,88,119831,88,119883,88,119935,88,119987,88,120039,88,120091,88,120143,88,120195,88,120247,88,120299,88,120351,88,120403,88,120455,88,42931,88,935,88,120510,88,120568,88,120626,88,120684,88,120742,88,11436,88,11613,88,5815,88,42219,88,66192,88,66228,88,66327,88,66855,88,611,121,7564,121,65369,121,119858,121,119910,121,119962,121,120014,121,120066,121,120118,121,120170,121,120222,121,120274,121,120326,121,120378,121,120430,121,120482,121,655,121,7935,121,43866,121,947,121,8509,121,120516,121,120574,121,120632,121,120690,121,120748,121,1199,121,4327,121,71900,121,65337,89,119832,89,119884,89,119936,89,119988,89,120040,89,120092,89,120144,89,120196,89,120248,89,120300,89,120352,89,120404,89,120456,89,933,89,978,89,120508,89,120566,89,120624,89,120682,89,120740,89,11432,89,1198,89,5033,89,5053,89,42220,89,94019,89,71844,89,66226,89,119859,122,119911,122,119963,122,120015,122,120067,122,120119,122,120171,122,120223,122,120275,122,120327,122,120379,122,120431,122,120483,122,7458,122,43923,122,71876,122,66293,90,71909,90,65338,90,8484,90,8488,90,119833,90,119885,90,119937,90,119989,90,120041,90,120197,90,120249,90,120301,90,120353,90,120405,90,120457,90,918,90,120493,90,120551,90,120609,90,120667,90,120725,90,5059,90,42204,90,71849,90,65282,34,65284,36,65285,37,65286,38,65290,42,65291,43,65294,46,65295,47,65296,48,65297,49,65298,50,65299,51,65300,52,65301,53,65302,54,65303,55,65304,56,65305,57,65308,60,65309,61,65310,62,65312,64,65316,68,65318,70,65319,71,65324,76,65329,81,65330,82,65333,85,65334,86,65335,87,65343,95,65346,98,65348,100,65350,102,65355,107,65357,109,65358,110,65361,113,65362,114,65364,116,65365,117,65367,119,65370,122,65371,123,65373,125,119846,109],\"_default\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"cs\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"de\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"es\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"fr\":[65374,126,65306,58,65281,33,8216,96,8245,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"it\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ja\":[8211,45,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65292,44,65307,59],\"ko\":[8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pl\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"pt-BR\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"qps-ploc\":[160,32,8211,45,65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"ru\":[65374,126,65306,58,65281,33,8216,96,8217,96,8245,96,180,96,12494,47,305,105,921,73,1009,112,215,120,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"tr\":[160,32,8211,45,65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65288,40,65289,41,65292,44,65307,59,65311,63],\"zh-hans\":[65374,126,65306,58,65281,33,8245,96,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65288,40,65289,41],\"zh-hant\":[8211,45,65374,126,180,96,12494,47,1047,51,1073,54,1072,97,1040,65,1068,98,1042,66,1089,99,1057,67,1077,101,1045,69,1053,72,305,105,1050,75,921,73,1052,77,1086,111,1054,79,1009,112,1088,112,1056,80,1075,114,1058,84,215,120,1093,120,1061,88,1091,121,1059,89,65283,35,65307,59]}' ); }); @@ -1130,7 +1144,7 @@ export class AmbiguousCharacters { return result; } - const data = this.ambiguousCharacterData.getValue(); + const data = this.ambiguousCharacterData.value; let filteredLocales = locales.filter( (l) => !l.startsWith('_') && l in data @@ -1156,12 +1170,12 @@ export class AmbiguousCharacters { } private static _locales = new Lazy<string[]>(() => - Object.keys(AmbiguousCharacters.ambiguousCharacterData.getValue()).filter( + Object.keys(AmbiguousCharacters.ambiguousCharacterData.value).filter( (k) => !k.startsWith('_') ) ); public static getLocales(): string[] { - return AmbiguousCharacters._locales.getValue(); + return AmbiguousCharacters._locales.value; } private constructor( diff --git a/src/vs/base/common/symbols.ts b/src/vs/base/common/symbols.ts new file mode 100644 index 0000000000..55b1e8291c --- /dev/null +++ b/src/vs/base/common/symbols.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Can be passed into the Delayed to defer using a microtask + * */ +export const MicrotaskDelay = Symbol('MicrotaskDelay'); diff --git a/src/vs/base/common/ternarySearchTree.ts b/src/vs/base/common/ternarySearchTree.ts new file mode 100644 index 0000000000..04af67c8eb --- /dev/null +++ b/src/vs/base/common/ternarySearchTree.ts @@ -0,0 +1,746 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { shuffle } from 'vs/base/common/arrays'; +import { CharCode } from 'vs/base/common/charCode'; +import { compare, compareIgnoreCase, compareSubstring, compareSubstringIgnoreCase } from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; + +export interface IKeyIterator<K> { + reset(key: K): this; + next(): this; + + hasNext(): boolean; + cmp(a: string): number; + value(): string; +} + +export class StringIterator implements IKeyIterator<string> { + + private _value: string = ''; + private _pos: number = 0; + + reset(key: string): this { + this._value = key; + this._pos = 0; + return this; + } + + next(): this { + this._pos += 1; + return this; + } + + hasNext(): boolean { + return this._pos < this._value.length - 1; + } + + cmp(a: string): number { + const aCode = a.charCodeAt(0); + const thisCode = this._value.charCodeAt(this._pos); + return aCode - thisCode; + } + + value(): string { + return this._value[this._pos]; + } +} + +export class ConfigKeysIterator implements IKeyIterator<string> { + + 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<string> { + + private _value!: string; + private _valueLen!: number; + private _from!: number; + private _to!: number; + + constructor( + private readonly _splitOnBackslash: boolean = true, + private readonly _caseSensitive: boolean = true + ) { } + + reset(key: string): this { + this._from = 0; + this._to = 0; + this._value = key; + this._valueLen = key.length; + for (let pos = key.length - 1; pos >= 0; pos--, this._valueLen--) { + const ch = this._value.charCodeAt(pos); + if (!(ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash)) { + break; + } + } + + return this.next(); + } + + hasNext(): boolean { + return this._to < this._valueLen; + } + + next(): this { + // this._data = key.split(/[\\/]/).filter(s => !!s); + this._from = this._to; + let justSeps = true; + for (; this._to < this._valueLen; this._to++) { + const ch = this._value.charCodeAt(this._to); + if (ch === CharCode.Slash || this._splitOnBackslash && ch === CharCode.Backslash) { + 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); + } +} + +const enum UriIteratorState { + Scheme = 1, Authority = 2, Path = 3, Query = 4, Fragment = 5 +} + +export class UriIterator implements IKeyIterator<URI> { + + private _pathIterator!: PathIterator; + private _value!: URI; + private _states: UriIteratorState[] = []; + private _stateIdx: number = 0; + + constructor( + private readonly _ignorePathCasing: (uri: URI) => boolean, + private readonly _ignoreQueryAndFragment: (uri: URI) => boolean) { } + + reset(key: URI): this { + this._value = key; + this._states = []; + if (this._value.scheme) { + this._states.push(UriIteratorState.Scheme); + } + if (this._value.authority) { + this._states.push(UriIteratorState.Authority); + } + if (this._value.path) { + this._pathIterator = new PathIterator(false, !this._ignorePathCasing(key)); + this._pathIterator.reset(key.path); + if (this._pathIterator.value()) { + this._states.push(UriIteratorState.Path); + } + } + if (!this._ignoreQueryAndFragment(key)) { + if (this._value.query) { + this._states.push(UriIteratorState.Query); + } + if (this._value.fragment) { + this._states.push(UriIteratorState.Fragment); + } + } + this._stateIdx = 0; + return this; + } + + next(): this { + if (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) { + this._pathIterator.next(); + } else { + this._stateIdx += 1; + } + return this; + } + + hasNext(): boolean { + return (this._states[this._stateIdx] === UriIteratorState.Path && this._pathIterator.hasNext()) + || this._stateIdx < this._states.length - 1; + } + + cmp(a: string): number { + if (this._states[this._stateIdx] === UriIteratorState.Scheme) { + return compareIgnoreCase(a, this._value.scheme); + } else if (this._states[this._stateIdx] === UriIteratorState.Authority) { + return compareIgnoreCase(a, this._value.authority); + } else if (this._states[this._stateIdx] === UriIteratorState.Path) { + return this._pathIterator.cmp(a); + } else if (this._states[this._stateIdx] === UriIteratorState.Query) { + return compare(a, this._value.query); + } else if (this._states[this._stateIdx] === UriIteratorState.Fragment) { + return compare(a, this._value.fragment); + } + throw new Error(); + } + + value(): string { + if (this._states[this._stateIdx] === UriIteratorState.Scheme) { + return this._value.scheme; + } else if (this._states[this._stateIdx] === UriIteratorState.Authority) { + return this._value.authority; + } else if (this._states[this._stateIdx] === UriIteratorState.Path) { + return this._pathIterator.value(); + } else if (this._states[this._stateIdx] === UriIteratorState.Query) { + return this._value.query; + } else if (this._states[this._stateIdx] === UriIteratorState.Fragment) { + return this._value.fragment; + } + throw new Error(); + } +} +class TernarySearchTreeNode<K, V> { + height: number = 1; + segment!: string; + value: V | undefined; + key: K | undefined; + left: TernarySearchTreeNode<K, V> | undefined; + mid: TernarySearchTreeNode<K, V> | undefined; + right: TernarySearchTreeNode<K, V> | undefined; + + isEmpty(): boolean { + return !this.left && !this.mid && !this.right && !this.value; + } + + rotateLeft() { + const tmp = this.right!; + this.right = tmp.left; + tmp.left = this; + this.updateHeight(); + tmp.updateHeight(); + return tmp; + } + + rotateRight() { + const tmp = this.left!; + this.left = tmp.right; + tmp.right = this; + this.updateHeight(); + tmp.updateHeight(); + return tmp; + } + + updateHeight() { + this.height = 1 + Math.max(this.heightLeft, this.heightRight); + } + + balanceFactor() { + return this.heightRight - this.heightLeft; + } + + get heightLeft() { + return this.left?.height ?? 0; + } + + get heightRight() { + return this.right?.height ?? 0; + } +} + +const enum Dir { + Left = -1, + Mid = 0, + Right = 1 +} + +export class TernarySearchTree<K, V> { + + static forUris<E>(ignorePathCasing: (key: URI) => boolean = () => false, ignoreQueryAndFragment: (key: URI) => boolean = () => false): TernarySearchTree<URI, E> { + return new TernarySearchTree<URI, E>(new UriIterator(ignorePathCasing, ignoreQueryAndFragment)); + } + + static forPaths<E>(ignorePathCasing = false): TernarySearchTree<string, E> { + return new TernarySearchTree<string, E>(new PathIterator(undefined, !ignorePathCasing)); + } + + static forStrings<E>(): TernarySearchTree<string, E> { + return new TernarySearchTree<string, E>(new StringIterator()); + } + + static forConfigKeys<E>(): TernarySearchTree<string, E> { + return new TernarySearchTree<string, E>(new ConfigKeysIterator()); + } + + private _iter: IKeyIterator<K>; + private _root: TernarySearchTreeNode<K, V> | undefined; + + constructor(segments: IKeyIterator<K>) { + this._iter = segments; + } + + clear(): void { + this._root = undefined; + } + + /** + * Fill the tree with the same value of the given keys + */ + fill(element: V, keys: readonly K[]): void; + /** + * Fill the tree with given [key,value]-tuples + */ + fill(values: readonly [K, V][]): void; + fill(values: readonly [K, V][] | V, keys?: readonly K[]): void { + if (keys) { + const arr = keys.slice(0); + shuffle(arr); + for (const k of arr) { + this.set(k, (<V>values)); + } + } else { + const arr = (<[K, V][]>values).slice(0); + shuffle(arr); + for (const entry of arr) { + this.set(entry[0], entry[1]); + } + } + } + + set(key: K, element: V): V | undefined { + const iter = this._iter.reset(key); + let node: TernarySearchTreeNode<K, V>; + + if (!this._root) { + this._root = new TernarySearchTreeNode<K, V>(); + this._root.segment = iter.value(); + } + const stack: [Dir, TernarySearchTreeNode<K, V>][] = []; + + // find insert_node + node = this._root; + while (true) { + const val = iter.cmp(node.segment); + if (val > 0) { + // left + if (!node.left) { + node.left = new TernarySearchTreeNode<K, V>(); + node.left.segment = iter.value(); + } + stack.push([Dir.Left, node]); + node = node.left; + + } else if (val < 0) { + // right + if (!node.right) { + node.right = new TernarySearchTreeNode<K, V>(); + node.right.segment = iter.value(); + } + stack.push([Dir.Right, node]); + node = node.right; + + } else if (iter.hasNext()) { + // mid + iter.next(); + if (!node.mid) { + node.mid = new TernarySearchTreeNode<K, V>(); + node.mid.segment = iter.value(); + } + stack.push([Dir.Mid, node]); + node = node.mid; + } else { + break; + } + } + + // set value + const oldElement = node.value; + node.value = element; + node.key = key; + + // balance + for (let i = stack.length - 1; i >= 0; i--) { + const node = stack[i][1]; + + node.updateHeight(); + const bf = node.balanceFactor(); + + if (bf < -1 || bf > 1) { + // needs rotate + const d1 = stack[i][0]; + const d2 = stack[i + 1][0]; + + if (d1 === Dir.Right && d2 === Dir.Right) { + //right, right -> rotate left + stack[i][1] = node.rotateLeft(); + + } else if (d1 === Dir.Left && d2 === Dir.Left) { + // left, left -> rotate right + stack[i][1] = node.rotateRight(); + + } else if (d1 === Dir.Right && d2 === Dir.Left) { + // right, left -> double rotate right, left + node.right = stack[i + 1][1] = stack[i + 1][1].rotateRight(); + stack[i][1] = node.rotateLeft(); + + } else if (d1 === Dir.Left && d2 === Dir.Right) { + // left, right -> double rotate left, right + node.left = stack[i + 1][1] = stack[i + 1][1].rotateLeft(); + stack[i][1] = node.rotateRight(); + + } else { + throw new Error(); + } + + // patch path to parent + if (i > 0) { + switch (stack[i - 1][0]) { + case Dir.Left: + stack[i - 1][1].left = stack[i][1]; + break; + case Dir.Right: + stack[i - 1][1].right = stack[i][1]; + break; + case Dir.Mid: + stack[i - 1][1].mid = stack[i][1]; + break; + } + } else { + this._root = stack[0][1]; + } + } + } + + return oldElement; + } + + 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) { + const val = iter.cmp(node.segment); + if (val > 0) { + // left + node = node.left; + } else if (val < 0) { + // right + node = node.right; + } else if (iter.hasNext()) { + // mid + iter.next(); + node = node.mid; + } else { + break; + } + } + return node; + } + + has(key: K): boolean { + const node = this._getNode(key); + return !(node?.value === undefined && node?.mid === undefined); + } + + delete(key: K): void { + return this._delete(key, false); + } + + deleteSuperstr(key: K): void { + return this._delete(key, true); + } + + private _delete(key: K, superStr: boolean): void { + const iter = this._iter.reset(key); + const stack: [Dir, TernarySearchTreeNode<K, V>][] = []; + let node = this._root; + + // find node + while (node) { + const val = iter.cmp(node.segment); + if (val > 0) { + // left + stack.push([Dir.Left, node]); + node = node.left; + } else if (val < 0) { + // right + stack.push([Dir.Right, node]); + node = node.right; + } else if (iter.hasNext()) { + // mid + iter.next(); + stack.push([Dir.Mid, node]); + node = node.mid; + } else { + break; + } + } + + if (!node) { + // node not found + return; + } + + if (superStr) { + // removing children, reset height + node.left = undefined; + node.mid = undefined; + node.right = undefined; + node.height = 1; + } else { + // removing element + node.key = undefined; + node.value = undefined; + } + + // BST node removal + if (!node.mid && !node.value) { + if (node.left && node.right) { + // full node + // replace deleted-node with the min-node of the right branch. + // If there is no true min-node leave things as they are + const min = this._min(node.right); + if (min.key) { + const { key, value, segment } = min; + this._delete(min.key!, false); + node.key = key; + node.value = value; + node.segment = segment; + } + + } else { + // empty or half empty + const newChild = node.left ?? node.right; + if (stack.length > 0) { + const [dir, parent] = stack[stack.length - 1]; + switch (dir) { + case Dir.Left: parent.left = newChild; break; + case Dir.Mid: parent.mid = newChild; break; + case Dir.Right: parent.right = newChild; break; + } + } else { + this._root = newChild; + } + } + } + + // AVL balance + for (let i = stack.length - 1; i >= 0; i--) { + const node = stack[i][1]; + + node.updateHeight(); + const bf = node.balanceFactor(); + if (bf > 1) { + // right heavy + if (node.right!.balanceFactor() >= 0) { + // right, right -> rotate left + stack[i][1] = node.rotateLeft(); + } else { + // right, left -> double rotate + node.right = node.right!.rotateRight(); + stack[i][1] = node.rotateLeft(); + } + + } else if (bf < -1) { + // left heavy + if (node.left!.balanceFactor() <= 0) { + // left, left -> rotate right + stack[i][1] = node.rotateRight(); + } else { + // left, right -> double rotate + node.left = node.left!.rotateLeft(); + stack[i][1] = node.rotateRight(); + } + } + + // patch path to parent + if (i > 0) { + switch (stack[i - 1][0]) { + case Dir.Left: + stack[i - 1][1].left = stack[i][1]; + break; + case Dir.Right: + stack[i - 1][1].right = stack[i][1]; + break; + case Dir.Mid: + stack[i - 1][1].mid = stack[i][1]; + break; + } + } else { + this._root = stack[0][1]; + } + } + } + + private _min(node: TernarySearchTreeNode<K, V>): TernarySearchTreeNode<K, V> { + while (node.left) { + node = node.left; + } + return node; + } + + findSubstr(key: K): V | undefined { + const iter = this._iter.reset(key); + let node = this._root; + let candidate: V | undefined = undefined; + while (node) { + const val = iter.cmp(node.segment); + if (val > 0) { + // left + node = node.left; + } else if (val < 0) { + // right + node = node.right; + } else if (iter.hasNext()) { + // mid + iter.next(); + candidate = node.value || candidate; + node = node.mid; + } else { + break; + } + } + return node && node.value || candidate; + } + + findSuperstr(key: K): IterableIterator<[K, V]> | undefined { + return this._findSuperstrOrElement(key, false); + } + + private _findSuperstrOrElement(key: K, allowValue: true): IterableIterator<[K, V]> | V | undefined; + private _findSuperstrOrElement(key: K, allowValue: false): IterableIterator<[K, V]> | undefined; + private _findSuperstrOrElement(key: K, allowValue: boolean): IterableIterator<[K, V]> | V | undefined { + const iter = this._iter.reset(key); + let node = this._root; + while (node) { + const val = iter.cmp(node.segment); + if (val > 0) { + // left + node = node.left; + } else if (val < 0) { + // right + node = node.right; + } else if (iter.hasNext()) { + // mid + iter.next(); + node = node.mid; + } else { + // collect + if (!node.mid) { + if (allowValue) { + return node.value; + } else { + return undefined; + } + } else { + return this._entries(node.mid); + } + } + } + return undefined; + } + + hasElementOrSubtree(key: K): boolean { + return this._findSuperstrOrElement(key, true) !== undefined; + } + + forEach(callback: (value: V, index: K) => any): void { + for (const [key, value] of this) { + callback(value, key); + } + } + + *[Symbol.iterator](): IterableIterator<[K, V]> { + yield* this._entries(this._root); + } + + private _entries(node: TernarySearchTreeNode<K, V> | undefined): IterableIterator<[K, V]> { + const result: [K, V][] = []; + this._dfsEntries(node, result); + return result[Symbol.iterator](); + } + + private _dfsEntries(node: TernarySearchTreeNode<K, V> | undefined, bucket: [K, V][]) { + // DFS + if (!node) { + return; + } + if (node.left) { + this._dfsEntries(node.left, bucket); + } + if (node.value) { + bucket.push([node.key!, node.value]); + } + if (node.mid) { + this._dfsEntries(node.mid, bucket); + } + if (node.right) { + this._dfsEntries(node.right, bucket); + } + } + + // for debug/testing + _isBalanced(): boolean { + const nodeIsBalanced = (node: TernarySearchTreeNode<any, any> | undefined): boolean => { + if (!node) { + return true; + } + const bf = node.balanceFactor(); + if (bf < -1 || bf > 1) { + return false; + } + return nodeIsBalanced(node.left) && nodeIsBalanced(node.right); + }; + return nodeIsBalanced(this._root); + } +} diff --git a/src/vs/base/common/themables.ts b/src/vs/base/common/themables.ts new file mode 100644 index 0000000000..9b853fb386 --- /dev/null +++ b/src/vs/base/common/themables.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { SqlIconId } from 'sql/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; + +export type ColorIdentifier = string; + +export type IconIdentifier = string; + +export interface ThemeColor { + id: string; +} + +export namespace ThemeColor { + export function isThemeColor(obj: any): obj is ThemeColor { + return obj && typeof obj === 'object' && typeof (<ThemeColor>obj).id === 'string'; + } +} + +export function themeColorFromId(id: ColorIdentifier) { + return { id }; +} + + +export interface ThemeIcon { + readonly id: string; + readonly color?: ThemeColor; +} + +export namespace ThemeIcon { + export const iconNameSegment = '[A-Za-z0-9]+'; + export const iconNameExpression = '[A-Za-z0-9-]+'; + export const iconModifierExpression = '~[A-Za-z]+'; + export const iconNameCharacter = '[A-Za-z0-9~-]'; + + const ThemeIconIdRegex = new RegExp(`^(${iconNameExpression})(${iconModifierExpression})?$`); + + + // {{SQL CARBON TODO}} - do we need the edit block below + // export function asClassNameArray(icon: CSSIcon): string[] { + // if (icon instanceof Codicon) { + // return ['codicon', 'codicon-' + icon.id]; + // } + // const match = cssIconIdRegex.exec(icon.id); + // if (!match) { + // return asClassNameArray(Codicon.error); + // } + // const [, id, modifier] = match; + + // // {{SQL CARBON EDIT}} Modifying method to not add 'codicon' in front of sql carbon icons. + // let sqlCarbonIcons: string[] = [SqlIconId.activeConnectionsAction, SqlIconId.addServerAction, SqlIconId.addServerGroupAction, SqlIconId.serverPage]; + // if (sqlCarbonIcons.includes(id)) { + // return ['codicon', id]; + // // {{SQL CARBON EDIT}} End of edit + // } else { + // const classNames = ['codicon', 'codicon-' + id]; + // if (modifier) { + // classNames.push('codicon-modifier-' + modifier.substr(1)); + // } + // return classNames; + // } + // } + + export function asClassNameArray(icon: ThemeIcon): string[] { + const match = ThemeIconIdRegex.exec(icon.id); + if (!match) { + return asClassNameArray(Codicon.error); + } + + // {{SQL CARBON EDIT}} Modifying method to not add 'codicon' in front of sql carbon icons. + const [, id, modifier] = match; + let sqlCarbonIcons: string[] = [SqlIconId.activeConnectionsAction, SqlIconId.addServerAction, SqlIconId.addServerGroupAction, SqlIconId.serverPage]; + if (sqlCarbonIcons.includes(id)) { + return ['codicon', id]; + // {{SQL CARBON EDIT}} End of edit + } else { + const classNames = ['codicon', 'codicon-' + id]; + if (modifier) { + classNames.push('codicon-modifier-' + modifier.substring(1)); + } + return classNames; + } + } + + export function asClassName(icon: ThemeIcon): string { + return asClassNameArray(icon).join(' '); + } + + export function asCSSSelector(icon: ThemeIcon): string { + return '.' + asClassNameArray(icon).join('.'); + } + + export function isThemeIcon(obj: any): obj is ThemeIcon { + return obj && typeof obj === 'object' && typeof (<ThemeIcon>obj).id === 'string' && (typeof (<ThemeIcon>obj).color === 'undefined' || ThemeColor.isThemeColor((<ThemeIcon>obj).color)); + } + + const _regexFromString = new RegExp(`^\\$\\((${ThemeIcon.iconNameExpression}(?:${ThemeIcon.iconModifierExpression})?)\\)$`); + + export function fromString(str: string): ThemeIcon | undefined { + const match = _regexFromString.exec(str); + if (!match) { + return undefined; + } + const [, name] = match; + return { id: name }; + } + + export function fromId(id: string): ThemeIcon { + return { id }; + } + + 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 getModifier(icon: ThemeIcon): string | undefined { + const tildeIndex = icon.id.lastIndexOf('~'); + if (tildeIndex !== -1) { + return icon.id.substring(tildeIndex + 1); + } + return undefined; + } + + export function isEqual(ti1: ThemeIcon, ti2: ThemeIcon): boolean { + return ti1.id === ti2.id && ti1.color?.id === ti2.color?.id; + } + +} diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index e073ea9c8b..c881b2f77b 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -3,15 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI, UriComponents } from 'vs/base/common/uri'; - -/** - * @returns whether the provided parameter is a JavaScript Array or not. - */ -export function isArray(array: any): array is any[] { - return Array.isArray(array); -} - /** * @returns whether the provided parameter is a JavaScript String or not. */ @@ -27,7 +18,6 @@ export function isStringArray(value: unknown): value is string[] { } /** - * * @returns whether the provided parameter is of type `object` but **not** * `null`, an `array`, a `regexp`, nor a `date`. */ @@ -43,7 +33,6 @@ export function isObject(obj: unknown): obj is Object { } /** - * * @returns whether the provided parameter is of type `Buffer` or Uint8Array dervived type */ export function isTypedArray(obj: unknown): obj is Object { @@ -201,41 +190,6 @@ export function validateConstraint(arg: unknown, constraint: TypeConstraint | un } } -export function getAllPropertyNames(obj: object): string[] { - let res: string[] = []; - let proto = Object.getPrototypeOf(obj); - while (Object.prototype !== proto) { - res = res.concat(Object.getOwnPropertyNames(proto)); - proto = Object.getPrototypeOf(proto); - } - return res; -} - -export function getAllMethodNames(obj: object): string[] { - const methods: string[] = []; - for (const prop of getAllPropertyNames(obj)) { - if (typeof (obj as any)[prop] === 'function') { - methods.push(prop); - } - } - return methods; -} - -export function createProxyObject<T extends object>(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); - }; - }; - - const result = {} as T; - for (const methodName of methodNames) { - (<any>result)[methodName] = createProxyMethod(methodName); - } - return result; -} - /** * Converts null to undefined, passes all other values through. */ @@ -266,12 +220,15 @@ export type AddFirstParameterToFunctions<Target, TargetFunctionsReturnType, Firs }; /** - * Mapped-type that replaces all occurrences of URI with UriComponents + * Given an object with all optional properties, requires at least one to be defined. + * i.e. AtLeastOne<MyObject>; */ -export type UriDto<T> = { [K in keyof T]: T[K] extends URI - ? UriComponents - : UriDto<T[K]> }; +export type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]; -export function assertNever(value: never, message = 'Unreachable'): never { - throw new Error(message); -} + +/** + * A type that removed readonly-less from all properties of `T` + */ +export type Mutable<T> = { + -readonly [P in keyof T]: T[P] +}; diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 0150c834f6..9cc099c739 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -327,15 +327,22 @@ export class URI implements UriComponents { return new Uri('file', authority, path, _empty, _empty); } - static from(components: { scheme: string; authority?: string; path?: string; query?: string; fragment?: string }): URI { + /** + * Creates new URI from uri components. + * + * Unless `strict` is `true` the scheme is defaults to be `file`. This function performs + * validation and should be used for untrusted uri components retrieved from storage, + * user input, command arguments etc + */ + static from(components: UriComponents, strict?: boolean): URI { const result = new Uri( components.scheme, components.authority, components.path, components.query, components.fragment, + strict ); - _validateUri(result, true); return result; } @@ -380,6 +387,16 @@ export class URI implements UriComponents { return this; } + /** + * A helper function to revive URIs. + * + * **Note** that this function should only be used when receiving URI#toJSON generated data + * and that it doesn't do any validation. Use {@link URI.from} when received "untrusted" + * uri components such as command arguments or data from storage. + * + * @param data The URI components or URI to revive. + * @returns The revived URI or undefined or null. + */ static revive(data: UriComponents | URI): URI; static revive(data: UriComponents | URI | undefined): URI | undefined; static revive(data: UriComponents | URI | null): URI | null; @@ -392,8 +409,8 @@ export class URI implements UriComponents { return data; } else { const result = new Uri(data); - result._formatted = (<UriState>data).external; - result._fsPath = (<UriState>data)._sep === _pathSepMarker ? (<UriState>data).fsPath : null; + result._formatted = (<UriState>data).external ?? null; + result._fsPath = (<UriState>data)._sep === _pathSepMarker ? (<UriState>data).fsPath ?? null : null; return result; } } @@ -401,17 +418,28 @@ export class URI implements UriComponents { export interface UriComponents { scheme: string; - authority: string; - path: string; - query: string; - fragment: string; + authority?: string; + path?: string; + query?: string; + fragment?: string; +} + +export function isUriComponents(thing: any): thing is UriComponents { + if (!thing || typeof thing !== 'object') { + return false; + } + return typeof (<UriComponents>thing).scheme === 'string' + && (typeof (<UriComponents>thing).authority === 'string' || typeof (<UriComponents>thing).authority === 'undefined') + && (typeof (<UriComponents>thing).path === 'string' || typeof (<UriComponents>thing).path === 'undefined') + && (typeof (<UriComponents>thing).query === 'string' || typeof (<UriComponents>thing).query === 'undefined') + && (typeof (<UriComponents>thing).fragment === 'string' || typeof (<UriComponents>thing).fragment === 'undefined'); } interface UriState extends UriComponents { $mid: MarshalledId.Uri; - external: string; - fsPath: string; - _sep: 1 | undefined; + external?: string; + fsPath?: string; + _sep?: 1; } const _pathSepMarker = isWindows ? 1 : undefined; @@ -453,10 +481,14 @@ class Uri extends URI { if (this._formatted) { res.external = this._formatted; } - // uri components + //--- uri components if (this.path) { res.path = this.path; } + // TODO + // this isn't correct and can violate the UriComponents contract but + // this is part of the vscode.Uri API and we shouldn't change how that + // works anymore if (this.scheme) { res.scheme = this.scheme; } @@ -498,7 +530,7 @@ const encodeTable: { [ch: number]: string } = { [CharCode.Space]: '%20', }; -function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): string { +function encodeURIComponentFast(uriComponent: string, isPath: boolean, isAuthority: boolean): string { let res: string | undefined = undefined; let nativeEncodePos = -1; @@ -514,7 +546,10 @@ function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): stri || code === CharCode.Period || code === CharCode.Underline || code === CharCode.Tilde - || (allowSlash && code === CharCode.Slash) + || (isPath && code === CharCode.Slash) + || (isAuthority && code === CharCode.OpenSquareBracket) + || (isAuthority && code === CharCode.CloseSquareBracket) + || (isAuthority && code === CharCode.Colon) ) { // check if we are delaying native encode if (nativeEncodePos !== -1) { @@ -632,24 +667,24 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string { // <user>@<auth> const userinfo = authority.substr(0, idx); authority = authority.substr(idx + 1); - idx = userinfo.indexOf(':'); + idx = userinfo.lastIndexOf(':'); if (idx === -1) { - res += encoder(userinfo, false); + res += encoder(userinfo, false, false); } else { // <user>:<pass>@<auth> - res += encoder(userinfo.substr(0, idx), false); + res += encoder(userinfo.substr(0, idx), false, false); res += ':'; - res += encoder(userinfo.substr(idx + 1), false); + res += encoder(userinfo.substr(idx + 1), false, true); } res += '@'; } authority = authority.toLowerCase(); - idx = authority.indexOf(':'); + idx = authority.lastIndexOf(':'); if (idx === -1) { - res += encoder(authority, false); + res += encoder(authority, false, true); } else { // <auth>:<port> - res += encoder(authority.substr(0, idx), false); + res += encoder(authority.substr(0, idx), false, true); res += authority.substr(idx); } } @@ -667,15 +702,15 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string { } } // encode the rest of the path - res += encoder(path, true); + res += encoder(path, true, false); } if (query) { res += '?'; - res += encoder(query, false); + res += encoder(query, false, false); } if (fragment) { res += '#'; - res += !skipEncoding ? encodeURIComponentFast(fragment, false) : fragment; + res += !skipEncoding ? encodeURIComponentFast(fragment, false, false) : fragment; } return res; } @@ -702,3 +737,10 @@ function percentDecode(str: string): string { } return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match)); } + +/** + * Mapped-type that replaces all occurrences of URI with UriComponents + */ +export type UriDto<T> = { [K in keyof T]: T[K] extends URI + ? UriComponents + : UriDto<T[K]> }; diff --git a/src/vs/base/common/uriIpc.ts b/src/vs/base/common/uriIpc.ts index 04ca2ffb49..f4b3eefd41 100644 --- a/src/vs/base/common/uriIpc.ts +++ b/src/vs/base/common/uriIpc.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { VSBuffer } from 'vs/base/common/buffer'; import { MarshalledObject } from 'vs/base/common/marshalling'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -125,6 +126,10 @@ function _transformIncomingURIs(obj: any, transformer: IURITransformer, revive: return revive ? URI.revive(transformer.transformIncoming(obj)) : transformer.transformIncoming(obj); } + if (obj instanceof VSBuffer) { + return null; + } + // walk object (or array) for (const key in obj) { if (Object.hasOwnProperty.call(obj, key)) { diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index a9a1b1ff1d..6b3d4d425d 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -6,8 +6,8 @@ import { transformErrorForSerialization } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { globals, isWeb } from 'vs/base/common/platform'; -import * as types from 'vs/base/common/types'; +import { getAllMethodNames } from 'vs/base/common/objects'; +import { isWeb } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; const INITIALIZE = '$initialize'; @@ -136,12 +136,12 @@ class SimpleWorkerProtocol { public listen(eventName: string, arg: any): Event<any> { let req: string | null = null; const emitter = new Emitter<any>({ - onFirstListenerAdd: () => { + onWillAddFirstListener: () => { req = String(++this._lastSentReq); this._pendingEmitters.set(req, emitter); this._send(new SubscribeEventMessage(this._workerId, req, eventName, arg)); }, - onLastListenerRemove: () => { + onDidRemoveLastListener: () => { this._pendingEmitters.delete(req!); this._send(new UnsubscribeEventMessage(this._workerId, req!)); req = null; @@ -324,15 +324,17 @@ export class SimpleWorkerClient<W extends object, H extends object> extends Disp // Gather loader configuration let loaderConfiguration: any = null; - if (typeof globals.require !== 'undefined' && typeof globals.require.getConfig === 'function') { + + const globalRequire: { getConfig?(): object } | undefined = (globalThis as any).require; + if (typeof globalRequire !== 'undefined' && typeof globalRequire.getConfig === 'function') { // Get the configuration from the Monaco AMD Loader - loaderConfiguration = globals.require.getConfig(); - } else if (typeof globals.requirejs !== 'undefined') { + loaderConfiguration = globalRequire.getConfig(); + } else if (typeof (globalThis as any).requirejs !== 'undefined') { // Get the configuration from requirejs - loaderConfiguration = globals.requirejs.s.contexts._.config; + loaderConfiguration = (globalThis as any).requirejs.s.contexts._.config; } - const hostMethods = types.getAllMethodNames(host); + const hostMethods = getAllMethodNames(host); // Send initialize message this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [ @@ -507,7 +509,7 @@ export class SimpleWorkerServer<H extends object> { if (this._requestHandlerFactory) { // static request handler this._requestHandler = this._requestHandlerFactory(hostProxy); - return Promise.resolve(types.getAllMethodNames(this._requestHandler)); + return Promise.resolve(getAllMethodNames(this._requestHandler)); } if (loaderConfig) { @@ -527,17 +529,17 @@ export class SimpleWorkerServer<H extends object> { // Since this is in a web worker, enable catching errors loaderConfig.catchError = true; - globals.require.config(loaderConfig); + globalThis.require.config(loaderConfig); } return new Promise<string[]>((resolve, reject) => { // Use the global require to be sure to get the global config // ESM-comment-begin - const req = (globals.require || require); + const req = (globalThis.require || require); // ESM-comment-end // ESM-uncomment-begin - // const req = globals.require; + // const req = globalThis.require; // ESM-uncomment-end req([moduleId], (module: { create: IRequestHandlerFactory<H> }) => { @@ -548,7 +550,7 @@ export class SimpleWorkerServer<H extends object> { return; } - resolve(types.getAllMethodNames(this._requestHandler)); + resolve(getAllMethodNames(this._requestHandler)); }, reject); }); } diff --git a/src/vs/base/node/decoder.ts b/src/vs/base/node/decoder.ts deleted file mode 100644 index 8a39679457..0000000000 --- a/src/vs/base/node/decoder.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as sd from 'string_decoder'; -import { CharCode } from 'vs/base/common/charCode'; - -/** - * Convenient way to iterate over output line by line. This helper accommodates for the fact that - * a buffer might not end with new lines all the way. - * - * To use: - * - call the write method - * - forEach() over the result to get the lines - */ -export class LineDecoder { - private stringDecoder: sd.StringDecoder; - private remaining: string | null; - - constructor(encoding: BufferEncoding = 'utf8') { - this.stringDecoder = new sd.StringDecoder(encoding); - this.remaining = null; - } - - write(buffer: Buffer): string[] { - const result: string[] = []; - const value = this.remaining - ? this.remaining + this.stringDecoder.write(buffer) - : this.stringDecoder.write(buffer); - - if (value.length < 1) { - return result; - } - let start = 0; - let ch: number; - let idx = start; - while (idx < value.length) { - ch = value.charCodeAt(idx); - if (ch === CharCode.CarriageReturn || ch === CharCode.LineFeed) { - result.push(value.substring(start, idx)); - idx++; - if (idx < value.length) { - const lastChar = ch; - ch = value.charCodeAt(idx); - if ((lastChar === CharCode.CarriageReturn && ch === CharCode.LineFeed) || (lastChar === CharCode.LineFeed && ch === CharCode.CarriageReturn)) { - idx++; - } - } - start = idx; - } else { - idx++; - } - } - this.remaining = start < value.length ? value.substr(start) : null; - return result; - } - - end(): string | null { - return this.remaining; - } -} diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index e16801508c..1dc6ef8131 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { networkInterfaces } from 'os'; -import * as errors from 'vs/base/common/errors'; -import { TernarySearchTree } from 'vs/base/common/map'; +import { TernarySearchTree } from 'vs/base/common/ternarySearchTree'; import * as uuid from 'vs/base/common/uuid'; import { getMac } from 'vs/base/node/macAddress'; @@ -78,10 +77,10 @@ export const virtualMachineHint: { value(): number } = new class { }; let machineId: Promise<string>; -export async function getMachineId(): Promise<string> { +export async function getMachineId(errorLogger: (error: any) => void): Promise<string> { if (!machineId) { machineId = (async () => { - const id = await getMacMachineId(); + const id = await getMacMachineId(errorLogger); return id || uuid.generateUuid(); // fallback, generate a UUID })(); @@ -90,13 +89,13 @@ export async function getMachineId(): Promise<string> { return machineId; } -async function getMacMachineId(): Promise<string | undefined> { +async function getMacMachineId(errorLogger: (error: any) => void): Promise<string | undefined> { try { const crypto = await import('crypto'); const macAddress = getMac(); return crypto.createHash('sha256').update(macAddress, 'utf8').digest('hex'); } catch (err) { - errors.onUnexpectedError(err); + errorLogger(err); return undefined; } } diff --git a/src/vs/base/node/languagePacks.d.ts b/src/vs/base/node/languagePacks.d.ts index 979ae4b6d6..9b20c69d46 100644 --- a/src/vs/base/node/languagePacks.d.ts +++ b/src/vs/base/node/languagePacks.d.ts @@ -5,6 +5,7 @@ export interface NLSConfiguration { locale: string; + osLocale: string; availableLanguages: { [key: string]: string; }; @@ -21,4 +22,4 @@ export interface InternalNLSConfiguration extends NLSConfiguration { _languagePackSupport?: boolean; } -export function getNLSConfiguration(commit: string, userDataPath: string, metaDataFile: string, locale: string): Promise<NLSConfiguration>; +export function getNLSConfiguration(commit: string | undefined, userDataPath: string, metaDataFile: string, locale: string, osLocale: string): Promise<NLSConfiguration>; diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js index 118f2a3091..2e8b35f484 100644 --- a/src/vs/base/node/languagePacks.js +++ b/src/vs/base/node/languagePacks.js @@ -10,12 +10,11 @@ 'use strict'; /** - * @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 factory(path, fs, perf) { /** * @param {string} file @@ -68,12 +67,12 @@ /** * @param {string} userDataPath - * @returns {object} + * @returns {Promise<object | undefined>} */ - function getLanguagePackConfigurations(userDataPath) { + async function getLanguagePackConfigurations(userDataPath) { const configFile = path.join(userDataPath, 'languagepacks.json'); try { - return nodeRequire(configFile); + return JSON.parse(await readFile(configFile)); } catch (err) { // Do nothing. If we can't read the file we have no // language pack config. @@ -83,7 +82,7 @@ /** * @param {object} config - * @param {string} locale + * @param {string | undefined} locale */ function resolveLanguagePackLocale(config, locale) { try { @@ -106,18 +105,27 @@ } /** - * @param {string} commit + * @param {string | undefined} commit * @param {string} userDataPath * @param {string} metaDataFile * @param {string} locale + * @param {string} osLocale + * @returns {Promise<import('./languagePacks').NLSConfiguration>} */ - function getNLSConfiguration(commit, userDataPath, metaDataFile, locale) { + function getNLSConfiguration(commit, userDataPath, metaDataFile, locale, osLocale) { + const defaultResult = function (locale) { + perf.mark('code/didGenerateNls'); + return Promise.resolve({ locale, osLocale, availableLanguages: {} }); + }; + + perf.mark('code/willGenerateNls'); + if (locale === 'pseudo') { - return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true }); + return Promise.resolve({ locale, osLocale, availableLanguages: {}, pseudo: true }); } if (process.env['VSCODE_DEV']) { - return Promise.resolve({ locale: locale, availableLanguages: {} }); + return Promise.resolve({ locale, osLocale, availableLanguages: {} }); } // We have a built version so we have extracted nls file. Try to find @@ -126,109 +134,106 @@ // 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: {} }); + return Promise.resolve({ locale, osLocale, 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); } - 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 getLanguagePackConfigurations(userDataPath).then(configs => { + if (!configs) { 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 resolvedLocale = resolveLanguagePackLocale(configs, locale); + if (!resolvedLocale) { + return defaultResult(initialLocale); + } + locale = resolvedLocale; + const packConfig = configs[locale]; + let mainPack; + if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') { + return defaultResult(initialLocale); + } + return exists(mainPack).then(fileExists => { + if (!fileExists) { + return defaultResult(initialLocale); } - 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, + osLocale, + availableLanguages: { '*': locale }, + _languagePackId: packId, + _translationsConfigFile: translationsConfigFile, + _cacheRoot: cacheRoot, + _resolvedLanguagePackCoreLocation: coreLocation, + _corruptedFile: corruptedFile + }; + return exists(corruptedFile).then(corrupted => { + // The nls cache directory is corrupted. + let toDelete; + if (corrupted) { + toDelete = rimraf(cacheRoot); + } else { + toDelete = Promise.resolve(undefined); + } + return toDelete.then(() => { + return exists(coreLocation).then(fileExists => { + if (fileExists) { + // We don't wait for this. No big harm if we can't touch + touch(coreLocation).catch(() => { }); + perf.mark('code/didGenerateNls'); + return result; } - 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); + }); }); }); }); @@ -247,12 +252,12 @@ 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); }); + define(['path', 'fs', 'vs/base/common/performance'], function (/** @type {typeof import('path')} */ path, /** @type {typeof import('fs')} */ fs, /** @type {typeof import('../common/performance')} */ perf) { return factory(path, fs, perf); }); } else if (typeof module === 'object' && typeof module.exports === 'object') { const path = require('path'); const fs = require('fs'); const perf = require('../common/performance'); - module.exports = factory(require, path, fs, perf); + module.exports = factory(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 b4edc7d8af..657a15c149 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -37,8 +37,13 @@ export enum RimRafMode { * - `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. + * the optional `moveToPath` allows to override where to rename the + * path to before deleting it. */ -async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise<void> { +async function rimraf(path: string, mode: RimRafMode.UNLINK): Promise<void>; +async function rimraf(path: string, mode: RimRafMode.MOVE, moveToPath?: string): Promise<void>; +async function rimraf(path: string, mode?: RimRafMode, moveToPath?: string): Promise<void>; +async function rimraf(path: string, mode = RimRafMode.UNLINK, moveToPath?: string): Promise<void> { if (isRootOrDriveLetter(path)) { throw new Error('rimraf - will refuse to recursively delete root'); } @@ -49,12 +54,11 @@ async function rimraf(path: string, mode = RimRafMode.UNLINK): Promise<void> { } // delete: via move - return rimrafMove(path); + return rimrafMove(path, moveToPath); } -async function rimrafMove(path: string): Promise<void> { +async function rimrafMove(path: string, moveToPath = randomPath(tmpdir())): Promise<void> { try { - const pathInTemp = randomPath(tmpdir()); try { // Intentionally using `fs.promises` here to skip // the patched graceful-fs method that can result @@ -64,13 +68,17 @@ async function rimrafMove(path: string): Promise<void> { // than necessary and we have a fallback to delete // via unlink. // https://github.com/microsoft/vscode/issues/139908 - await fs.promises.rename(path, pathInTemp); + await fs.promises.rename(path, moveToPath); } catch (error) { - return rimrafUnlink(path); // if rename fails, delete without tmp dir + if (error.code === 'ENOENT') { + return; // ignore - path to delete did not exist + } + + return rimrafUnlink(path); // otherwise fallback to unlink } // Delete but do not return as promise - rimrafUnlink(pathInTemp).catch(error => {/* ignore */ }); + rimrafUnlink(moveToPath).catch(error => {/* ignore */ }); } catch (error) { if (error.code !== 'ENOENT') { throw error; diff --git a/src/vs/base/node/ports.ts b/src/vs/base/node/ports.ts index 71ede3de14..b7928236c6 100644 --- a/src/vs/base/node/ports.ts +++ b/src/vs/base/node/ports.ts @@ -150,7 +150,7 @@ export const BROWSER_RESTRICTED_PORTS: any = { /** * Uses listen instead of connect. Is faster, but if there is another listener on 0.0.0.0 then this will take 127.0.0.1 from that listener. */ -export function findFreePortFaster(startPort: number, giveUpAfter: number, timeout: number): Promise<number> { +export function findFreePortFaster(startPort: number, giveUpAfter: number, timeout: number, hostname: string = '127.0.0.1'): Promise<number> { let resolved: boolean = false; let timeoutHandle: NodeJS.Timeout | undefined = undefined; let countTried: number = 1; @@ -178,7 +178,7 @@ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeo if (err && ((<any>err).code === 'EADDRINUSE' || (<any>err).code === 'EACCES') && (countTried < giveUpAfter)) { startPort++; countTried++; - server.listen(startPort, '127.0.0.1'); + server.listen(startPort, hostname); } else { doResolve(0, resolve); } @@ -186,7 +186,7 @@ export function findFreePortFaster(startPort: number, giveUpAfter: number, timeo server.on('close', () => { doResolve(0, resolve); }); - server.listen(startPort, '127.0.0.1'); + server.listen(startPort, hostname); }); } diff --git a/src/vs/base/node/powershell.ts b/src/vs/base/node/powershell.ts index deb2115023..1f83a70fd3 100644 --- a/src/vs/base/node/powershell.ts +++ b/src/vs/base/node/powershell.ts @@ -79,7 +79,7 @@ export interface IPowerShellExeDetails { readonly exePath: string; } -export interface IPossiblePowerShellExe extends IPowerShellExeDetails { +interface IPossiblePowerShellExe extends IPowerShellExeDetails { exists(): Promise<boolean>; } diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index 0e2f57739a..3c876d4610 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -5,392 +5,23 @@ import * as cp from 'child_process'; import { Stats } from 'fs'; -import { IStringDictionary } from 'vs/base/common/collections'; -import * as extpath from 'vs/base/common/extpath'; -import { FileAccess } from 'vs/base/common/network'; -import * as Objects from 'vs/base/common/objects'; import * as path from 'vs/base/common/path'; import * as Platform from 'vs/base/common/platform'; import * as process from 'vs/base/common/process'; -import { CommandOptions, Executable, ForkOptions, Source, SuccessData, TerminateResponse, TerminateResponseCode } from 'vs/base/common/processes'; +import { CommandOptions, ForkOptions, Source, SuccessData, TerminateResponse, TerminateResponseCode } from 'vs/base/common/processes'; import * as Types from 'vs/base/common/types'; -import { LineDecoder } from 'vs/base/node/decoder'; import * as pfs from 'vs/base/node/pfs'; -import * as nls from 'vs/nls'; export { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode }; export type ValueCallback<T> = (value: T | Promise<T>) => void; export type ErrorCallback = (error?: any) => void; export type ProgressCallback<T> = (progress: T) => void; -export interface LineData { - line: string; - source: Source; -} - -function getWindowsCode(status: number): TerminateResponseCode { - switch (status) { - case 0: - return TerminateResponseCode.Success; - case 1: - return TerminateResponseCode.AccessDenied; - case 128: - return TerminateResponseCode.ProcessNotFound; - default: - return TerminateResponseCode.Unknown; - } -} - -function terminateProcess(process: cp.ChildProcess, cwd?: string): Promise<TerminateResponse> { - if (Platform.isWindows) { - try { - const options: any = { - stdio: ['pipe', 'pipe', 'ignore'] - }; - if (cwd) { - options.cwd = cwd; - } - const killProcess = cp.execFile('taskkill', ['/T', '/F', '/PID', process.pid!.toString()], options); - return new Promise(resolve => { - killProcess.once('error', (err) => { - resolve({ success: false, error: err }); - }); - killProcess.once('exit', (code, signal) => { - if (code === 0) { - resolve({ success: true }); - } else { - resolve({ success: false, code: code !== null ? code : TerminateResponseCode.Unknown }); - } - }); - }); - } catch (err) { - return Promise.resolve({ success: false, error: err, code: err.status ? getWindowsCode(err.status) : TerminateResponseCode.Unknown }); - } - } else if (Platform.isLinux || Platform.isMacintosh) { - try { - const cmd = FileAccess.asFileUri('vs/base/node/terminateProcess.sh', require).fsPath; - return new Promise(resolve => { - cp.execFile(cmd, [process.pid!.toString()], { encoding: 'utf8', shell: true } as cp.ExecFileOptions, (err, stdout, stderr) => { - if (err) { - resolve({ success: false, error: err }); - } else { - resolve({ success: true }); - } - }); - }); - } catch (err) { - return Promise.resolve({ success: false, error: err }); - } - } else { - process.kill('SIGKILL'); - } - return Promise.resolve({ success: true }); -} export function getWindowsShell(env = process.env as Platform.IProcessEnvironment): string { return env['comspec'] || 'cmd.exe'; } -export abstract class AbstractProcess<TProgressData> { - private cmd: string; - private args: string[]; - private options: CommandOptions | ForkOptions; - protected shell: boolean; - - private childProcess: cp.ChildProcess | null; - protected childProcessPromise: Promise<cp.ChildProcess> | null; - private pidResolve: ValueCallback<number> | undefined; - protected terminateRequested: boolean; - - private static WellKnowCommands: IStringDictionary<boolean> = { - 'ant': true, - 'cmake': true, - 'eslint': true, - 'gradle': true, - 'grunt': true, - 'gulp': true, - 'jake': true, - 'jenkins': true, - 'jshint': true, - 'make': true, - 'maven': true, - 'msbuild': true, - 'msc': true, - 'nmake': true, - 'npm': true, - 'rake': true, - 'tsc': true, - 'xbuild': true - }; - - public constructor(executable: Executable); - public constructor(cmd: string, args: string[] | undefined, shell: boolean, options: CommandOptions | undefined); - public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean, arg4?: CommandOptions) { - if (arg2 !== undefined && arg3 !== undefined && arg4 !== undefined) { - this.cmd = <string>arg1; - this.args = arg2; - this.shell = arg3; - this.options = arg4; - } else { - const executable = <Executable>arg1; - this.cmd = executable.command; - this.shell = executable.isShellCommand; - this.args = executable.args.slice(0); - this.options = executable.options || {}; - } - - this.childProcess = null; - this.childProcessPromise = null; - this.terminateRequested = false; - - if (this.options.env) { - const newEnv: IStringDictionary<string> = Object.create(null); - Object.keys(process.env).forEach((key) => { - newEnv[key] = process.env[key]!; - }); - Object.keys(this.options.env).forEach((key) => { - newEnv[key] = this.options.env![key]!; - }); - this.options.env = newEnv; - } - } - - public getSanitizedCommand(): string { - let result = this.cmd.toLowerCase(); - const index = result.lastIndexOf(path.sep); - if (index !== -1) { - result = result.substring(index + 1); - } - if (AbstractProcess.WellKnowCommands[result]) { - return result; - } - return 'other'; - } - - public start(pp: ProgressCallback<TProgressData>): Promise<SuccessData> { - if (Platform.isWindows && ((this.options && this.options.cwd && extpath.isUNC(this.options.cwd)) || !this.options && extpath.isUNC(process.cwd()))) { - return Promise.reject(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.'))); - } - return this.useExec().then((useExec) => { - let cc: ValueCallback<SuccessData>; - let ee: ErrorCallback; - const result = new Promise<any>((c, e) => { - cc = c; - ee = e; - }); - - if (useExec) { - let cmd: string = this.cmd; - if (this.args) { - cmd = cmd + ' ' + this.args.join(' '); - } - this.childProcess = cp.exec(cmd, this.options, (error, stdout, stderr) => { - this.childProcess = null; - const err: any = error; - // This is tricky since executing a command shell reports error back in case the executed command return an - // error or the command didn't exist at all. So we can't blindly treat an error as a failed command. So we - // always parse the output and report success unless the job got killed. - if (err && err.killed) { - ee({ killed: this.terminateRequested, stdout: stdout.toString(), stderr: stderr.toString() }); - } else { - this.handleExec(cc, pp, error, stdout as any, stderr as any); - } - }); - } else { - let childProcess: cp.ChildProcess | null = null; - const closeHandler = (data: any) => { - this.childProcess = null; - this.childProcessPromise = null; - this.handleClose(data, cc, pp, ee); - const result: SuccessData = { - terminated: this.terminateRequested - }; - if (Types.isNumber(data)) { - result.cmdCode = <number>data; - } - cc(result); - }; - if (this.shell && Platform.isWindows) { - const options: any = Objects.deepClone(this.options); - options.windowsVerbatimArguments = true; - options.detached = false; - let quotedCommand: boolean = false; - let quotedArg: boolean = false; - const commandLine: string[] = []; - let quoted = this.ensureQuotes(this.cmd); - commandLine.push(quoted.value); - quotedCommand = quoted.quoted; - if (this.args) { - this.args.forEach((elem) => { - quoted = this.ensureQuotes(elem); - commandLine.push(quoted.value); - quotedArg = quotedArg && quoted.quoted; - }); - } - const args: string[] = [ - '/s', - '/c', - ]; - if (quotedCommand) { - if (quotedArg) { - args.push('"' + commandLine.join(' ') + '"'); - } else if (commandLine.length > 1) { - args.push('"' + commandLine[0] + '"' + ' ' + commandLine.slice(1).join(' ')); - } else { - args.push('"' + commandLine[0] + '"'); - } - } else { - args.push(commandLine.join(' ')); - } - childProcess = cp.spawn(getWindowsShell(), args, options); - } else { - if (this.cmd) { - childProcess = cp.spawn(this.cmd, this.args, this.options); - } - } - if (childProcess) { - this.childProcess = childProcess; - this.childProcessPromise = Promise.resolve(childProcess); - if (this.pidResolve) { - this.pidResolve(Types.isNumber(childProcess.pid) ? childProcess.pid : -1); - this.pidResolve = undefined; - } - childProcess.on('error', (error: Error) => { - this.childProcess = null; - ee({ terminated: this.terminateRequested, error: error }); - }); - if (childProcess.pid) { - this.childProcess.on('close', closeHandler); - this.handleSpawn(childProcess, cc!, pp, ee!, true); - } - } - } - return result; - }); - } - - protected abstract handleExec(cc: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, error: Error | null, stdout: Buffer, stderr: Buffer): void; - protected abstract handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, ee: ErrorCallback, sync: boolean): void; - - protected handleClose(data: any, cc: ValueCallback<SuccessData>, pp: ProgressCallback<TProgressData>, ee: ErrorCallback): void { - // Default is to do nothing. - } - - private static readonly regexp = /^[^"].* .*[^"]/; - private ensureQuotes(value: string) { - if (AbstractProcess.regexp.test(value)) { - return { - value: '"' + value + '"', //`"${value}"`, - quoted: true - }; - } else { - return { - value: value, - quoted: value.length > 0 && value[0] === '"' && value[value.length - 1] === '"' - }; - } - } - - public get pid(): Promise<number> { - if (this.childProcessPromise) { - return this.childProcessPromise.then(childProcess => childProcess.pid!, err => -1); - } else { - return new Promise<number>((resolve) => { - this.pidResolve = resolve; - }); - } - } - - public terminate(): Promise<TerminateResponse> { - if (!this.childProcessPromise) { - return Promise.resolve<TerminateResponse>({ success: true }); - } - return this.childProcessPromise.then((childProcess) => { - this.terminateRequested = true; - return terminateProcess(childProcess, this.options.cwd).then(response => { - if (response.success) { - this.childProcess = null; - } - return response; - }); - }, (err) => { - return { success: true }; - }); - } - - private useExec(): Promise<boolean> { - return new Promise<boolean>(resolve => { - if (!this.shell || !Platform.isWindows) { - return resolve(false); - } - const cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']); - cmdShell.on('error', (error: Error) => { - return resolve(true); - }); - cmdShell.on('exit', (data: any) => { - return resolve(false); - }); - }); - } -} - -export class LineProcess extends AbstractProcess<LineData> { - - private stdoutLineDecoder: LineDecoder | null; - private stderrLineDecoder: LineDecoder | null; - - public constructor(executable: Executable); - public constructor(cmd: string, args: string[], shell: boolean, options: CommandOptions); - public constructor(arg1: string | Executable, arg2?: string[], arg3?: boolean | ForkOptions, arg4?: CommandOptions) { - super(<any>arg1, arg2, <any>arg3, arg4); - - this.stdoutLineDecoder = null; - this.stderrLineDecoder = null; - } - - protected handleExec(cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, error: Error, stdout: Buffer, stderr: Buffer) { - [stdout, stderr].forEach((buffer: Buffer, index: number) => { - const lineDecoder = new LineDecoder(); - const lines = lineDecoder.write(buffer); - lines.forEach((line) => { - pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr }); - }); - const line = lineDecoder.end(); - if (line) { - pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr }); - } - }); - cc({ terminated: this.terminateRequested, error: error }); - } - - protected handleSpawn(childProcess: cp.ChildProcess, cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, ee: ErrorCallback, sync: boolean): void { - const stdoutLineDecoder = new LineDecoder(); - const stderrLineDecoder = new LineDecoder(); - childProcess.stdout!.on('data', (data: Buffer) => { - const lines = stdoutLineDecoder.write(data); - lines.forEach(line => pp({ line: line, source: Source.stdout })); - }); - childProcess.stderr!.on('data', (data: Buffer) => { - const lines = stderrLineDecoder.write(data); - lines.forEach(line => pp({ line: line, source: Source.stderr })); - }); - - this.stdoutLineDecoder = stdoutLineDecoder; - this.stderrLineDecoder = stderrLineDecoder; - } - - protected override handleClose(data: any, cc: ValueCallback<SuccessData>, pp: ProgressCallback<LineData>, ee: ErrorCallback): void { - const stdoutLine = this.stdoutLineDecoder ? this.stdoutLineDecoder.end() : null; - if (stdoutLine) { - pp({ line: stdoutLine, source: Source.stdout }); - } - const stderrLine = this.stderrLineDecoder ? this.stderrLineDecoder.end() : null; - if (stderrLine) { - pp({ line: stderrLine, source: Source.stderr }); - } - } -} - export interface IQueuedSender { send: (msg: any) => void; } diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 9d29195666..0f2752c5b5 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -48,14 +48,11 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> { function findName(cmd: string): string { - const SHARED_PROCESS_HINT = /--vscode-window-kind=shared-process/; - const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/; - const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/; - const UTILITY_NETWORK_HINT = /--utility-sub-type=network/; - const UTILITY_EXTENSION_HOST_HINT = /--utility-sub-type=node.mojom.NodeService/; - const WINDOWS_CRASH_REPORTER = /--crashes-directory/; - const WINDOWS_PTY = /\\pipe\\winpty-control/; - const WINDOWS_CONSOLE_HOST = /conhost\.exe/; + const UTILITY_NETWORK_HINT = /--utility-sub-type=network/i; + const NODEJS_PROCESS_HINT = /--ms-enable-electron-run-as-node/i; + const WINDOWS_CRASH_REPORTER = /--crashes-directory/i; + const WINDOWS_PTY = /\\pipe\\winpty-control/i; + const WINDOWS_CONSOLE_HOST = /conhost\.exe/i; const TYPE = /--type=([a-zA-Z-]+)/; // find windows crash reporter @@ -68,7 +65,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> { return 'winpty-process'; } - //find windows console host process + // find windows console host process if (WINDOWS_CONSOLE_HOST.exec(cmd)) { return 'console-window-host (Windows internal process)'; } @@ -77,27 +74,15 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> { let matches = TYPE.exec(cmd); if (matches && matches.length === 2) { if (matches[1] === 'renderer') { - if (SHARED_PROCESS_HINT.exec(cmd)) { - return 'shared-process'; - } - - if (ISSUE_REPORTER_HINT.exec(cmd)) { - return 'issue-reporter'; - } - - if (PROCESS_EXPLORER_HINT.exec(cmd)) { - return 'process-explorer'; - } - return `window`; } else if (matches[1] === 'utility') { if (UTILITY_NETWORK_HINT.exec(cmd)) { return 'utility-network-service'; } - if (UTILITY_EXTENSION_HOST_HINT.exec(cmd)) { - return 'extension-host'; - } + return 'utility-process'; + } else if (matches[1] === 'extensionHost') { + return 'extension-host'; // normalize remote extension host type } return matches[1]; } @@ -114,9 +99,15 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> { if (result) { if (cmd.indexOf('node ') < 0 && cmd.indexOf('node.exe') < 0) { - return `electron_node ${result}`; + return `electron-nodejs (${result})`; } } + + // find Electron node.js processes + if (NODEJS_PROCESS_HINT.exec(cmd)) { + return `electron-nodejs (${cmd})`; + } + return cmd; } @@ -136,7 +127,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> { } }; - (import('windows-process-tree')).then(windowsProcessTree => { + (import('@vscode/windows-process-tree')).then(windowsProcessTree => { windowsProcessTree.getProcessList(rootPid, (processList) => { if (!processList) { reject(new Error(`Root process ${rootPid} not found`)); @@ -178,7 +169,10 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> { reject(new Error(`Root process ${rootPid} not found`)); } }); - }, windowsProcessTree.ProcessDataFlag.CommandLine | windowsProcessTree.ProcessDataFlag.Memory); + }, + // Workaround duplicate enum identifiers issue in @vscode/windows-process-tree + // Ref https://github.com/microsoft/vscode/pull/179508 + (windowsProcessTree.ProcessDataFlag as any).CommandLine | (windowsProcessTree.ProcessDataFlag as any).Memory); }); } else { // OS X & Linux function calculateLinuxCpuUsage() { @@ -198,7 +192,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> { // The cpu usage value reported on Linux is the average over the process lifetime, // recalculate the usage over a one second interval // JSON.stringify is needed to escape spaces, https://github.com/nodejs/node/issues/6803 - let cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/cpuUsage.sh', require).fsPath); + let cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/cpuUsage.sh').fsPath); cmd += ' ' + pids.join(' '); exec(cmd, {}, (err, stdout, stderr) => { @@ -226,7 +220,7 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> { if (process.platform !== 'linux') { reject(err || new Error(stderr.toString())); } else { - const cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/ps.sh', require).fsPath); + const cmd = JSON.stringify(FileAccess.asFileUri('vs/base/node/ps.sh').fsPath); exec(cmd, {}, (err, stdout, stderr) => { if (err || stderr) { reject(err || new Error(stderr.toString())); diff --git a/src/vs/base/node/shell.ts b/src/vs/base/node/shell.ts index 01959cf364..7aa4f9285a 100644 --- a/src/vs/base/node/shell.ts +++ b/src/vs/base/node/shell.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { release, userInfo } from 'os'; +import { userInfo } from 'os'; import * as platform from 'vs/base/common/platform'; import { getFirstAvailablePowerShellInstallation } from 'vs/base/node/powershell'; import * as processes from 'vs/base/node/processes'; @@ -25,18 +25,6 @@ export async function getSystemShell(os: platform.OperatingSystem, env: platform return getSystemShellUnixLike(os, env); } -export function getSystemShellSync(os: platform.OperatingSystem, env: platform.IProcessEnvironment): string { - if (os === platform.OperatingSystem.Windows) { - if (platform.isWindows) { - return getSystemShellWindowsSync(env); - } - // Don't detect Windows shell when not on Windows - return processes.getWindowsShell(env); - } - - return getSystemShellUnixLike(os, env); -} - let _TERMINAL_DEFAULT_SHELL_UNIX_LIKE: string | null = null; function getSystemShellUnixLike(os: platform.OperatingSystem, env: platform.IProcessEnvironment): string { // Only use $SHELL for the current OS @@ -80,14 +68,3 @@ async function getSystemShellWindows(): Promise<string> { } return _TERMINAL_DEFAULT_SHELL_WINDOWS; } - -function getSystemShellWindowsSync(env: platform.IProcessEnvironment): string { - if (_TERMINAL_DEFAULT_SHELL_WINDOWS) { - return _TERMINAL_DEFAULT_SHELL_WINDOWS; - } - - const isAtLeastWindows10 = platform.isWindows && parseFloat(release()) >= 10; - const is32ProcessOn64Windows = env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const powerShellPath = `${env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}\\WindowsPowerShell\\v1.0\\powershell.exe`; - return isAtLeastWindows10 ? powerShellPath : processes.getWindowsShell(env); -} diff --git a/src/vs/base/node/unc.d.ts b/src/vs/base/node/unc.d.ts new file mode 100644 index 0000000000..521d41760b --- /dev/null +++ b/src/vs/base/node/unc.d.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Helper to get the hostname of a possible UNC path. + */ +export function getUNCHost(maybeUNCPath: string | undefined | null): string | undefined; + +/** + * Returns the current list of allowed UNC hosts as defined by node.js. + */ +export function getUNCHostAllowlist(): string[]; + +/** + * Adds one to many UNC host(s) to the allowed list in node.js. + */ +export function addUNCHostToAllowlist(allowedHost: string | string[]): void; + +/** + * Disables UNC Host allow list in node.js and thus disables UNC + * path validation. + */ +export function disableUNCAccessRestrictions(): void; + +/** + * Whether UNC Host allow list in node.js is disabled. + */ +export function isUNCAccessRestrictionsDisabled(): boolean; diff --git a/src/vs/base/node/unc.js b/src/vs/base/node/unc.js new file mode 100644 index 0000000000..94045a6e07 --- /dev/null +++ b/src/vs/base/node/unc.js @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +//@ts-check + +(function () { + function factory() { + + /** + * @returns {Set<string> | undefined} + */ + function processUNCHostAllowlist() { + + // The property `process.uncHostAllowlist` is not available in official node.js + // releases, only in our own builds, so we have to probe for availability + + return process.uncHostAllowlist; + } + + /** + * @param {unknown} arg0 + * @returns {string[]} + */ + function toSafeStringArray(arg0) { + const allowedUNCHosts = new Set(); + + if (Array.isArray(arg0)) { + for (const host of arg0) { + if (typeof host === 'string') { + allowedUNCHosts.add(host); + } + } + } + + return Array.from(allowedUNCHosts); + } + + /** + * @returns {string[]} + */ + function getUNCHostAllowlist() { + const allowlist = processUNCHostAllowlist(); + if (allowlist) { + return Array.from(allowlist); + } + + return []; + } + + /** + * @param {string | string[]} allowedHost + */ + function addUNCHostToAllowlist(allowedHost) { + if (process.platform !== 'win32') { + return; + } + + const allowlist = processUNCHostAllowlist(); + if (allowlist) { + if (typeof allowedHost === 'string') { + allowlist.add(allowedHost.toLowerCase()); // UNC hosts are case-insensitive + } else { + for (const host of toSafeStringArray(allowedHost)) { + addUNCHostToAllowlist(host); + } + } + } + } + + /** + * @param {string | undefined | null} maybeUNCPath + * @returns {string | undefined} + */ + function getUNCHost(maybeUNCPath) { + if (typeof maybeUNCPath !== 'string') { + return undefined; // require a valid string + } + + const uncRoots = [ + '\\\\.\\UNC\\', // DOS Device paths (https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats) + '\\\\?\\UNC\\', + '\\\\' // standard UNC path + ]; + + let host = undefined; + + for (const uncRoot of uncRoots) { + const indexOfUNCRoot = maybeUNCPath.indexOf(uncRoot); + if (indexOfUNCRoot !== 0) { + continue; // not matching any of our expected UNC roots + } + + const indexOfUNCPath = maybeUNCPath.indexOf('\\', uncRoot.length); + if (indexOfUNCPath === -1) { + continue; // no path component found + } + + const hostCandidate = maybeUNCPath.substring(uncRoot.length, indexOfUNCPath); + if (hostCandidate) { + host = hostCandidate; + break; + } + } + + return host; + } + + function disableUNCAccessRestrictions() { + if (process.platform !== 'win32') { + return; + } + + process.enableUNCAccessChecks = false; + } + + function isUNCAccessRestrictionsDisabled() { + if (process.platform !== 'win32') { + return true; + } + + return process.enableUNCAccessChecks === false; + } + + return { + getUNCHostAllowlist, + addUNCHostToAllowlist, + getUNCHost, + disableUNCAccessRestrictions, + isUNCAccessRestrictionsDisabled + }; + } + + if (typeof define === 'function') { + // amd + define([], function () { return factory(); }); + } else if (typeof module === 'object' && typeof module.exports === 'object') { + // commonjs + module.exports = factory(); + } else { + console.trace('vs/base/node/unc defined in UNKNOWN context (neither requirejs or commonjs)'); + } +})(); diff --git a/src/vs/base/node/zip.ts b/src/vs/base/node/zip.ts index 30730a853f..69dedf8596 100644 --- a/src/vs/base/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -14,6 +14,9 @@ import * as nls from 'vs/nls'; import { Entry, open as _openZip, ZipFile } from 'yauzl'; import * as yazl from 'yazl'; +export const CorruptZipMessage: string = 'end of central directory record signature not found'; +const CORRUPT_ZIP_PATTERN = new RegExp(CorruptZipMessage); + export interface IExtractOptions { overwrite?: boolean; @@ -63,7 +66,7 @@ function toExtractError(err: Error): ExtractError { let type: ExtractErrorType | undefined = undefined; - if (/end of central directory record signature not found/.test(err.message)) { + if (CORRUPT_ZIP_PATTERN.test(err.message)) { type = 'CorruptZip'; } @@ -81,9 +84,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa let istream: WriteStream; token.onCancellationRequested(() => { - if (istream) { - istream.destroy(); - } + istream?.destroy(); }); return Promise.resolve(Promises.mkdir(targetDirName, { recursive: true })).then(() => new Promise<void>((c, e) => { diff --git a/src/vs/base/parts/ipc/common/ipc.net.ts b/src/vs/base/parts/ipc/common/ipc.net.ts index d6833ae6ed..1969a58e59 100644 --- a/src/vs/base/parts/ipc/common/ipc.net.ts +++ b/src/vs/base/parts/ipc/common/ipc.net.ts @@ -3,9 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IntervalTimer } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { IIPCLogger, IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc'; export const enum SocketDiagnosticsEventType { @@ -136,6 +137,12 @@ export interface WebSocketCloseEvent { export type SocketCloseEvent = NodeSocketCloseEvent | WebSocketCloseEvent | undefined; +export interface SocketTimeoutEvent { + readonly unacknowledgedMsgCount: number; + readonly timeSinceOldestUnacknowledgedMsg: number; + readonly timeSinceLastReceivedSomeData: number; +} + export interface ISocket extends IDisposable { onData(listener: (e: VSBuffer) => void): IDisposable; onClose(listener: (e: SocketCloseEvent) => void): IDisposable; @@ -256,7 +263,10 @@ const enum ProtocolMessageType { Disconnect = 5, ReplayRequest = 6, Pause = 7, - Resume = 8 + Resume = 8, + KeepAlive = 9, + LatencyMeasurementRequest = 10, + LatencyMeasurementResponse = 11, } function protocolMessageTypeToString(messageType: ProtocolMessageType) { @@ -269,6 +279,9 @@ function protocolMessageTypeToString(messageType: ProtocolMessageType) { case ProtocolMessageType.ReplayRequest: return 'ReplayRequest'; case ProtocolMessageType.Pause: return 'PauseWriting'; case ProtocolMessageType.Resume: return 'ResumeWriting'; + case ProtocolMessageType.KeepAlive: return 'KeepAlive'; + case ProtocolMessageType.LatencyMeasurementRequest: return 'LatencyMeasurementRequest'; + case ProtocolMessageType.LatencyMeasurementResponse: return 'LatencyMeasurementResponse'; } } @@ -292,6 +305,26 @@ export const enum ProtocolConstants { * Maximal grace time between the first and the last reconnection... */ ReconnectionShortGraceTime = 5 * 60 * 1000, // 5min + /** + * Send a message every 5 seconds to avoid that the connection is closed by the OS. + */ + KeepAliveSendTime = 5000, // 5 seconds + /** + * Measure the latency every 1 minute. + */ + LatencySampleTime = 1 * 60 * 1000, // 1 minute + /** + * Keep the last 5 samples for latency measurement. + */ + LatencySampleCount = 5, + /** + * A latency over 1s will be considered high. + */ + HighLatencyTimeThreshold = 1000, + /** + * Having 3 or more samples with high latency will trigger a high latency event. + */ + HighLatencySampleThreshold = 3, } class ProtocolMessage { @@ -604,14 +637,14 @@ export class BufferedEmitter<T> { constructor() { this._emitter = new Emitter<T>({ - onFirstListenerAdd: () => { + onWillAddFirstListener: () => { this._hasListeners = true; // it is important to deliver these messages after this call, but before // other messages have a chance to be received (to guarantee in order delivery) // that's why we're using here queueMicrotask and not other types of timeouts queueMicrotask(() => this._deliverMessages()); }, - onLastListenerRemove: () => { + onDidRemoveLastListener: () => { this._hasListeners = false; } }); @@ -667,6 +700,16 @@ class Queue<T> { this._last = null; } + public length(): number { + let result = 0; + let current = this._first; + while (current) { + current = current.next; + result++; + } + return result; + } + public peek(): T | null { if (!this._first) { return null; @@ -760,6 +803,75 @@ export interface ILoadEstimator { hasHighLoad(): boolean; } +export const enum ConnectionHealth { + /** + * The connection health is considered good when a certain number of recent round trip time measurements are below a certain threshold. + * @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold + */ + Good, + /** + * The connection health is considered poor when a certain number of recent round trip time measurements are above a certain threshold. + * @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold + */ + Poor +} + +export function connectionHealthToString(connectionHealth: ConnectionHealth): 'good' | 'poor' { + switch (connectionHealth) { + case ConnectionHealth.Good: return 'good'; + case ConnectionHealth.Poor: return 'poor'; + } +} + +/** + * An event describing that the connection health has changed. + */ +export class ConnectionHealthChangedEvent { + constructor( + public readonly connectionHealth: ConnectionHealth + ) { } +} + +/** + * An event describing that a round trip time measurement was above a certain threshold. + */ +export class HighRoundTripTimeEvent { + constructor( + /** + * The round trip time in milliseconds. + */ + public readonly roundTripTime: number, + /** + * The number of recent round trip time measurements that were above the threshold. + * @see ProtocolConstants.HighLatencyTimeThreshold @see ProtocolConstants.HighLatencySampleThreshold + */ + public readonly recentHighRoundTripCount: number + ) { } +} + +export interface PersistentProtocolOptions { + /** + * The socket to use. + */ + socket: ISocket; + /** + * The initial chunk of data that has already been received from the socket. + */ + initialChunk?: VSBuffer | null; + /** + * The CPU load estimator to use. + */ + loadEstimator?: ILoadEstimator; + /** + * Whether to measure round trip time. Defaults to false. + */ + measureRoundTripTime?: boolean; + /** + * Whether to send keep alive messages. Defaults to true. + */ + sendKeepAlive?: boolean; +} + /** * Same as Protocol, but will actually track messages and acks. * Moreover, it will ensure no messages are lost if there are no event listeners. @@ -778,15 +890,20 @@ export class PersistentProtocol implements IMessagePassingProtocol { private _incomingMsgLastTime: number; private _incomingAckTimeout: any | null; + private _keepAliveInterval: any | null; + private _lastReplayRequestTime: number; private _lastSocketTimeoutTime: number; private _socket: ISocket; private _socketWriter: ProtocolWriter; private _socketReader: ProtocolReader; - private _socketDisposables: IDisposable[]; + private _socketLatencyMonitor: LatencyMonitor; + private _socketDisposables: DisposableStore; private readonly _loadEstimator: ILoadEstimator; + private readonly _measureRoundTripTime: boolean; + private readonly _shouldSendKeepAlive: boolean; private readonly _onControlMessage = new BufferedEmitter<VSBuffer>(); readonly onControlMessage: Event<VSBuffer> = this._onControlMessage.event; @@ -800,15 +917,23 @@ export class PersistentProtocol implements IMessagePassingProtocol { private readonly _onSocketClose = new BufferedEmitter<SocketCloseEvent>(); readonly onSocketClose: Event<SocketCloseEvent> = this._onSocketClose.event; - private readonly _onSocketTimeout = new BufferedEmitter<void>(); - readonly onSocketTimeout: Event<void> = this._onSocketTimeout.event; + private readonly _onSocketTimeout = new BufferedEmitter<SocketTimeoutEvent>(); + readonly onSocketTimeout: Event<SocketTimeoutEvent> = this._onSocketTimeout.event; + + private readonly _onHighRoundTripTime = new BufferedEmitter<HighRoundTripTimeEvent>(); + readonly onHighRoundTripTime = this._onHighRoundTripTime.event; + + private readonly _onDidChangeConnectionHealth = new BufferedEmitter<ConnectionHealth>(); + readonly onDidChangeConnectionHealth = this._onDidChangeConnectionHealth.event; public get unacknowledgedCount(): number { return this._outgoingMsgId - this._outgoingAckId; } - constructor(socket: ISocket, initialChunk: VSBuffer | null = null, loadEstimator: ILoadEstimator = LoadEstimator.getInstance()) { - this._loadEstimator = loadEstimator; + constructor(opts: PersistentProtocolOptions) { + this._loadEstimator = opts.loadEstimator ?? LoadEstimator.getInstance(); + this._measureRoundTripTime = opts.measureRoundTripTime ?? false; + this._shouldSendKeepAlive = opts.sendKeepAlive ?? true; this._isReconnecting = false; this._outgoingUnackMsg = new Queue<ProtocolMessage>(); this._outgoingMsgId = 0; @@ -823,16 +948,30 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._lastReplayRequestTime = 0; this._lastSocketTimeoutTime = Date.now(); - this._socketDisposables = []; - this._socket = socket; - this._socketWriter = new ProtocolWriter(this._socket); - this._socketDisposables.push(this._socketWriter); - this._socketReader = new ProtocolReader(this._socket); - this._socketDisposables.push(this._socketReader); - this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg))); - this._socketDisposables.push(this._socket.onClose((e) => this._onSocketClose.fire(e))); - if (initialChunk) { - this._socketReader.acceptChunk(initialChunk); + this._socketDisposables = new DisposableStore(); + this._socket = opts.socket; + this._socketWriter = this._socketDisposables.add(new ProtocolWriter(this._socket)); + this._socketReader = this._socketDisposables.add(new ProtocolReader(this._socket)); + this._socketDisposables.add(this._socketReader.onMessage(msg => this._receiveMessage(msg))); + this._socketDisposables.add(this._socket.onClose(e => this._onSocketClose.fire(e))); + this._socketLatencyMonitor = this._socketDisposables.add(new LatencyMonitor()); // is started immediately + this._socketDisposables.add(this._socketLatencyMonitor.onSendLatencyRequest(buffer => this._sendLatencyMeasurementRequest(buffer))); + this._socketDisposables.add(this._socketLatencyMonitor.onHighRoundTripTime(e => this._onHighRoundTripTime.fire(e))); + this._socketDisposables.add(this._socketLatencyMonitor.onDidChangeConnectionHealth(e => this._onDidChangeConnectionHealth.fire(e))); + if (this._measureRoundTripTime) { + this._socketLatencyMonitor.start(); + } + + if (opts.initialChunk) { + this._socketReader.acceptChunk(opts.initialChunk); + } + + if (this._shouldSendKeepAlive) { + this._keepAliveInterval = setInterval(() => { + this._sendKeepAlive(); + }, ProtocolConstants.KeepAliveSendTime); + } else { + this._keepAliveInterval = null; } } @@ -845,7 +984,11 @@ export class PersistentProtocol implements IMessagePassingProtocol { clearTimeout(this._incomingAckTimeout); this._incomingAckTimeout = null; } - this._socketDisposables = dispose(this._socketDisposables); + if (this._keepAliveInterval) { + clearInterval(this._keepAliveInterval); + this._keepAliveInterval = null; + } + this._socketDisposables.dispose(); } drain(): Promise<void> { @@ -883,7 +1026,8 @@ export class PersistentProtocol implements IMessagePassingProtocol { public beginAcceptReconnection(socket: ISocket, initialDataChunk: VSBuffer | null): void { this._isReconnecting = true; - this._socketDisposables = dispose(this._socketDisposables); + this._socketDisposables.dispose(); + this._socketDisposables = new DisposableStore(); this._onControlMessage.flushBuffer(); this._onSocketClose.flushBuffer(); this._onSocketTimeout.flushBuffer(); @@ -893,17 +1037,23 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._lastSocketTimeoutTime = Date.now(); this._socket = socket; - this._socketWriter = new ProtocolWriter(this._socket); - this._socketDisposables.push(this._socketWriter); - this._socketReader = new ProtocolReader(this._socket); - this._socketDisposables.push(this._socketReader); - this._socketDisposables.push(this._socketReader.onMessage(msg => this._receiveMessage(msg))); - this._socketDisposables.push(this._socket.onClose((e) => this._onSocketClose.fire(e))); + this._socketWriter = this._socketDisposables.add(new ProtocolWriter(this._socket)); + this._socketReader = this._socketDisposables.add(new ProtocolReader(this._socket)); + this._socketDisposables.add(this._socketReader.onMessage(msg => this._receiveMessage(msg))); + this._socketDisposables.add(this._socket.onClose(e => this._onSocketClose.fire(e))); + this._socketLatencyMonitor = this._socketDisposables.add(new LatencyMonitor()); // will be started later + this._socketDisposables.add(this._socketLatencyMonitor.onSendLatencyRequest(buffer => this._sendLatencyMeasurementRequest(buffer))); + this._socketDisposables.add(this._socketLatencyMonitor.onHighRoundTripTime(e => this._onHighRoundTripTime.fire(e))); + this._socketDisposables.add(this._socketLatencyMonitor.onDidChangeConnectionHealth(e => this._onDidChangeConnectionHealth.fire(e))); + this._socketReader.acceptChunk(initialDataChunk); } public endAcceptReconnection(): void { this._isReconnecting = false; + if (this._measureRoundTripTime) { + this._socketLatencyMonitor.start(); + } // After a reconnection, let the other party know (again) which messages have been received. // (perhaps the other party didn't receive a previous ACK) @@ -966,7 +1116,7 @@ export class PersistentProtocol implements IMessagePassingProtocol { break; } case ProtocolMessageType.Ack: { - // nothing to do + // nothing to do, .ack is handled above already break; } case ProtocolMessageType.Disconnect: { @@ -990,6 +1140,19 @@ export class PersistentProtocol implements IMessagePassingProtocol { this._socketWriter.resume(); break; } + case ProtocolMessageType.KeepAlive: { + // nothing to do + break; + } + case ProtocolMessageType.LatencyMeasurementRequest: { + // we just send the data back + this._sendLatencyMeasurementResponse(msg.data); + break; + } + case ProtocolMessageType.LatencyMeasurementResponse: { + this._socketLatencyMonitor.handleResponse(msg.data); + break; + } } } @@ -1081,7 +1244,11 @@ export class PersistentProtocol implements IMessagePassingProtocol { if (!this._loadEstimator.hasHighLoad()) { // Trash the socket this._lastSocketTimeoutTime = Date.now(); - this._onSocketTimeout.fire(undefined); + this._onSocketTimeout.fire({ + unacknowledgedMsgCount: this._outgoingUnackMsg.length(), + timeSinceOldestUnacknowledgedMsg, + timeSinceLastReceivedSomeData + }); return; } } @@ -1109,6 +1276,98 @@ export class PersistentProtocol implements IMessagePassingProtocol { const msg = new ProtocolMessage(ProtocolMessageType.Ack, 0, this._incomingAckId, getEmptyBuffer()); this._socketWriter.write(msg); } + + private _sendKeepAlive(): void { + this._incomingAckId = this._incomingMsgId; + const msg = new ProtocolMessage(ProtocolMessageType.KeepAlive, 0, this._incomingAckId, getEmptyBuffer()); + this._socketWriter.write(msg); + } + + private _sendLatencyMeasurementRequest(buffer: VSBuffer): void { + this._incomingAckId = this._incomingMsgId; + const msg = new ProtocolMessage(ProtocolMessageType.LatencyMeasurementRequest, 0, this._incomingAckId, buffer); + this._socketWriter.write(msg); + } + + private _sendLatencyMeasurementResponse(buffer: VSBuffer): void { + this._incomingAckId = this._incomingMsgId; + const msg = new ProtocolMessage(ProtocolMessageType.LatencyMeasurementResponse, 0, this._incomingAckId, buffer); + this._socketWriter.write(msg); + } +} + +class LatencyMonitor extends Disposable { + + private readonly _onSendLatencyRequest = this._register(new Emitter<VSBuffer>()); + readonly onSendLatencyRequest: Event<VSBuffer> = this._onSendLatencyRequest.event; + + private readonly _onHighRoundTripTime = this._register(new Emitter<HighRoundTripTimeEvent>()); + public readonly onHighRoundTripTime = this._onHighRoundTripTime.event; + + private readonly _onDidChangeConnectionHealth = this._register(new Emitter<ConnectionHealth>()); + public readonly onDidChangeConnectionHealth = this._onDidChangeConnectionHealth.event; + + private readonly _measureLatencyTimer = this._register(new IntervalTimer()); + + /** + * Timestamp of our last latency request message sent to the other host. + */ + private _lastLatencyMeasurementSent: number = -1; + + /** + * ID separate from the regular message IDs. Used to match up latency + * requests with responses so we know we're timing the right message + * even if a reconnection occurs. + */ + private _lastLatencyMeasurementId: number = 0; + + /** + * Circular buffer of latency measurements + */ + private _latencySamples: number[] = Array.from({ length: ProtocolConstants.LatencySampleCount }, (_) => 0); + private _latencySampleIndex: number = 0; + private _connectionHealth = ConnectionHealth.Good; + + constructor() { + super(); + } + + public start(): void { + this._measureLatencyTimer.cancelAndSet(() => { + this._lastLatencyMeasurementSent = Date.now(); + const measurementId = ++this._lastLatencyMeasurementId; + const buffer = VSBuffer.alloc(4); + buffer.writeUInt32BE(measurementId, 0); + this._onSendLatencyRequest.fire(buffer); + }, ProtocolConstants.LatencySampleTime); + } + + public handleResponse(buffer: VSBuffer): void { + if (buffer.byteLength !== 4) { + // invalid measurementId + return; + } + const measurementId = buffer.readUInt32BE(0); + if (this._lastLatencyMeasurementSent <= 0 || measurementId !== this._lastLatencyMeasurementId) { + // invalid measurementId + return; + } + + const roundtripTime = Date.now() - this._lastLatencyMeasurementSent; + const sampleIndex = this._latencySampleIndex++; + this._latencySamples[sampleIndex % this._latencySamples.length] = roundtripTime; + + const previousConnectionHealth = this._connectionHealth; + const highLatencySampleCount = this._latencySamples.filter(s => s >= ProtocolConstants.HighLatencyTimeThreshold).length; + this._connectionHealth = (highLatencySampleCount >= ProtocolConstants.HighLatencySampleThreshold ? ConnectionHealth.Poor : ConnectionHealth.Good); + + if (roundtripTime > ProtocolConstants.HighLatencyTimeThreshold) { + this._onHighRoundTripTime.fire(new HighRoundTripTimeEvent(roundtripTime, highLatencySampleCount)); + } + if (previousConnectionHealth !== this._connectionHealth) { + this._onDidChangeConnectionHealth.fire(this._connectionHealth); + } + } } // (() => { diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 69e3ecaefb..9dc5ec6ee0 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -8,7 +8,7 @@ import { CancelablePromise, createCancelablePromise, timeout } from 'vs/base/com import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; -import * as errors from 'vs/base/common/errors'; +import { CancellationError } from 'vs/base/common/errors'; import { Emitter, Event, EventMultiplexer, Relay } from 'vs/base/common/event'; import { combinedDisposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; @@ -36,7 +36,7 @@ export interface IServerChannel<TContext = string> { listen<T>(ctx: TContext, event: string, arg?: any): Event<T>; } -export const enum RequestType { +const enum RequestType { Promise = 100, PromiseCancel = 101, EventListen = 102, @@ -62,7 +62,7 @@ type IRawEventListenRequest = { type: RequestType.EventListen; id: number; chann type IRawEventDisposeRequest = { type: RequestType.EventDispose; id: number }; type IRawRequest = IRawPromiseRequest | IRawPromiseCancelRequest | IRawEventListenRequest | IRawEventDisposeRequest; -export const enum ResponseType { +const enum ResponseType { Initialize = 200, PromiseSuccess = 201, PromiseError = 202, @@ -164,7 +164,50 @@ interface IWriter { write(buffer: VSBuffer): void; } -class BufferReader implements IReader { + +/** + * @see https://en.wikipedia.org/wiki/Variable-length_quantity + */ +function readIntVQL(reader: IReader) { + let value = 0; + for (let n = 0; ; n += 7) { + const next = reader.read(1); + value |= (next.buffer[0] & 0b01111111) << n; + if (!(next.buffer[0] & 0b10000000)) { + return value; + } + } +} + +const vqlZero = createOneByteBuffer(0); + +/** + * @see https://en.wikipedia.org/wiki/Variable-length_quantity + */ +function writeInt32VQL(writer: IWriter, value: number) { + if (value === 0) { + writer.write(vqlZero); + return; + } + + let len = 0; + for (let v2 = value; v2 !== 0; v2 = v2 >>> 7) { + len++; + } + + const scratch = VSBuffer.alloc(len); + for (let i = 0; value !== 0; i++) { + scratch.buffer[i] = value & 0b01111111; + value = value >>> 7; + if (value > 0) { + scratch.buffer[i] |= 0b10000000; + } + } + + writer.write(scratch); +} + +export class BufferReader implements IReader { private pos = 0; @@ -177,7 +220,7 @@ class BufferReader implements IReader { } } -class BufferWriter implements IWriter { +export class BufferWriter implements IWriter { private buffers: VSBuffer[] = []; @@ -196,17 +239,8 @@ enum DataType { Buffer = 2, VSBuffer = 3, Array = 4, - Object = 5 -} - -function createSizeBuffer(size: number): VSBuffer { - const result = VSBuffer.alloc(4); - result.writeUInt32BE(size, 0); - return result; -} - -function readSizeBuffer(reader: IReader): number { - return reader.read(4).readUInt32BE(0); + Object = 5, + Int = 6 } function createOneByteBuffer(value: number): VSBuffer { @@ -222,53 +256,58 @@ const BufferPresets = { VSBuffer: createOneByteBuffer(DataType.VSBuffer), Array: createOneByteBuffer(DataType.Array), Object: createOneByteBuffer(DataType.Object), + Uint: createOneByteBuffer(DataType.Int), }; declare const Buffer: any; const hasBuffer = (typeof Buffer !== 'undefined'); -function serialize(writer: IWriter, data: any): void { +export function serialize(writer: IWriter, data: any): void { if (typeof data === 'undefined') { writer.write(BufferPresets.Undefined); } else if (typeof data === 'string') { const buffer = VSBuffer.fromString(data); writer.write(BufferPresets.String); - writer.write(createSizeBuffer(buffer.byteLength)); + writeInt32VQL(writer, buffer.byteLength); writer.write(buffer); } else if (hasBuffer && Buffer.isBuffer(data)) { const buffer = VSBuffer.wrap(data); writer.write(BufferPresets.Buffer); - writer.write(createSizeBuffer(buffer.byteLength)); + writeInt32VQL(writer, buffer.byteLength); writer.write(buffer); } else if (data instanceof VSBuffer) { writer.write(BufferPresets.VSBuffer); - writer.write(createSizeBuffer(data.byteLength)); + writeInt32VQL(writer, data.byteLength); writer.write(data); } else if (Array.isArray(data)) { writer.write(BufferPresets.Array); - writer.write(createSizeBuffer(data.length)); + writeInt32VQL(writer, data.length); for (const el of data) { serialize(writer, el); } + } else if (typeof data === 'number' && (data | 0) === data) { + // write a vql if it's a number that we can do bitwise operations on + writer.write(BufferPresets.Uint); + writeInt32VQL(writer, data); } else { const buffer = VSBuffer.fromString(JSON.stringify(data)); writer.write(BufferPresets.Object); - writer.write(createSizeBuffer(buffer.byteLength)); + writeInt32VQL(writer, buffer.byteLength); writer.write(buffer); } } -function deserialize(reader: IReader): any { +export function deserialize(reader: IReader): any { const type = reader.read(1).readUInt8(0); switch (type) { case DataType.Undefined: return undefined; - case DataType.String: return reader.read(readSizeBuffer(reader)).toString(); - case DataType.Buffer: return reader.read(readSizeBuffer(reader)).buffer; - case DataType.VSBuffer: return reader.read(readSizeBuffer(reader)); + case DataType.String: return reader.read(readIntVQL(reader)).toString(); + case DataType.Buffer: return reader.read(readIntVQL(reader)).buffer; + case DataType.VSBuffer: return reader.read(readIntVQL(reader)); case DataType.Array: { - const length = readSizeBuffer(reader); + const length = readIntVQL(reader); const result: any[] = []; for (let i = 0; i < length; i++) { @@ -277,7 +316,8 @@ function deserialize(reader: IReader): any { return result; } - case DataType.Object: return JSON.parse(reader.read(readSizeBuffer(reader)).toString()); + case DataType.Object: return JSON.parse(reader.read(readIntVQL(reader)).toString()); + case DataType.Int: return readIntVQL(reader); } } @@ -516,7 +556,7 @@ export class ChannelClient implements IChannelClient, IDisposable { return { call(command: string, arg?: any, cancellationToken?: CancellationToken) { if (that.isDisposed) { - return Promise.reject(errors.canceled()); + return Promise.reject(new CancellationError()); } return that.requestPromise(channelName, command, arg, cancellationToken); }, @@ -535,14 +575,14 @@ export class ChannelClient implements IChannelClient, IDisposable { const request: IRawRequest = { id, type, channelName, name, arg }; if (cancellationToken.isCancellationRequested) { - return Promise.reject(errors.canceled()); + return Promise.reject(new CancellationError()); } let disposable: IDisposable; const result = new Promise((c, e) => { if (cancellationToken.isCancellationRequested) { - return e(errors.canceled()); + return e(new CancellationError()); } const doRequest = () => { @@ -556,7 +596,7 @@ export class ChannelClient implements IChannelClient, IDisposable { case ResponseType.PromiseError: { this.handlers.delete(id); const error = new Error(response.data.message); - (<any>error).stack = response.data.stack; + (<any>error).stack = Array.isArray(response.data.stack) ? response.data.stack.join('\n') : response.data.stack; error.name = response.data.name; e(error); break; @@ -591,7 +631,7 @@ export class ChannelClient implements IChannelClient, IDisposable { this.sendRequest({ id, type: RequestType.PromiseCancel }); } - e(errors.canceled()); + e(new CancellationError()); }; const cancellationTokenListener = cancellationToken.onCancellationRequested(cancel); @@ -610,7 +650,7 @@ export class ChannelClient implements IChannelClient, IDisposable { let uninitializedPromise: CancelablePromise<void> | null = null; const emitter = new Emitter<any>({ - onFirstListenerAdd: () => { + onWillAddFirstListener: () => { uninitializedPromise = createCancelablePromise(_ => this.whenInitialized()); uninitializedPromise.then(() => { uninitializedPromise = null; @@ -618,7 +658,7 @@ export class ChannelClient implements IChannelClient, IDisposable { this.sendRequest(request); }); }, - onLastListenerRemove: () => { + onDidRemoveLastListener: () => { if (uninitializedPromise) { uninitializedPromise.cancel(); uninitializedPromise = null; @@ -846,7 +886,7 @@ export class IPCServer<TContext = string> implements IChannelServer<TContext>, I // disconnects from all clients as soon as the last listener // is removed. const emitter = new Emitter<T>({ - onFirstListenerAdd: () => { + onWillAddFirstListener: () => { disposables = new DisposableStore(); // The event multiplexer is useful since the active @@ -881,7 +921,7 @@ export class IPCServer<TContext = string> implements IChannelServer<TContext>, I disposables.add(eventMultiplexer); }, - onLastListenerRemove: () => { + onDidRemoveLastListener: () => { disposables.dispose(); } }); @@ -909,7 +949,7 @@ export class IPCServer<TContext = string> implements IChannelServer<TContext>, I * An `IPCClient` is both a channel client and a channel server. * * As the owner of a protocol, you should extend both this - * and the `IPCClient` classes to get IPC implementations + * and the `IPCServer` classes to get IPC implementations * for your protocol. */ export class IPCClient<TContext = string> implements IChannelClient, IChannelServer<TContext>, IDisposable { @@ -1186,7 +1226,7 @@ function pretty(data: any): any { return prettyWithoutArrays(data); } -export function logWithColors(direction: string, totalLength: number, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void { +function logWithColors(direction: string, totalLength: number, msgLength: number, req: number, initiator: RequestInitiator, str: string, data: any): void { data = pretty(data); const colorTable = colorTables[initiator]; diff --git a/src/vs/base/parts/ipc/electron-browser/ipc.mp.ts b/src/vs/base/parts/ipc/electron-browser/ipc.mp.ts deleted file mode 100644 index 1b83fe4489..0000000000 --- a/src/vs/base/parts/ipc/electron-browser/ipc.mp.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -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'; - -/** - * An implementation of a `IPCServer` on top of MessagePort style IPC communication. - * The clients register themselves via Electron IPC transfer. - */ -export class Server extends IPCServer { - - private static getOnDidClientConnect(): Event<ClientConnectionEvent> { - - // Clients connect via `vscode:createMessageChannel` to get a - // `MessagePort` that is ready to be used. For every connection - // we create a pair of message ports and send it back. - // - // The `nonce` is included so that the main side has a chance to - // correlate the response back to the sender. - const onCreateMessageChannel = Event.fromNodeEventEmitter<string>(ipcRenderer, 'vscode:createMessageChannel', (_, nonce: string) => nonce); - - return Event.map(onCreateMessageChannel, nonce => { - - // Create a new pair of ports and protocol for this connection - const { port1: incomingPort, port2: outgoingPort } = new MessageChannel(); - const protocol = new MessagePortProtocol(incomingPort); - - const result: ClientConnectionEvent = { - protocol, - // Not part of the standard spec, but in Electron we get a `close` event - // when the other side closes. We can use this to detect disconnects - // (https://github.com/electron/electron/blob/11-x-y/docs/api/message-port-main.md#event-close) - onDidClientDisconnect: Event.fromDOMEventEmitter(incomingPort, 'close') - }; - - // Send one port back to the requestor - // Note: we intentionally use `electron` APIs here because - // transferables like the `MessagePort` cannot be transferred - // over preload scripts when `contextIsolation: true` - ipcRenderer.postMessage('vscode:createMessageChannelResult', nonce, [outgoingPort]); - - return result; - }); - } - - constructor() { - super(Server.getOnDidClientConnect()); - } -} diff --git a/src/vs/base/parts/ipc/electron-main/ipc.electron.ts b/src/vs/base/parts/ipc/electron-main/ipc.electron.ts index e35ddbbf0a..1386e02dc8 100644 --- a/src/vs/base/parts/ipc/electron-main/ipc.electron.ts +++ b/src/vs/base/parts/ipc/electron-main/ipc.electron.ts @@ -37,9 +37,7 @@ export class Server extends IPCServer { const id = webContents.id; const client = Server.Clients.get(id); - if (client) { - client.dispose(); - } + client?.dispose(); const onDidClientReconnect = new Emitter<void>(); Server.Clients.set(id, toDisposable(() => onDidClientReconnect.fire())); diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 11751a790c..ae33c6747e 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -151,14 +151,14 @@ export class Client implements IChannelClient, IDisposable { let listener: IDisposable; const emitter = new Emitter<any>({ - onFirstListenerAdd: () => { + onWillAddFirstListener: () => { const channel = this.getCachedChannel(channelName); const event: Event<T> = channel.listen(name, arg); listener = event(emitter.fire, emitter); this.activeRequests.add(listener); }, - onLastListenerRemove: () => { + onDidRemoveLastListener: () => { this.activeRequests.delete(listener); listener.dispose(); @@ -195,9 +195,9 @@ export class Client implements IChannelClient, IDisposable { } 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 + forkOpts.execArgv = process.execArgv // if not set, the forked process inherits the execArgv of the parent process + .filter(a => !/^--inspect(-brk)?=/.test(a)) // --inspect and --inspect-brk can not be inherited as the port would conflict + .filter(a => !a.startsWith('--vscode-')); // --vscode-* arguments are unsupported by node.js and thus need to remove } removeDangerousEnvVariables(forkOpts.env); @@ -241,9 +241,7 @@ export class Client implements IChannelClient, IDisposable { console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code + ' and signal ' + signal); } - if (this.disposeDelayer) { - this.disposeDelayer.cancel(); - } + this.disposeDelayer?.cancel(); this.disposeClient(); this._onDidProcessExit.fire({ code, signal }); }); @@ -276,10 +274,8 @@ export class Client implements IChannelClient, IDisposable { dispose() { this._onDidProcessExit.dispose(); - if (this.disposeDelayer) { - this.disposeDelayer.cancel(); - this.disposeDelayer = undefined; - } + this.disposeDelayer?.cancel(); + this.disposeDelayer = undefined; this.disposeClient(); this.activeRequests.clear(); } diff --git a/src/vs/base/parts/ipc/node/ipc.mp.ts b/src/vs/base/parts/ipc/node/ipc.mp.ts new file mode 100644 index 0000000000..bf799fc938 --- /dev/null +++ b/src/vs/base/parts/ipc/node/ipc.mp.ts @@ -0,0 +1,88 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MessagePortMain, isUtilityProcess, MessageEvent } from 'vs/base/parts/sandbox/node/electronTypes'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer } from 'vs/base/parts/ipc/common/ipc'; +import { Emitter, Event } from 'vs/base/common/event'; +import { assertType } from 'vs/base/common/types'; +import { firstOrDefault } from 'vs/base/common/arrays'; + +/** + * The MessagePort `Protocol` leverages MessagePortMain style IPC communication + * for the implementation of the `IMessagePassingProtocol`. + */ +class Protocol implements IMessagePassingProtocol { + + readonly onMessage = Event.fromNodeEventEmitter<VSBuffer>(this.port, 'message', (e: MessageEvent) => VSBuffer.wrap(e.data)); + + constructor(private port: MessagePortMain) { + + // we must call start() to ensure messages are flowing + port.start(); + } + + send(message: VSBuffer): void { + this.port.postMessage(message.buffer); + } + + disconnect(): void { + this.port.close(); + } +} + +/** + * An implementation of a `IPCServer` on top of MessagePort style IPC communication. + * The clients register themselves via Electron Utility Process IPC transfer. + */ +export class Server extends IPCServer { + + private static getOnDidClientConnect(): Event<ClientConnectionEvent> { + assertType(isUtilityProcess(process), 'Electron Utility Process'); + + const onCreateMessageChannel = new Emitter<MessagePortMain>(); + + process.parentPort.on('message', (e: Electron.MessageEvent) => { + const port = firstOrDefault(e.ports); + if (port) { + onCreateMessageChannel.fire(port); + } + }); + + return Event.map(onCreateMessageChannel.event, port => { + const protocol = new Protocol(port); + + const result: ClientConnectionEvent = { + protocol, + // Not part of the standard spec, but in Electron we get a `close` event + // when the other side closes. We can use this to detect disconnects + // (https://github.com/electron/electron/blob/11-x-y/docs/api/message-port-main.md#event-close) + onDidClientDisconnect: Event.fromNodeEventEmitter(port, 'close') + }; + + return result; + }); + } + + constructor() { + super(Server.getOnDidClientConnect()); + } +} + +interface INodeMessagePortFragment { + on(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + removeListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this; +} + +export function once(port: INodeMessagePortFragment, message: unknown, callback: () => void): void { + const listener = (e: MessageEvent) => { + if (e.data === message) { + port.removeListener('message', listener); + callback(); + } + }; + + port.on('message', listener); +} diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 33331ff7e7..1952c00a32 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// import { createHash } from 'crypto'; -import type { Server as NetServer, Socket } from 'net'; -// import { tmpdir } from 'os'; -import type * as zlib from 'zlib'; +import { createHash } from 'crypto'; +import { Server as NetServer, Socket, createServer, createConnection } from 'net'; +import { tmpdir } from 'os'; +import { createDeflateRaw, ZlibOptions, InflateRaw, DeflateRaw, createInflateRaw } from 'zlib'; import { VSBuffer } from 'vs/base/common/buffer'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -17,21 +17,14 @@ import { generateUuid } from 'vs/base/common/uuid'; import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc'; import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; -// TODO@bpasero remove me once electron utility process has landed -function getNodeDependencies() { - return { - crypto: (require.__$__nodeRequire('crypto') as any) as typeof import('crypto'), - zlib: (require.__$__nodeRequire('zlib') as any) as typeof import('zlib'), - net: (require.__$__nodeRequire('net') as any) as typeof import('net'), - os: (require.__$__nodeRequire('os') as any) as typeof import('os') - }; -} - export class NodeSocket implements ISocket { public readonly debugLabel: string; public readonly socket: Socket; private readonly _errorListener: (err: any) => void; + private readonly _closeListener: (hadError: boolean) => void; + private readonly _endListener: () => void; + private _canWrite = true; public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void { SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data); @@ -57,10 +50,24 @@ export class NodeSocket implements ISocket { } }; this.socket.on('error', this._errorListener); + + this._closeListener = (hadError: boolean) => { + this.traceSocketEvent(SocketDiagnosticsEventType.Close, { hadError }); + this._canWrite = false; + }; + this.socket.on('close', this._closeListener); + + this._endListener = () => { + this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndReceived); + this._canWrite = false; + }; + this.socket.on('end', this._endListener); } public dispose(): void { this.socket.off('error', this._errorListener); + this.socket.off('close', this._closeListener); + this.socket.off('end', this._endListener); this.socket.destroy(); } @@ -77,7 +84,6 @@ export class NodeSocket implements ISocket { public onClose(listener: (e: SocketCloseEvent) => void): IDisposable { const adapter = (hadError: boolean) => { - this.traceSocketEvent(SocketDiagnosticsEventType.Close, { hadError }); listener({ type: SocketCloseEventType.NodeSocketCloseEvent, hadError: hadError, @@ -92,7 +98,6 @@ export class NodeSocket implements ISocket { public onEnd(listener: () => void): IDisposable { const adapter = () => { - this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndReceived); listener(); }; this.socket.on('end', adapter); @@ -103,7 +108,7 @@ export class NodeSocket implements ISocket { public write(buffer: VSBuffer): void { // return early if socket has been destroyed in the meantime - if (this.socket.destroyed) { + if (this.socket.destroyed || !this._canWrite) { return; } @@ -174,7 +179,15 @@ export class NodeSocket implements ISocket { } const enum Constants { - MinHeaderByteSize = 2 + MinHeaderByteSize = 2, + /** + * If we need to write a large buffer, we will split it into 256KB chunks and + * send each chunk as a websocket message. This is to prevent that the sending + * side is stuck waiting for the entire buffer to be compressed before writing + * to the underlying socket or that the receiving side is stuck waiting for the + * entire message to be received before processing the bytes. + */ + MaxWebSocketMessageLength = 256 * 1024 // 256 KB } const enum ReadState { @@ -257,7 +270,14 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT })); this._incomingData = new ChunkStream(); this._register(this.socket.onData(data => this._acceptChunk(data))); - this._register(this.socket.onClose((e) => this._onClose.fire(e))); + this._register(this.socket.onClose(async (e) => { + // Delay surfacing the close event until the async inflating is done + // and all data has been emitted + if (this._flowManager.isProcessingReadQueue()) { + await Event.toPromise(this._flowManager.onDidFinishProcessingReadQueue); + } + this._onClose.fire(e); + })); } public override dispose(): void { @@ -285,7 +305,23 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT } public write(buffer: VSBuffer): void { - this._flowManager.writeMessage(buffer); + // If we write many logical messages (let's say 1000 messages of 100KB) during a single process tick, we do + // this thing where we install a process.nextTick timer and group all of them together and we then issue a + // single WebSocketNodeSocket.write with a 100MB buffer. + // + // The first problem is that the actual writing to the underlying node socket will only happen after all of + // the 100MB have been deflated (due to waiting on zlib flush). The second problem is on the reading side, + // where we will get a single WebSocketNodeSocket.onData event fired when all the 100MB have arrived, + // delaying processing the 1000 received messages until all have arrived, instead of processing them as each + // one arrives. + // + // We therefore split the buffer into chunks, and issue a write for each chunk. + + let start = 0; + while (start < buffer.byteLength) { + this._flowManager.writeMessage(buffer.slice(start, Math.min(start + Constants.MaxWebSocketMessageLength, buffer.byteLength))); + start += Constants.MaxWebSocketMessageLength; + } } private _write(buffer: VSBuffer, compressed: boolean): void { @@ -450,6 +486,9 @@ class WebSocketFlowManager extends Disposable { private readonly _writeQueue: VSBuffer[] = []; private readonly _readQueue: { data: VSBuffer; isCompressed: boolean; isLastFrameOfMessage: boolean }[] = []; + private readonly _onDidFinishProcessingReadQueue = this._register(new Emitter<void>()); + public readonly onDidFinishProcessingReadQueue = this._onDidFinishProcessingReadQueue.event; + private readonly _onDidFinishProcessingWriteQueue = this._register(new Emitter<void>()); public readonly onDidFinishProcessingWriteQueue = this._onDidFinishProcessingWriteQueue.event; @@ -550,6 +589,11 @@ class WebSocketFlowManager extends Disposable { } } this._isProcessingReadQueue = false; + this._onDidFinishProcessingReadQueue.fire(); + } + + public isProcessingReadQueue(): boolean { + return (this._isProcessingReadQueue); } /** @@ -572,7 +616,7 @@ class ZlibInflateStream extends Disposable { private readonly _onError = this._register(new Emitter<Error>()); public readonly onError = this._onError.event; - private readonly _zlibInflate: zlib.InflateRaw; + private readonly _zlibInflate: InflateRaw; private readonly _recordedInflateBytes: VSBuffer[] = []; private readonly _pendingInflateData: VSBuffer[] = []; @@ -587,10 +631,10 @@ class ZlibInflateStream extends Disposable { private readonly _tracer: ISocketTracer, private readonly _recordInflateBytes: boolean, inflateBytes: VSBuffer | null, - options: zlib.ZlibOptions + options: ZlibOptions ) { super(); - this._zlibInflate = getNodeDependencies().zlib.createInflateRaw(options); + this._zlibInflate = createInflateRaw(options); this._zlibInflate.on('error', (err) => { this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (<any>err)?.code }); this._onError.fire(err); @@ -632,16 +676,16 @@ class ZlibDeflateStream extends Disposable { private readonly _onError = this._register(new Emitter<Error>()); public readonly onError = this._onError.event; - private readonly _zlibDeflate: zlib.DeflateRaw; + private readonly _zlibDeflate: DeflateRaw; private readonly _pendingDeflateData: VSBuffer[] = []; constructor( private readonly _tracer: ISocketTracer, - options: zlib.ZlibOptions + options: ZlibOptions ) { super(); - this._zlibDeflate = getNodeDependencies().zlib.createDeflateRaw({ + this._zlibDeflate = createDeflateRaw({ windowBits: 15 }); this._zlibDeflate.on('error', (err) => { @@ -702,8 +746,7 @@ function unmask(buffer: VSBuffer, mask: number): void { // Read this before there's any chance it is overwritten // Related to https://github.com/microsoft/vscode/issues/30624 -// TODO@bpasero revert me once electron utility process has landed -export const XDG_RUNTIME_DIR = typeof process !== 'undefined' ? <string | undefined>process.env['XDG_RUNTIME_DIR'] : undefined; +export const XDG_RUNTIME_DIR = <string | undefined>process.env['XDG_RUNTIME_DIR']; const safeIpcPathLengths: { [platform: number]: number } = { [Platform.Linux]: 107, @@ -718,14 +761,10 @@ export function createRandomIPCHandle(): string { return `\\\\.\\pipe\\vscode-ipc-${randomSuffix}-sock`; } - // Mac/Unix: use socket file and prefer - // XDG_RUNTIME_DIR over tmpDir - let result: string; - if (XDG_RUNTIME_DIR) { - result = join(XDG_RUNTIME_DIR, `vscode-ipc-${randomSuffix}.sock`); - } else { - result = join(getNodeDependencies().os.tmpdir(), `vscode-ipc-${randomSuffix}.sock`); - } + // Mac & Unix: Use socket file + // Unix: Prefer XDG_RUNTIME_DIR over user data path + const basePath = process.platform !== 'darwin' && XDG_RUNTIME_DIR ? XDG_RUNTIME_DIR : tmpdir(); + const result = join(basePath, `vscode-ipc-${randomSuffix}.sock`); // Validate length validateIPCHandleLength(result); @@ -734,21 +773,27 @@ export function createRandomIPCHandle(): string { } export function createStaticIPCHandle(directoryPath: string, type: string, version: string): string { - const scope = getNodeDependencies().crypto.createHash('md5').update(directoryPath).digest('hex'); + const scope = createHash('md5').update(directoryPath).digest('hex'); // Windows: use named pipe if (process.platform === 'win32') { return `\\\\.\\pipe\\${scope}-${version}-${type}-sock`; } - // Mac/Unix: use socket file and prefer - // XDG_RUNTIME_DIR over user data path - // unless portable + // Mac & Unix: Use socket file + // Unix: Prefer XDG_RUNTIME_DIR over user data path, unless portable + // Trim the version and type values for the socket to prevent too large + // file names causing issues: https://unix.stackexchange.com/q/367008 + + const versionForSocket = version.substr(0, 4); + const typeForSocket = type.substr(0, 6); + const scopeForSocket = scope.substr(0, 8); + let result: string; - if (XDG_RUNTIME_DIR && !process.env['VSCODE_PORTABLE']) { - result = join(XDG_RUNTIME_DIR, `vscode-${scope.substr(0, 8)}-${version}-${type}.sock`); + if (process.platform !== 'darwin' && XDG_RUNTIME_DIR && !process.env['VSCODE_PORTABLE']) { + result = join(XDG_RUNTIME_DIR, `vscode-${scopeForSocket}-${versionForSocket}-${typeForSocket}.sock`); } else { - result = join(directoryPath, `${version}-${type}.sock`); + result = join(directoryPath, `${versionForSocket}-${typeForSocket}.sock`); } // Validate length @@ -796,7 +841,7 @@ export function serve(port: number): Promise<Server>; export function serve(namedPipe: string): Promise<Server>; export function serve(hook: any): Promise<Server> { return new Promise<Server>((c, e) => { - const server = getNodeDependencies().net.createServer(); + const server = createServer(); server.on('error', e); server.listen(hook, () => { @@ -811,7 +856,7 @@ export function connect(port: number, clientId: string): Promise<Client>; export function connect(namedPipe: string, clientId: string): Promise<Client>; export function connect(hook: any, clientId: string): Promise<Client> { return new Promise<Client>((c, e) => { - const socket = getNodeDependencies().net.createConnection(hook, () => { + const socket = createConnection(hook, () => { socket.removeListener('error', e); c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId)); }); 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 c326bdf43f..b3a7d9af15 100644 --- a/src/vs/base/parts/ipc/test/common/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/common/ipc.test.ts @@ -11,7 +11,7 @@ import { canceled } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { ClientConnectionEvent, IChannel, IMessagePassingProtocol, IPCClient, IPCServer, IServerChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { BufferReader, BufferWriter, ClientConnectionEvent, deserialize, IChannel, IMessagePassingProtocol, IPCClient, IPCServer, IServerChannel, ProxyChannel, serialize } from 'vs/base/parts/ipc/common/ipc'; class QueueProtocol implements IMessagePassingProtocol { @@ -19,7 +19,7 @@ class QueueProtocol implements IMessagePassingProtocol { private buffers: VSBuffer[] = []; private readonly _onMessage = new Emitter<VSBuffer>({ - onFirstListenerDidAdd: () => { + onDidAddFirstListener: () => { for (const buffer of this.buffers) { this._onMessage.fire(buffer); } @@ -27,7 +27,7 @@ class QueueProtocol implements IMessagePassingProtocol { this.buffers = []; this.buffering = false; }, - onLastListenerRemove: () => { + onDidRemoveLastListener: () => { this.buffering = true; } }); @@ -319,6 +319,22 @@ suite('Base IPC', function () { const r = await ipcService.buffersLength([VSBuffer.alloc(2), VSBuffer.alloc(3)]); return assert.strictEqual(r, 5); }); + + test('round trips numbers', () => { + const input = [ + 0, + 1, + -1, + 12345, + -12345, + 42.6, + 123412341234 + ]; + + const writer = new BufferWriter(); + serialize(writer, input); + assert.deepStrictEqual(deserialize(new BufferReader(writer.buffer)), input); + }); }); suite('one to one (proxy)', function () { diff --git a/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts b/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts index 4edd3b23b4..077649e2de 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts @@ -4,75 +4,68 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { Event } from 'vs/base/common/event'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; -import { TestServiceClient } from './testService'; +import { ITestService, TestServiceClient } from './testService'; +import { FileAccess } from 'vs/base/common/network'; function createClient(): Client { - return new Client(getPathFromAmdModule(require, 'bootstrap-fork'), { + return new Client(FileAccess.asFileUri('bootstrap-fork').fsPath, { serverName: 'TestServer', env: { VSCODE_AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true } }); } -suite('IPC, Child Process', () => { - test('createChannel', () => { - const client = createClient(); - const channel = client.getChannel('test'); - const service = new TestServiceClient(channel); +suite('IPC, Child Process', function () { + this.slow(2000); + this.timeout(10000); - const result = service.pong('ping').then(r => { - assert.strictEqual(r.incoming, 'ping'); - assert.strictEqual(r.outgoing, 'pong'); - }); + let client: Client; + let channel: IChannel; + let service: ITestService; - return result.finally(() => client.dispose()); + setup(() => { + client = createClient(); + channel = client.getChannel('test'); + service = new TestServiceClient(channel); }); - test('events', () => { - const client = createClient(); - const channel = client.getChannel('test'); - const service = new TestServiceClient(channel); - - const event = new Promise((c, e) => { - service.onMarco(({ answer }) => { - try { - assert.strictEqual(answer, 'polo'); - c(undefined); - } catch (err) { - e(err); - } - }); - }); - - const request = service.marco(); - const result = Promise.all([request, event]); - - return result.finally(() => client.dispose()); + teardown(() => { + client.dispose(); }); - test('event dispose', () => { - const client = createClient(); - const channel = client.getChannel('test'); - const service = new TestServiceClient(channel); + test('createChannel', async () => { + const result = await service.pong('ping'); + assert.strictEqual(result.incoming, 'ping'); + assert.strictEqual(result.outgoing, 'pong'); + }); + test('events', async () => { + const event = Event.toPromise(Event.once(service.onMarco)); + const promise = service.marco(); + + const [promiseResult, eventResult] = await Promise.all([promise, event]); + + assert.strictEqual(promiseResult, 'polo'); + assert.strictEqual(eventResult.answer, 'polo'); + }); + + test('event dispose', async () => { let count = 0; const disposable = service.onMarco(() => count++); - const result = service.marco().then(async answer => { - assert.strictEqual(answer, 'polo'); - assert.strictEqual(count, 1); + const answer = await service.marco(); + assert.strictEqual(answer, 'polo'); + assert.strictEqual(count, 1); - const answer_1 = await service.marco(); - assert.strictEqual(answer_1, 'polo'); - assert.strictEqual(count, 2); - disposable.dispose(); + const answer_1 = await service.marco(); + assert.strictEqual(answer_1, 'polo'); + assert.strictEqual(count, 2); + disposable.dispose(); - const answer_2 = await service.marco(); - assert.strictEqual(answer_2, 'polo'); - assert.strictEqual(count, 2); - }); - - return result.finally(() => client.dispose()); + const answer_2 = await service.marco(); + assert.strictEqual(answer_2, 'polo'); + assert.strictEqual(count, 2); }); }); diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 9baf16d712..a7ad1dd00e 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { EventEmitter } from 'events'; -import { createServer, Socket } from 'net'; +import { AddressInfo, connect, createServer, Server, Socket } from 'net'; import { tmpdir } from 'os'; import { Barrier, timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -194,9 +194,9 @@ suite('PersistentProtocol reconnection', () => { test('acks get piggybacked with messages', async () => { const ether = new Ether(); - const a = new PersistentProtocol(new NodeSocket(ether.a)); + const a = new PersistentProtocol({ socket: new NodeSocket(ether.a) }); const aMessages = new MessageStream(a); - const b = new PersistentProtocol(new NodeSocket(ether.b)); + const b = new PersistentProtocol({ socket: new NodeSocket(ether.b) }); const bMessages = new MessageStream(b); a.send(VSBuffer.fromString('a1')); @@ -257,10 +257,10 @@ suite('PersistentProtocol reconnection', () => { }; const ether = new Ether(); const aSocket = new NodeSocket(ether.a); - const a = new PersistentProtocol(aSocket, null, loadEstimator); + const a = new PersistentProtocol({ socket: aSocket, loadEstimator }); const aMessages = new MessageStream(a); const bSocket = new NodeSocket(ether.b); - const b = new PersistentProtocol(bSocket, null, loadEstimator); + const b = new PersistentProtocol({ socket: bSocket, loadEstimator }); const bMessages = new MessageStream(b); // send one message A -> B @@ -302,10 +302,10 @@ suite('PersistentProtocol reconnection', () => { }; const ether = new Ether(); const aSocket = new NodeSocket(ether.a); - const a = new PersistentProtocol(aSocket, null, loadEstimator); + const a = new PersistentProtocol({ socket: aSocket, loadEstimator, sendKeepAlive: false }); const aMessages = new MessageStream(a); const bSocket = new NodeSocket(ether.b); - const b = new PersistentProtocol(bSocket, null, loadEstimator); + const b = new PersistentProtocol({ socket: bSocket, loadEstimator, sendKeepAlive: false }); const bMessages = new MessageStream(b); // send message a1 before reconnection to get _recvAckCheck() scheduled @@ -384,10 +384,10 @@ suite('PersistentProtocol reconnection', () => { const wireLatency = 1000; const ether = new Ether(wireLatency); const aSocket = new NodeSocket(ether.a); - const a = new PersistentProtocol(aSocket, null, loadEstimator); + const a = new PersistentProtocol({ socket: aSocket, loadEstimator }); const aMessages = new MessageStream(a); const bSocket = new NodeSocket(ether.b); - const b = new PersistentProtocol(bSocket, null, loadEstimator); + const b = new PersistentProtocol({ socket: bSocket, loadEstimator }); const bMessages = new MessageStream(b); // send message a1 to have something unacknowledged @@ -445,10 +445,10 @@ suite('PersistentProtocol reconnection', () => { }; const ether = new Ether(); const aSocket = new NodeSocket(ether.a); - const a = new PersistentProtocol(aSocket, null, loadEstimator); + const a = new PersistentProtocol({ socket: aSocket, loadEstimator }); const aMessages = new MessageStream(a); const bSocket = new NodeSocket(ether.b); - const b = new PersistentProtocol(bSocket, null, loadEstimator); + const b = new PersistentProtocol({ socket: bSocket, loadEstimator }); const bMessages = new MessageStream(b); // never receive acks @@ -490,10 +490,10 @@ suite('PersistentProtocol reconnection', () => { }; const ether = new Ether(); const aSocket = new NodeSocket(ether.a); - const a = new PersistentProtocol(aSocket, null, loadEstimator); + const a = new PersistentProtocol({ socket: aSocket, loadEstimator }); const aMessages = new MessageStream(a); const bSocket = new NodeSocket(ether.b); - const b = new PersistentProtocol(bSocket, null, loadEstimator); + const b = new PersistentProtocol({ socket: bSocket, loadEstimator }); const bMessages = new MessageStream(b); // send one message A -> B @@ -706,4 +706,63 @@ suite('WebSocketNodeSocket', () => { assert.deepStrictEqual(actual, 'Helloworld'); }); }); + + test('Large buffers are split and sent in chunks', async () => { + + let receivingSideOnDataCallCount = 0; + let receivingSideTotalBytes = 0; + const receivingSideSocketClosedBarrier = new Barrier(); + + const server = await listenOnRandomPort((socket) => { + // stop the server when the first connection is received + server.close(); + + const webSocketNodeSocket = new WebSocketNodeSocket(new NodeSocket(socket), true, null, false); + webSocketNodeSocket.onData((data) => { + receivingSideOnDataCallCount++; + receivingSideTotalBytes += data.byteLength; + }); + + webSocketNodeSocket.onClose(() => { + webSocketNodeSocket.dispose(); + receivingSideSocketClosedBarrier.open(); + }); + }); + + const socket = connect({ + host: '127.0.0.1', + port: (<AddressInfo>server.address()).port + }); + + const buff = generateRandomBuffer(1 * 1024 * 1024); + + const webSocketNodeSocket = new WebSocketNodeSocket(new NodeSocket(socket), true, null, false); + webSocketNodeSocket.write(buff); + await webSocketNodeSocket.drain(); + webSocketNodeSocket.dispose(); + await receivingSideSocketClosedBarrier.wait(); + + assert.strictEqual(receivingSideTotalBytes, buff.byteLength); + assert.strictEqual(receivingSideOnDataCallCount, 4); + }); + + function generateRandomBuffer(size: number): VSBuffer { + const buff = VSBuffer.alloc(size); + for (let i = 0; i < size; i++) { + buff.writeUInt8(Math.floor(256 * Math.random()), i); + } + return buff; + } + + function listenOnRandomPort(handler: (socket: Socket) => void): Promise<Server> { + return new Promise((resolve, reject) => { + const server = createServer(handler).listen(0); + server.on('listening', () => { + resolve(server); + }); + server.on('error', (err) => { + reject(err); + }); + }); + } }); diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts deleted file mode 100644 index 8ca3721f8f..0000000000 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ /dev/null @@ -1,1860 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as dom from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; -import { Button, IButtonStyles } from 'vs/base/browser/ui/button/button'; -import { CountBadge, ICountBadgetyles } from 'vs/base/browser/ui/countBadge/countBadge'; -import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox'; -import { IKeybindingLabelStyles } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; -import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IListOptions, IListStyles, List } from 'vs/base/browser/ui/list/listWidget'; -import { IProgressBarStyles, ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { Action } from 'vs/base/common/actions'; -import { equals } from 'vs/base/common/arrays'; -import { TimeoutTimer } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Codicon } from 'vs/base/common/codicons'; -import { Color } from 'vs/base/common/color'; -import { Emitter, Event } from 'vs/base/common/event'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; -import { isIOS } from 'vs/base/common/platform'; -import Severity from 'vs/base/common/severity'; -import { isString, withNullAsUndefined } from 'vs/base/common/types'; -import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils'; -import { IInputBox, IInputOptions, IKeyMods, IPickOptions, IQuickInput, IQuickInputButton, IQuickInputHideEvent, IQuickNavigateConfiguration, IQuickPick, IQuickPickDidAcceptEvent, IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator, IQuickPickWillAcceptEvent, ItemActivation, NO_KEY_MODS, QuickInputHideReason, QuickPickInput } from 'vs/base/parts/quickinput/common/quickInput'; -import 'vs/css!./media/quickInput'; -import { localize } from 'vs/nls'; -import { QuickInputBox } from './quickInputBox'; -import { QuickInputList, QuickInputListFocus } from './quickInputList'; - -export interface IQuickInputOptions { - idPrefix: string; - container: HTMLElement; - ignoreFocusOut(): boolean; - isScreenReaderOptimized(): boolean; - backKeybindingLabel(): string | undefined; - setContextKey(id?: string): void; - returnFocus(): void; - createList<T>( - user: string, - container: HTMLElement, - delegate: IListVirtualDelegate<T>, - renderers: IListRenderer<T, any>[], - options: IListOptions<T>, - ): List<T>; - styles: IQuickInputStyles; -} - -export interface IQuickInputStyles { - widget: IQuickInputWidgetStyles; - inputBox: IInputBoxStyles; - countBadge: ICountBadgetyles; - button: IButtonStyles; - progressBar: IProgressBarStyles; - keybindingLabel: IKeybindingLabelStyles; - list: IListStyles & { pickerGroupBorder?: Color; pickerGroupForeground?: Color }; -} - -export interface IQuickInputWidgetStyles { - quickInputBackground?: Color; - quickInputForeground?: Color; - quickInputTitleBackground?: Color; - contrastBorder?: Color; - widgetShadow?: Color; -} - -const $ = dom.$; - -type Writeable<T> = { -readonly [P in keyof T]: T[P] }; - -const backButton = { - iconClass: Codicon.quickInputBack.classNames, - tooltip: localize('quickInput.back', "Back"), - handle: -1 // TODO -}; - -interface QuickInputUI { - container: HTMLElement; - styleSheet: HTMLStyleElement; - leftActionBar: ActionBar; - titleBar: HTMLElement; - title: HTMLElement; - description1: HTMLElement; - description2: HTMLElement; - rightActionBar: ActionBar; - checkAll: HTMLInputElement; - filterContainer: HTMLElement; - inputBox: QuickInputBox; - visibleCountContainer: HTMLElement; - visibleCount: CountBadge; - countContainer: HTMLElement; - count: CountBadge; - okContainer: HTMLElement; - ok: Button; - message: HTMLElement; - customButtonContainer: HTMLElement; - customButton: Button; - progressBar: ProgressBar; - list: QuickInputList; - onDidAccept: Event<void>; - onDidCustom: Event<void>; - onDidTriggerButton: Event<IQuickInputButton>; - ignoreFocusOut: boolean; - keyMods: Writeable<IKeyMods>; - isScreenReaderOptimized(): boolean; - show(controller: QuickInput): void; - setVisibilities(visibilities: Visibilities): void; - setComboboxAccessibility(enabled: boolean): void; - setEnabled(enabled: boolean): void; - setContextKey(contextKey?: string): void; - hide(): void; -} - -type Visibilities = { - title?: boolean; - description?: boolean; - checkAll?: boolean; - inputBox?: boolean; - checkBox?: boolean; - visibleCount?: boolean; - count?: boolean; - message?: boolean; - list?: boolean; - ok?: boolean; - customButton?: boolean; - progressBar?: boolean; -}; - -class QuickInput extends Disposable implements IQuickInput { - protected static readonly noPromptMessage = localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel"); - - private _title: string | undefined; - private _description: string | undefined; - private _steps: number | undefined; - private _totalSteps: number | undefined; - protected visible = false; - private _enabled = true; - private _contextKey: string | undefined; - private _busy = false; - private _ignoreFocusOut = false; - private _buttons: IQuickInputButton[] = []; - protected noValidationMessage = QuickInput.noPromptMessage; - private _validationMessage: string | undefined; - private _lastValidationMessage: string | undefined; - private _severity: Severity = Severity.Ignore; - private _lastSeverity: Severity | undefined; - private buttonsUpdated = false; - private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>()); - private readonly onDidHideEmitter = this._register(new Emitter<IQuickInputHideEvent>()); - private readonly onDisposeEmitter = this._register(new Emitter<void>()); - - protected readonly visibleDisposables = this._register(new DisposableStore()); - - private busyDelay: TimeoutTimer | undefined; - - constructor( - protected ui: QuickInputUI - ) { - super(); - } - - get title() { - return this._title; - } - - set title(title: string | undefined) { - this._title = title; - this.update(); - } - - get description() { - return this._description; - } - - set description(description: string | undefined) { - this._description = description; - this.update(); - } - - get step() { - return this._steps; - } - - set step(step: number | undefined) { - this._steps = step; - this.update(); - } - - get totalSteps() { - return this._totalSteps; - } - - set totalSteps(totalSteps: number | undefined) { - this._totalSteps = totalSteps; - this.update(); - } - - get enabled() { - return this._enabled; - } - - set enabled(enabled: boolean) { - this._enabled = enabled; - this.update(); - } - - get contextKey() { - return this._contextKey; - } - - set contextKey(contextKey: string | undefined) { - this._contextKey = contextKey; - this.update(); - } - - get busy() { - return this._busy; - } - - set busy(busy: boolean) { - this._busy = busy; - this.update(); - } - - get ignoreFocusOut() { - return this._ignoreFocusOut; - } - - set ignoreFocusOut(ignoreFocusOut: boolean) { - const shouldUpdate = this._ignoreFocusOut !== ignoreFocusOut && !isIOS; - this._ignoreFocusOut = ignoreFocusOut && !isIOS; - if (shouldUpdate) { - this.update(); - } - } - - get buttons() { - return this._buttons; - } - - set buttons(buttons: IQuickInputButton[]) { - this._buttons = buttons; - this.buttonsUpdated = true; - this.update(); - } - - get validationMessage() { - return this._validationMessage; - } - - set validationMessage(validationMessage: string | undefined) { - this._validationMessage = validationMessage; - this.update(); - } - - get severity() { - return this._severity; - } - - set severity(severity: Severity) { - this._severity = severity; - this.update(); - } - - readonly onDidTriggerButton = this.onDidTriggerButtonEmitter.event; - - show(): void { - if (this.visible) { - return; - } - this.visibleDisposables.add( - this.ui.onDidTriggerButton(button => { - if (this.buttons.indexOf(button) !== -1) { - this.onDidTriggerButtonEmitter.fire(button); - } - }), - ); - this.ui.show(this); - - // update properties in the controller that get reset in the ui.show() call - this.visible = true; - // This ensures the message/prompt gets rendered - this._lastValidationMessage = undefined; - // This ensures the input box has the right severity applied - this._lastSeverity = undefined; - if (this.buttons.length) { - // if there are buttons, the ui.show() clears them out of the UI so we should - // rerender them. - this.buttonsUpdated = true; - } - - this.update(); - } - - hide(): void { - if (!this.visible) { - return; - } - this.ui.hide(); - } - - didHide(reason = QuickInputHideReason.Other): void { - this.visible = false; - this.visibleDisposables.clear(); - this.onDidHideEmitter.fire({ reason }); - } - - readonly onDidHide = this.onDidHideEmitter.event; - - protected update() { - if (!this.visible) { - return; - } - const title = this.getTitle(); - if (title && this.ui.title.textContent !== title) { - this.ui.title.textContent = title; - } else if (!title && this.ui.title.innerHTML !== ' ') { - this.ui.title.innerText = '\u00a0'; - } - const description = this.getDescription(); - if (this.ui.description1.textContent !== description) { - this.ui.description1.textContent = description; - } - if (this.ui.description2.textContent !== description) { - this.ui.description2.textContent = description; - } - if (this.busy && !this.busyDelay) { - this.busyDelay = new TimeoutTimer(); - this.busyDelay.setIfNotSet(() => { - if (this.visible) { - this.ui.progressBar.infinite(); - } - }, 800); - } - if (!this.busy && this.busyDelay) { - this.ui.progressBar.stop(); - this.busyDelay.cancel(); - this.busyDelay = undefined; - } - if (this.buttonsUpdated) { - this.buttonsUpdated = false; - this.ui.leftActionBar.clear(); - const leftButtons = this.buttons.filter(button => button === backButton); - this.ui.leftActionBar.push(leftButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => { - this.onDidTriggerButtonEmitter.fire(button); - }); - action.tooltip = button.tooltip || ''; - return action; - }), { icon: true, label: false }); - this.ui.rightActionBar.clear(); - const rightButtons = this.buttons.filter(button => button !== backButton); - this.ui.rightActionBar.push(rightButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, async () => { - this.onDidTriggerButtonEmitter.fire(button); - }); - action.tooltip = button.tooltip || ''; - return action; - }), { icon: true, label: false }); - } - this.ui.ignoreFocusOut = this.ignoreFocusOut; - this.ui.setEnabled(this.enabled); - this.ui.setContextKey(this.contextKey); - - const validationMessage = this.validationMessage || this.noValidationMessage; - if (this._lastValidationMessage !== validationMessage) { - this._lastValidationMessage = validationMessage; - dom.reset(this.ui.message, ...renderLabelWithIcons(validationMessage)); - } - if (this._lastSeverity !== this.severity) { - this._lastSeverity = this.severity; - this.showMessageDecoration(this.severity); - } - } - - private getTitle() { - if (this.title && this.step) { - return `${this.title} (${this.getSteps()})`; - } - if (this.title) { - return this.title; - } - if (this.step) { - return this.getSteps(); - } - return ''; - } - - private getDescription() { - return this.description || ''; - } - - private getSteps() { - if (this.step && this.totalSteps) { - return localize('quickInput.steps', "{0}/{1}", this.step, this.totalSteps); - } - if (this.step) { - return String(this.step); - } - return ''; - } - - protected showMessageDecoration(severity: Severity) { - this.ui.inputBox.showDecoration(severity); - if (severity !== Severity.Ignore) { - const styles = this.ui.inputBox.stylesForType(severity); - this.ui.message.style.color = styles.foreground ? `${styles.foreground}` : ''; - this.ui.message.style.backgroundColor = styles.background ? `${styles.background}` : ''; - this.ui.message.style.border = styles.border ? `1px solid ${styles.border}` : ''; - this.ui.message.style.marginBottom = '-2px'; - } else { - this.ui.message.style.color = ''; - this.ui.message.style.backgroundColor = ''; - this.ui.message.style.border = ''; - this.ui.message.style.marginBottom = ''; - } - } - - readonly onDispose = this.onDisposeEmitter.event; - - override dispose(): void { - this.hide(); - this.onDisposeEmitter.fire(); - - super.dispose(); - } -} - -class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPick<T> { - - private static readonly DEFAULT_ARIA_LABEL = localize('quickInputBox.ariaLabel', "Type to narrow down results."); - - private _value = ''; - private _ariaLabel: string | undefined; - private _placeholder: string | undefined; - private readonly onDidChangeValueEmitter = this._register(new Emitter<string>()); - private readonly onWillAcceptEmitter = this._register(new Emitter<IQuickPickWillAcceptEvent>()); - private readonly onDidAcceptEmitter = this._register(new Emitter<IQuickPickDidAcceptEvent>()); - private readonly onDidCustomEmitter = this._register(new Emitter<void>()); - private _items: Array<T | IQuickPickSeparator> = []; - private itemsUpdated = false; - private _canSelectMany = false; - private _canAcceptInBackground = false; - private _matchOnDescription = false; - private _matchOnDetail = false; - private _matchOnLabel = true; - private _matchOnLabelMode: 'fuzzy' | 'contiguous' = 'fuzzy'; - private _sortByLabel = true; - private _autoFocusOnList = true; - private _keepScrollPosition = false; - private _itemActivation = this.ui.isScreenReaderOptimized() ? ItemActivation.NONE /* https://github.com/microsoft/vscode/issues/57501 */ : ItemActivation.FIRST; - private _activeItems: T[] = []; - private activeItemsUpdated = false; - private activeItemsToConfirm: T[] | null = []; - private readonly onDidChangeActiveEmitter = this._register(new Emitter<T[]>()); - private _selectedItems: T[] = []; - private selectedItemsUpdated = false; - private selectedItemsToConfirm: T[] | null = []; - private readonly onDidChangeSelectionEmitter = this._register(new Emitter<T[]>()); - private readonly onDidTriggerItemButtonEmitter = this._register(new Emitter<IQuickPickItemButtonEvent<T>>()); - private _valueSelection: Readonly<[number, number]> | undefined; - private valueSelectionUpdated = true; - private _ok: boolean | 'default' = 'default'; - private _customButton = false; - private _customButtonLabel: string | undefined; - private _customButtonHover: string | undefined; - private _quickNavigate: IQuickNavigateConfiguration | undefined; - private _hideInput: boolean | undefined; - private _hideCheckAll: boolean | undefined; - - get quickNavigate() { - return this._quickNavigate; - } - - set quickNavigate(quickNavigate: IQuickNavigateConfiguration | undefined) { - this._quickNavigate = quickNavigate; - this.update(); - } - - get value() { - return this._value; - } - - set value(value: string) { - this.doSetValue(value); - } - - private doSetValue(value: string, skipUpdate?: boolean): void { - if (this._value !== value) { - this._value = value; - if (!skipUpdate) { - this.update(); - } - if (this.visible) { - const didFilter = this.ui.list.filter(this.filterValue(this._value)); - if (didFilter) { - this.trySelectFirst(); - } - } - this.onDidChangeValueEmitter.fire(this._value); - } - } - - filterValue = (value: string) => value; - - set ariaLabel(ariaLabel: string | undefined) { - this._ariaLabel = ariaLabel; - this.update(); - } - - get ariaLabel() { - return this._ariaLabel; - } - - get placeholder() { - return this._placeholder; - } - - set placeholder(placeholder: string | undefined) { - this._placeholder = placeholder; - this.update(); - } - - onDidChangeValue = this.onDidChangeValueEmitter.event; - - onWillAccept = this.onWillAcceptEmitter.event; - onDidAccept = this.onDidAcceptEmitter.event; - - onDidCustom = this.onDidCustomEmitter.event; - - get items() { - return this._items; - } - - private get scrollTop() { - return this.ui.list.scrollTop; - } - - private set scrollTop(scrollTop: number) { - this.ui.list.scrollTop = scrollTop; - } - - set items(items: Array<T | IQuickPickSeparator>) { - this._items = items; - this.itemsUpdated = true; - this.update(); - } - - get canSelectMany() { - return this._canSelectMany; - } - - set canSelectMany(canSelectMany: boolean) { - this._canSelectMany = canSelectMany; - this.update(); - } - - get canAcceptInBackground() { - return this._canAcceptInBackground; - } - - set canAcceptInBackground(canAcceptInBackground: boolean) { - this._canAcceptInBackground = canAcceptInBackground; - } - - get matchOnDescription() { - return this._matchOnDescription; - } - - set matchOnDescription(matchOnDescription: boolean) { - this._matchOnDescription = matchOnDescription; - this.update(); - } - - get matchOnDetail() { - return this._matchOnDetail; - } - - set matchOnDetail(matchOnDetail: boolean) { - this._matchOnDetail = matchOnDetail; - this.update(); - } - - get matchOnLabel() { - return this._matchOnLabel; - } - - set matchOnLabel(matchOnLabel: boolean) { - this._matchOnLabel = matchOnLabel; - this.update(); - } - - get matchOnLabelMode() { - return this._matchOnLabelMode; - } - - set matchOnLabelMode(matchOnLabelMode: 'fuzzy' | 'contiguous') { - this._matchOnLabelMode = matchOnLabelMode; - this.update(); - } - - get sortByLabel() { - return this._sortByLabel; - } - - set sortByLabel(sortByLabel: boolean) { - this._sortByLabel = sortByLabel; - this.update(); - } - - get autoFocusOnList() { - return this._autoFocusOnList; - } - - set autoFocusOnList(autoFocusOnList: boolean) { - this._autoFocusOnList = autoFocusOnList; - this.update(); - } - - get keepScrollPosition() { - return this._keepScrollPosition; - } - - set keepScrollPosition(keepScrollPosition: boolean) { - this._keepScrollPosition = keepScrollPosition; - } - - get itemActivation() { - return this._itemActivation; - } - - set itemActivation(itemActivation: ItemActivation) { - this._itemActivation = itemActivation; - } - - get activeItems() { - return this._activeItems; - } - - set activeItems(activeItems: T[]) { - this._activeItems = activeItems; - this.activeItemsUpdated = true; - this.update(); - } - - onDidChangeActive = this.onDidChangeActiveEmitter.event; - - get selectedItems() { - return this._selectedItems; - } - - set selectedItems(selectedItems: T[]) { - this._selectedItems = selectedItems; - this.selectedItemsUpdated = true; - this.update(); - } - - get keyMods() { - if (this._quickNavigate) { - // Disable keyMods when quick navigate is enabled - // because in this model the interaction is purely - // keyboard driven and Ctrl/Alt are typically - // pressed and hold during this interaction. - return NO_KEY_MODS; - } - return this.ui.keyMods; - } - - set valueSelection(valueSelection: Readonly<[number, number]>) { - this._valueSelection = valueSelection; - this.valueSelectionUpdated = true; - this.update(); - } - - get customButton() { - return this._customButton; - } - - set customButton(showCustomButton: boolean) { - this._customButton = showCustomButton; - this.update(); - } - - get customLabel() { - return this._customButtonLabel; - } - - set customLabel(label: string | undefined) { - this._customButtonLabel = label; - this.update(); - } - - get customHover() { - return this._customButtonHover; - } - - set customHover(hover: string | undefined) { - this._customButtonHover = hover; - this.update(); - } - - get ok() { - return this._ok; - } - - set ok(showOkButton: boolean | 'default') { - this._ok = showOkButton; - this.update(); - } - - inputHasFocus(): boolean { - return this.visible ? this.ui.inputBox.hasFocus() : false; - } - - focusOnInput() { - this.ui.inputBox.setFocus(); - } - - get hideInput() { - return !!this._hideInput; - } - - set hideInput(hideInput: boolean) { - this._hideInput = hideInput; - this.update(); - } - - get hideCheckAll() { - return !!this._hideCheckAll; - } - - set hideCheckAll(hideCheckAll: boolean) { - this._hideCheckAll = hideCheckAll; - this.update(); - } - - onDidChangeSelection = this.onDidChangeSelectionEmitter.event; - - onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event; - - private trySelectFirst() { - if (this.autoFocusOnList) { - if (!this.canSelectMany) { - this.ui.list.focus(QuickInputListFocus.First); - } - } - } - - override show() { - if (!this.visible) { - this.visibleDisposables.add( - this.ui.inputBox.onDidChange(value => { - this.doSetValue(value, true /* skip update since this originates from the UI */); - })); - this.visibleDisposables.add(this.ui.inputBox.onMouseDown(event => { - if (!this.autoFocusOnList) { - this.ui.list.clearFocus(); - } - })); - this.visibleDisposables.add((this._hideInput ? this.ui.list : this.ui.inputBox).onKeyDown((event: KeyboardEvent | StandardKeyboardEvent) => { - switch (event.keyCode) { - case KeyCode.DownArrow: - this.ui.list.focus(QuickInputListFocus.Next); - if (this.canSelectMany) { - this.ui.list.domFocus(); - } - dom.EventHelper.stop(event, true); - break; - case KeyCode.UpArrow: - if (this.ui.list.getFocusedElements().length) { - this.ui.list.focus(QuickInputListFocus.Previous); - } else { - this.ui.list.focus(QuickInputListFocus.Last); - } - if (this.canSelectMany) { - this.ui.list.domFocus(); - } - dom.EventHelper.stop(event, true); - break; - case KeyCode.PageDown: - this.ui.list.focus(QuickInputListFocus.NextPage); - if (this.canSelectMany) { - this.ui.list.domFocus(); - } - dom.EventHelper.stop(event, true); - break; - case KeyCode.PageUp: - this.ui.list.focus(QuickInputListFocus.PreviousPage); - if (this.canSelectMany) { - this.ui.list.domFocus(); - } - dom.EventHelper.stop(event, true); - break; - case KeyCode.RightArrow: - if (!this._canAcceptInBackground) { - return; // needs to be enabled - } - - if (!this.ui.inputBox.isSelectionAtEnd()) { - return; // ensure input box selection at end - } - - if (this.activeItems[0]) { - this._selectedItems = [this.activeItems[0]]; - this.onDidChangeSelectionEmitter.fire(this.selectedItems); - this.handleAccept(true); - } - - break; - case KeyCode.Home: - if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) { - this.ui.list.focus(QuickInputListFocus.First); - dom.EventHelper.stop(event, true); - } - break; - case KeyCode.End: - if ((event.ctrlKey || event.metaKey) && !event.shiftKey && !event.altKey) { - this.ui.list.focus(QuickInputListFocus.Last); - dom.EventHelper.stop(event, true); - } - break; - } - })); - this.visibleDisposables.add(this.ui.onDidAccept(() => { - if (this.canSelectMany) { - // if there are no checked elements, it means that an onDidChangeSelection never fired to overwrite - // `_selectedItems`. In that case, we should emit one with an empty array to ensure that - // `.selectedItems` is up to date. - if (!this.ui.list.getCheckedElements().length) { - this._selectedItems = []; - this.onDidChangeSelectionEmitter.fire(this.selectedItems); - } - } else if (this.activeItems[0]) { - // For single-select, we set `selectedItems` to the item that was accepted. - this._selectedItems = [this.activeItems[0]]; - this.onDidChangeSelectionEmitter.fire(this.selectedItems); - } - this.handleAccept(false); - })); - this.visibleDisposables.add(this.ui.onDidCustom(() => { - this.onDidCustomEmitter.fire(); - })); - this.visibleDisposables.add(this.ui.list.onDidChangeFocus(focusedItems => { - if (this.activeItemsUpdated) { - return; // Expect another event. - } - if (this.activeItemsToConfirm !== this._activeItems && equals(focusedItems, this._activeItems, (a, b) => a === b)) { - return; - } - this._activeItems = focusedItems as T[]; - this.onDidChangeActiveEmitter.fire(focusedItems as T[]); - })); - this.visibleDisposables.add(this.ui.list.onDidChangeSelection(({ items: selectedItems, event }) => { - if (this.canSelectMany) { - if (selectedItems.length) { - this.ui.list.setSelectedElements([]); - } - return; - } - if (this.selectedItemsToConfirm !== this._selectedItems && equals(selectedItems, this._selectedItems, (a, b) => a === b)) { - return; - } - this._selectedItems = selectedItems as T[]; - this.onDidChangeSelectionEmitter.fire(selectedItems as T[]); - if (selectedItems.length) { - this.handleAccept(event instanceof MouseEvent && event.button === 1 /* mouse middle click */); - } - })); - this.visibleDisposables.add(this.ui.list.onChangedCheckedElements(checkedItems => { - if (!this.canSelectMany) { - return; - } - if (this.selectedItemsToConfirm !== this._selectedItems && equals(checkedItems, this._selectedItems, (a, b) => a === b)) { - return; - } - this._selectedItems = checkedItems as T[]; - this.onDidChangeSelectionEmitter.fire(checkedItems as T[]); - })); - this.visibleDisposables.add(this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent<T>))); - this.visibleDisposables.add(this.registerQuickNavigation()); - this.valueSelectionUpdated = true; - } - super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.) - } - - private handleAccept(inBackground: boolean): void { - - // Figure out veto via `onWillAccept` event - let veto = false; - this.onWillAcceptEmitter.fire({ veto: () => veto = true }); - - // Continue with `onDidAccept` if no veto - if (!veto) { - this.onDidAcceptEmitter.fire({ inBackground }); - } - } - - private registerQuickNavigation() { - return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, e => { - if (this.canSelectMany || !this._quickNavigate) { - return; - } - - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e); - const keyCode = keyboardEvent.keyCode; - - // Select element when keys are pressed that signal it - const quickNavKeys = this._quickNavigate.keybindings; - const wasTriggerKeyPressed = quickNavKeys.some(k => { - const [firstPart, chordPart] = k.getParts(); - if (chordPart) { - return false; - } - - if (firstPart.shiftKey && keyCode === KeyCode.Shift) { - if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { - return false; // this is an optimistic check for the shift key being used to navigate back in quick input - } - - return true; - } - - if (firstPart.altKey && keyCode === KeyCode.Alt) { - return true; - } - - if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) { - return true; - } - - if (firstPart.metaKey && keyCode === KeyCode.Meta) { - return true; - } - - return false; - }); - - if (wasTriggerKeyPressed) { - if (this.activeItems[0]) { - this._selectedItems = [this.activeItems[0]]; - this.onDidChangeSelectionEmitter.fire(this.selectedItems); - this.handleAccept(false); - } - // Unset quick navigate after press. It is only valid once - // and should not result in any behaviour change afterwards - // if the picker remains open because there was no active item - this._quickNavigate = undefined; - } - }); - } - - protected override update() { - if (!this.visible) { - return; - } - // store the scrollTop before it is reset - const scrollTopBefore = this.keepScrollPosition ? this.scrollTop : 0; - const hideInput = !!this._hideInput && this._items.length > 0; - this.ui.container.classList.toggle('hidden-input', hideInput && !this.description); - const visibilities: Visibilities = { - title: !!this.title || !!this.step || !!this.buttons.length, - description: !!this.description, - checkAll: this.canSelectMany && !this._hideCheckAll, - checkBox: this.canSelectMany, - inputBox: !hideInput, - progressBar: !hideInput, - visibleCount: true, - count: this.canSelectMany, - ok: this.ok === 'default' ? this.canSelectMany : this.ok, - list: true, - message: !!this.validationMessage, - customButton: this.customButton - }; - this.ui.setVisibilities(visibilities); - super.update(); - if (this.ui.inputBox.value !== this.value) { - this.ui.inputBox.value = this.value; - } - if (this.valueSelectionUpdated) { - this.valueSelectionUpdated = false; - this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] }); - } - if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { - this.ui.inputBox.placeholder = (this.placeholder || ''); - } - - let ariaLabel = this.ariaLabel; - if (!ariaLabel) { - ariaLabel = this.placeholder || QuickPick.DEFAULT_ARIA_LABEL; - // If we have a title, include it in the aria label. - if (this.title) { - ariaLabel += ` - ${this.title}`; - } - } - if (this.ui.inputBox.ariaLabel !== ariaLabel) { - this.ui.inputBox.ariaLabel = ariaLabel; - } - this.ui.list.matchOnDescription = this.matchOnDescription; - this.ui.list.matchOnDetail = this.matchOnDetail; - this.ui.list.matchOnLabel = this.matchOnLabel; - this.ui.list.matchOnLabelMode = this.matchOnLabelMode; - this.ui.list.sortByLabel = this.sortByLabel; - if (this.itemsUpdated) { - this.itemsUpdated = false; - this.ui.list.setElements(this.items); - this.ui.list.filter(this.filterValue(this.ui.inputBox.value)); - this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); - this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); - this.ui.count.setCount(this.ui.list.getCheckedCount()); - switch (this._itemActivation) { - case ItemActivation.NONE: - this._itemActivation = ItemActivation.FIRST; // only valid once, then unset - break; - case ItemActivation.SECOND: - this.ui.list.focus(QuickInputListFocus.Second); - this._itemActivation = ItemActivation.FIRST; // only valid once, then unset - break; - case ItemActivation.LAST: - this.ui.list.focus(QuickInputListFocus.Last); - this._itemActivation = ItemActivation.FIRST; // only valid once, then unset - break; - default: - this.trySelectFirst(); - break; - } - } - if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) { - if (this.canSelectMany) { - this.ui.list.clearFocus(); - } else { - this.trySelectFirst(); - } - } - if (this.activeItemsUpdated) { - this.activeItemsUpdated = false; - this.activeItemsToConfirm = this._activeItems; - this.ui.list.setFocusedElements(this.activeItems); - if (this.activeItemsToConfirm === this._activeItems) { - this.activeItemsToConfirm = null; - } - } - if (this.selectedItemsUpdated) { - this.selectedItemsUpdated = false; - this.selectedItemsToConfirm = this._selectedItems; - if (this.canSelectMany) { - this.ui.list.setCheckedElements(this.selectedItems); - } else { - this.ui.list.setSelectedElements(this.selectedItems); - } - if (this.selectedItemsToConfirm === this._selectedItems) { - this.selectedItemsToConfirm = null; - } - } - this.ui.customButton.label = this.customLabel || ''; - this.ui.customButton.element.title = this.customHover || ''; - this.ui.setComboboxAccessibility(true); - if (!visibilities.inputBox) { - // we need to move focus into the tree to detect keybindings - // properly when the input box is not visible (quick nav) - this.ui.list.domFocus(); - - // Focus the first element in the list if multiselect is enabled - if (this.canSelectMany) { - this.ui.list.focus(QuickInputListFocus.First); - } - } - - // Set the scroll position to what it was before updating the items - if (this.keepScrollPosition) { - this.scrollTop = scrollTopBefore; - } - } -} - -class InputBox extends QuickInput implements IInputBox { - private _value = ''; - private _valueSelection: Readonly<[number, number]> | undefined; - private valueSelectionUpdated = true; - private _placeholder: string | undefined; - private _password = false; - private _prompt: string | undefined; - private readonly onDidValueChangeEmitter = this._register(new Emitter<string>()); - private readonly onDidAcceptEmitter = this._register(new Emitter<void>()); - - get value() { - return this._value; - } - - set value(value: string) { - this._value = value || ''; - this.update(); - } - - set valueSelection(valueSelection: Readonly<[number, number]>) { - this._valueSelection = valueSelection; - this.valueSelectionUpdated = true; - this.update(); - } - - get placeholder() { - return this._placeholder; - } - - set placeholder(placeholder: string | undefined) { - this._placeholder = placeholder; - this.update(); - } - - get password() { - return this._password; - } - - set password(password: boolean) { - this._password = password; - this.update(); - } - - get prompt() { - return this._prompt; - } - - set prompt(prompt: string | undefined) { - this._prompt = prompt; - this.noValidationMessage = prompt - ? localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", prompt) - : QuickInput.noPromptMessage; - this.update(); - } - - readonly onDidChangeValue = this.onDidValueChangeEmitter.event; - - readonly onDidAccept = this.onDidAcceptEmitter.event; - - override show() { - if (!this.visible) { - this.visibleDisposables.add( - this.ui.inputBox.onDidChange(value => { - if (value === this.value) { - return; - } - this._value = value; - this.onDidValueChangeEmitter.fire(value); - })); - this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire())); - this.valueSelectionUpdated = true; - } - super.show(); - } - - protected override update() { - if (!this.visible) { - return; - } - const visibilities: Visibilities = { - title: !!this.title || !!this.step || !!this.buttons.length, - description: !!this.description || !!this.step, - inputBox: true, message: true - }; - this.ui.setVisibilities(visibilities); - super.update(); - if (this.ui.inputBox.value !== this.value) { - this.ui.inputBox.value = this.value; - } - if (this.valueSelectionUpdated) { - this.valueSelectionUpdated = false; - this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] }); - } - if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { - this.ui.inputBox.placeholder = (this.placeholder || ''); - } - if (this.ui.inputBox.password !== this.password) { - this.ui.inputBox.password = this.password; - } - - } -} - -export class QuickInputController extends Disposable { - private static readonly MAX_WIDTH = 600; // Max total width of quick input widget - - private idPrefix: string; - private ui: QuickInputUI | undefined; - private dimension?: dom.IDimension; - private titleBarOffset?: number; - private comboboxAccessibility = false; - private enabled = true; - private readonly onDidAcceptEmitter = this._register(new Emitter<void>()); - private readonly onDidCustomEmitter = this._register(new Emitter<void>()); - private readonly onDidTriggerButtonEmitter = this._register(new Emitter<IQuickInputButton>()); - private keyMods: Writeable<IKeyMods> = { ctrlCmd: false, alt: false }; - - private controller: QuickInput | null = null; - - private parentElement: HTMLElement; - private styles: IQuickInputStyles; - - private onShowEmitter = this._register(new Emitter<void>()); - readonly onShow = this.onShowEmitter.event; - - private onHideEmitter = this._register(new Emitter<void>()); - readonly onHide = this.onHideEmitter.event; - - private previousFocusElement?: HTMLElement; - - constructor(private options: IQuickInputOptions) { - super(); - this.idPrefix = options.idPrefix; - this.parentElement = options.container; - this.styles = options.styles; - this.registerKeyModsListeners(); - } - - private registerKeyModsListeners() { - const listener = (e: KeyboardEvent | MouseEvent) => { - this.keyMods.ctrlCmd = e.ctrlKey || e.metaKey; - this.keyMods.alt = e.altKey; - }; - this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, listener, true)); - this._register(dom.addDisposableListener(window, dom.EventType.KEY_UP, listener, true)); - this._register(dom.addDisposableListener(window, dom.EventType.MOUSE_DOWN, listener, true)); - } - - private getUI() { - if (this.ui) { - return this.ui; - } - - const container = dom.append(this.parentElement, $('.quick-input-widget.show-file-icons')); - container.tabIndex = -1; - container.style.display = 'none'; - - const styleSheet = dom.createStyleSheet(container); - - const titleBar = dom.append(container, $('.quick-input-titlebar')); - - const leftActionBar = this._register(new ActionBar(titleBar)); - leftActionBar.domNode.classList.add('quick-input-left-action-bar'); - - const title = dom.append(titleBar, $('.quick-input-title')); - - const rightActionBar = this._register(new ActionBar(titleBar)); - rightActionBar.domNode.classList.add('quick-input-right-action-bar'); - - const description1 = dom.append(container, $('.quick-input-description')); - const headerContainer = dom.append(container, $('.quick-input-header')); - - const checkAll = <HTMLInputElement>dom.append(headerContainer, $('input.quick-input-check-all')); - checkAll.type = 'checkbox'; - checkAll.setAttribute('aria-label', localize('quickInput.checkAll', "Toggle all checkboxes")); - this._register(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => { - const checked = checkAll.checked; - list.setAllVisibleChecked(checked); - })); - this._register(dom.addDisposableListener(checkAll, dom.EventType.CLICK, e => { - if (e.x || e.y) { // Avoid 'click' triggered by 'space'... - inputBox.setFocus(); - } - })); - - const description2 = dom.append(headerContainer, $('.quick-input-description')); - const extraContainer = dom.append(headerContainer, $('.quick-input-and-message')); - const filterContainer = dom.append(extraContainer, $('.quick-input-filter')); - - const inputBox = this._register(new QuickInputBox(filterContainer)); - inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`); - - const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count')); - visibleCountContainer.setAttribute('aria-live', 'polite'); - visibleCountContainer.setAttribute('aria-atomic', 'true'); - const visibleCount = new CountBadge(visibleCountContainer, { countFormat: localize({ key: 'quickInput.visibleCount', comment: ['This tells the user how many items are shown in a list of items to select from. The items can be anything. Currently not visible, but read by screen readers.'] }, "{0} Results") }); - - const countContainer = dom.append(filterContainer, $('.quick-input-count')); - countContainer.setAttribute('aria-live', 'polite'); - const count = new CountBadge(countContainer, { countFormat: localize({ key: 'quickInput.countSelected', comment: ['This tells the user how many items are selected in a list of items to select from. The items can be anything.'] }, "{0} Selected") }); - - const okContainer = dom.append(headerContainer, $('.quick-input-action')); - const ok = new Button(okContainer); - ok.label = localize('ok', "OK"); - this._register(ok.onDidClick(e => { - this.onDidAcceptEmitter.fire(); - })); - - const customButtonContainer = dom.append(headerContainer, $('.quick-input-action')); - const customButton = new Button(customButtonContainer); - customButton.label = localize('custom', "Custom"); - this._register(customButton.onDidClick(e => { - this.onDidCustomEmitter.fire(); - })); - - const message = dom.append(extraContainer, $(`#${this.idPrefix}message.quick-input-message`)); - - const list = this._register(new QuickInputList(container, this.idPrefix + 'list', this.options)); - this._register(list.onChangedAllVisibleChecked(checked => { - checkAll.checked = checked; - })); - this._register(list.onChangedVisibleCount(c => { - visibleCount.setCount(c); - })); - this._register(list.onChangedCheckedCount(c => { - count.setCount(c); - })); - this._register(list.onLeave(() => { - // Defer to avoid the input field reacting to the triggering key. - setTimeout(() => { - inputBox.setFocus(); - if (this.controller instanceof QuickPick && this.controller.canSelectMany) { - list.clearFocus(); - } - }, 0); - })); - this._register(list.onDidChangeFocus(() => { - if (this.comboboxAccessibility) { - this.getUI().inputBox.setAttribute('aria-activedescendant', this.getUI().list.getActiveDescendant() || ''); - } - })); - - const progressBar = new ProgressBar(container); - progressBar.getContainer().classList.add('quick-input-progress'); - - const focusTracker = dom.trackFocus(container); - this._register(focusTracker); - this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, e => { - this.previousFocusElement = e.relatedTarget instanceof HTMLElement ? e.relatedTarget : undefined; - }, true)); - this._register(focusTracker.onDidBlur(() => { - if (!this.getUI().ignoreFocusOut && !this.options.ignoreFocusOut()) { - this.hide(QuickInputHideReason.Blur); - } - this.previousFocusElement = undefined; - })); - this._register(dom.addDisposableListener(container, dom.EventType.FOCUS, (e: FocusEvent) => { - inputBox.setFocus(); - })); - this._register(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { - const event = new StandardKeyboardEvent(e); - switch (event.keyCode) { - case KeyCode.Enter: - dom.EventHelper.stop(e, true); - this.onDidAcceptEmitter.fire(); - break; - case KeyCode.Escape: - dom.EventHelper.stop(e, true); - this.hide(QuickInputHideReason.Gesture); - break; - case KeyCode.Tab: - if (!event.altKey && !event.ctrlKey && !event.metaKey) { - const selectors = ['.action-label.codicon']; - if (container.classList.contains('show-checkboxes')) { - selectors.push('input'); - } else { - selectors.push('input[type=text]'); - } - if (this.getUI().list.isDisplayed()) { - selectors.push('.monaco-list'); - } - const stops = container.querySelectorAll<HTMLElement>(selectors.join(', ')); - if (event.shiftKey && event.target === stops[0]) { - dom.EventHelper.stop(e, true); - stops[stops.length - 1].focus(); - } else if (!event.shiftKey && event.target === stops[stops.length - 1]) { - dom.EventHelper.stop(e, true); - stops[0].focus(); - } - } - break; - } - })); - - this.ui = { - container, - styleSheet, - leftActionBar, - titleBar, - title, - description1, - description2, - rightActionBar, - checkAll, - filterContainer, - inputBox, - visibleCountContainer, - visibleCount, - countContainer, - count, - okContainer, - ok, - message, - customButtonContainer, - customButton, - list, - progressBar, - onDidAccept: this.onDidAcceptEmitter.event, - onDidCustom: this.onDidCustomEmitter.event, - onDidTriggerButton: this.onDidTriggerButtonEmitter.event, - ignoreFocusOut: false, - keyMods: this.keyMods, - isScreenReaderOptimized: () => this.options.isScreenReaderOptimized(), - show: controller => this.show(controller), - hide: () => this.hide(), - setVisibilities: visibilities => this.setVisibilities(visibilities), - setComboboxAccessibility: enabled => this.setComboboxAccessibility(enabled), - setEnabled: enabled => this.setEnabled(enabled), - setContextKey: contextKey => this.options.setContextKey(contextKey), - }; - this.updateStyles(); - return this.ui; - } - - pick<T extends IQuickPickItem, O extends IPickOptions<T>>(picks: Promise<QuickPickInput<T>[]> | QuickPickInput<T>[], options: O = <O>{}, token: CancellationToken = CancellationToken.None): Promise<(O extends { canPickMany: true } ? T[] : T) | undefined> { - type R = (O extends { canPickMany: true } ? T[] : T) | undefined; - return new Promise<R>((doResolve, reject) => { - let resolve = (result: R) => { - resolve = doResolve; - options.onKeyMods?.(input.keyMods); - doResolve(result); - }; - if (token.isCancellationRequested) { - resolve(undefined); - return; - } - const input = this.createQuickPick<T>(); - let activeItem: T | undefined; - const disposables = [ - input, - input.onDidAccept(() => { - if (input.canSelectMany) { - resolve(<R>input.selectedItems.slice()); - input.hide(); - } else { - const result = input.activeItems[0]; - if (result) { - resolve(<R>result); - input.hide(); - } - } - }), - input.onDidChangeActive(items => { - const focused = items[0]; - if (focused && options.onDidFocus) { - options.onDidFocus(focused); - } - }), - input.onDidChangeSelection(items => { - if (!input.canSelectMany) { - const result = items[0]; - if (result) { - resolve(<R>result); - input.hide(); - } - } - }), - input.onDidTriggerItemButton(event => options.onDidTriggerItemButton && options.onDidTriggerItemButton({ - ...event, - removeItem: () => { - const index = input.items.indexOf(event.item); - if (index !== -1) { - const items = input.items.slice(); - const removed = items.splice(index, 1); - const activeItems = input.activeItems.filter(activeItem => activeItem !== removed[0]); - const keepScrollPositionBefore = input.keepScrollPosition; - input.keepScrollPosition = true; - input.items = items; - if (activeItems) { - input.activeItems = activeItems; - } - input.keepScrollPosition = keepScrollPositionBefore; - } - } - })), - input.onDidChangeValue(value => { - if (activeItem && !value && (input.activeItems.length !== 1 || input.activeItems[0] !== activeItem)) { - input.activeItems = [activeItem]; - } - }), - token.onCancellationRequested(() => { - input.hide(); - }), - input.onDidHide(() => { - dispose(disposables); - resolve(undefined); - }), - ]; - input.title = options.title; - input.canSelectMany = !!options.canPickMany; - input.placeholder = options.placeHolder; - input.ignoreFocusOut = !!options.ignoreFocusLost; - input.matchOnDescription = !!options.matchOnDescription; - input.matchOnDetail = !!options.matchOnDetail; - input.matchOnLabel = (options.matchOnLabel === undefined) || options.matchOnLabel; // default to true - input.autoFocusOnList = (options.autoFocusOnList === undefined) || options.autoFocusOnList; // default to true - input.quickNavigate = options.quickNavigate; - input.hideInput = !!options.hideInput; - input.contextKey = options.contextKey; - input.busy = true; - Promise.all([picks, options.activeItem]) - .then(([items, _activeItem]) => { - activeItem = _activeItem; - input.busy = false; - input.items = items; - if (input.canSelectMany) { - input.selectedItems = items.filter(item => item.type !== 'separator' && item.picked) as T[]; - } - if (activeItem) { - input.activeItems = [activeItem]; - } - }); - input.show(); - Promise.resolve(picks).then(undefined, err => { - reject(err); - input.hide(); - }); - }); - } - - private setValidationOnInput(input: IInputBox, validationResult: string | { - content: string; - severity: Severity; - } | null | undefined) { - if (validationResult && isString(validationResult)) { - input.severity = Severity.Error; - input.validationMessage = validationResult; - } else if (validationResult && !isString(validationResult)) { - input.severity = validationResult.severity; - input.validationMessage = validationResult.content; - } else { - input.severity = Severity.Ignore; - input.validationMessage = undefined; - } - } - - input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): Promise<string | undefined> { - return new Promise<string | undefined>((resolve) => { - if (token.isCancellationRequested) { - resolve(undefined); - return; - } - const input = this.createInputBox(); - const validateInput = options.validateInput || (() => <Promise<undefined>>Promise.resolve(undefined)); - const onDidValueChange = Event.debounce(input.onDidChangeValue, (last, cur) => cur, 100); - let validationValue = options.value || ''; - let validation = Promise.resolve(validateInput(validationValue)); - const disposables = [ - input, - onDidValueChange(value => { - if (value !== validationValue) { - validation = Promise.resolve(validateInput(value)); - validationValue = value; - } - validation.then(result => { - if (value === validationValue) { - this.setValidationOnInput(input, result); - } - }); - }), - input.onDidAccept(() => { - const value = input.value; - if (value !== validationValue) { - validation = Promise.resolve(validateInput(value)); - validationValue = value; - } - validation.then(result => { - if (!result || (!isString(result) && result.severity !== Severity.Error)) { - resolve(value); - input.hide(); - } else if (value === validationValue) { - this.setValidationOnInput(input, result); - } - }); - }), - token.onCancellationRequested(() => { - input.hide(); - }), - input.onDidHide(() => { - dispose(disposables); - resolve(undefined); - }), - ]; - - input.title = options.title; - input.value = options.value || ''; - input.valueSelection = options.valueSelection; - input.prompt = options.prompt; - input.placeholder = options.placeHolder; - input.password = !!options.password; - input.ignoreFocusOut = !!options.ignoreFocusLost; - input.show(); - }); - } - - backButton = backButton; - - createQuickPick<T extends IQuickPickItem>(): IQuickPick<T> { - const ui = this.getUI(); - return new QuickPick<T>(ui); - } - - createInputBox(): IInputBox { - const ui = this.getUI(); - return new InputBox(ui); - } - - private show(controller: QuickInput) { - const ui = this.getUI(); - this.onShowEmitter.fire(); - const oldController = this.controller; - this.controller = controller; - if (oldController) { - oldController.didHide(); - } - - this.setEnabled(true); - ui.leftActionBar.clear(); - ui.title.textContent = ''; - ui.description1.textContent = ''; - ui.description2.textContent = ''; - ui.rightActionBar.clear(); - ui.checkAll.checked = false; - // ui.inputBox.value = ''; Avoid triggering an event. - ui.inputBox.placeholder = ''; - ui.inputBox.password = false; - ui.inputBox.showDecoration(Severity.Ignore); - ui.visibleCount.setCount(0); - ui.count.setCount(0); - dom.reset(ui.message); - ui.progressBar.stop(); - ui.list.setElements([]); - ui.list.matchOnDescription = false; - ui.list.matchOnDetail = false; - ui.list.matchOnLabel = true; - ui.list.sortByLabel = true; - ui.ignoreFocusOut = false; - this.setComboboxAccessibility(false); - ui.inputBox.ariaLabel = ''; - - const backKeybindingLabel = this.options.backKeybindingLabel(); - backButton.tooltip = backKeybindingLabel ? localize('quickInput.backWithKeybinding', "Back ({0})", backKeybindingLabel) : localize('quickInput.back', "Back"); - - ui.container.style.display = ''; - this.updateLayout(); - ui.inputBox.setFocus(); - } - - private setVisibilities(visibilities: Visibilities) { - const ui = this.getUI(); - ui.title.style.display = visibilities.title ? '' : 'none'; - ui.description1.style.display = visibilities.description && (visibilities.inputBox || visibilities.checkAll) ? '' : 'none'; - ui.description2.style.display = visibilities.description && !(visibilities.inputBox || visibilities.checkAll) ? '' : 'none'; - ui.checkAll.style.display = visibilities.checkAll ? '' : 'none'; - ui.filterContainer.style.display = visibilities.inputBox ? '' : 'none'; - ui.visibleCountContainer.style.display = visibilities.visibleCount ? '' : 'none'; - ui.countContainer.style.display = visibilities.count ? '' : 'none'; - ui.okContainer.style.display = visibilities.ok ? '' : 'none'; - ui.customButtonContainer.style.display = visibilities.customButton ? '' : 'none'; - ui.message.style.display = visibilities.message ? '' : 'none'; - ui.progressBar.getContainer().style.display = visibilities.progressBar ? '' : 'none'; - ui.list.display(!!visibilities.list); - ui.container.classList[visibilities.checkBox ? 'add' : 'remove']('show-checkboxes'); - this.updateLayout(); // TODO - } - - private setComboboxAccessibility(enabled: boolean) { - if (enabled !== this.comboboxAccessibility) { - const ui = this.getUI(); - this.comboboxAccessibility = enabled; - if (this.comboboxAccessibility) { - ui.inputBox.setAttribute('role', 'combobox'); - ui.inputBox.setAttribute('aria-haspopup', 'true'); - ui.inputBox.setAttribute('aria-autocomplete', 'list'); - ui.inputBox.setAttribute('aria-activedescendant', ui.list.getActiveDescendant() || ''); - } else { - ui.inputBox.removeAttribute('role'); - ui.inputBox.removeAttribute('aria-haspopup'); - ui.inputBox.removeAttribute('aria-autocomplete'); - ui.inputBox.removeAttribute('aria-activedescendant'); - } - } - } - - private setEnabled(enabled: boolean) { - if (enabled !== this.enabled) { - this.enabled = enabled; - for (const item of this.getUI().leftActionBar.viewItems) { - (item as ActionViewItem).getAction().enabled = enabled; - } - for (const item of this.getUI().rightActionBar.viewItems) { - (item as ActionViewItem).getAction().enabled = enabled; - } - this.getUI().checkAll.disabled = !enabled; - // this.getUI().inputBox.enabled = enabled; Avoid loosing focus. - this.getUI().ok.enabled = enabled; - this.getUI().list.enabled = enabled; - } - } - - hide(reason?: QuickInputHideReason) { - const controller = this.controller; - if (controller) { - const focusChanged = !this.ui?.container.contains(document.activeElement); - this.controller = null; - this.onHideEmitter.fire(); - this.getUI().container.style.display = 'none'; - if (!focusChanged) { - let currentElement = this.previousFocusElement; - while (currentElement && !currentElement.offsetParent) { - currentElement = withNullAsUndefined(currentElement.parentElement); - } - if (currentElement?.offsetParent) { - currentElement.focus(); - this.previousFocusElement = undefined; - } else { - this.options.returnFocus(); - } - } - controller.didHide(reason); - } - } - - focus() { - if (this.isDisplayed()) { - const ui = this.getUI(); - if (ui.inputBox.enabled) { - ui.inputBox.setFocus(); - } else { - ui.list.domFocus(); - } - } - } - - toggle() { - if (this.isDisplayed() && this.controller instanceof QuickPick && this.controller.canSelectMany) { - this.getUI().list.toggleCheckbox(); - } - } - - navigate(next: boolean, quickNavigate?: IQuickNavigateConfiguration) { - if (this.isDisplayed() && this.getUI().list.isDisplayed()) { - this.getUI().list.focus(next ? QuickInputListFocus.Next : QuickInputListFocus.Previous); - if (quickNavigate && this.controller instanceof QuickPick) { - this.controller.quickNavigate = quickNavigate; - } - } - } - - async accept(keyMods: IKeyMods = { alt: false, ctrlCmd: false }) { - // When accepting the item programmatically, it is important that - // we update `keyMods` either from the provided set or unset it - // because the accept did not happen from mouse or keyboard - // interaction on the list itself - this.keyMods.alt = keyMods.alt; - this.keyMods.ctrlCmd = keyMods.ctrlCmd; - - this.onDidAcceptEmitter.fire(); - } - - async back() { - this.onDidTriggerButtonEmitter.fire(this.backButton); - } - - async cancel() { - this.hide(); - } - - layout(dimension: dom.IDimension, titleBarOffset: number): void { - this.dimension = dimension; - this.titleBarOffset = titleBarOffset; - this.updateLayout(); - } - - private updateLayout() { - if (this.ui) { - this.ui.container.style.top = `${this.titleBarOffset}px`; - - const style = this.ui.container.style; - const width = Math.min(this.dimension!.width * 0.62 /* golden cut */, QuickInputController.MAX_WIDTH); - style.width = width + 'px'; - style.marginLeft = '-' + (width / 2) + 'px'; - - this.ui.inputBox.layout(); - this.ui.list.layout(this.dimension && this.dimension.height * 0.4); - } - } - - applyStyles(styles: IQuickInputStyles) { - this.styles = styles; - this.updateStyles(); - } - - private updateStyles() { - if (this.ui) { - const { - quickInputTitleBackground, - quickInputBackground, - quickInputForeground, - contrastBorder, - widgetShadow, - } = this.styles.widget; - this.ui.titleBar.style.backgroundColor = quickInputTitleBackground ? quickInputTitleBackground.toString() : ''; - 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 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); - this.ui.customButton.style(this.styles.button); - this.ui.progressBar.style(this.styles.progressBar); - this.ui.list.style(this.styles.list); - - const content: string[] = []; - if (this.styles.list.pickerGroupBorder) { - content.push(`.quick-input-list .quick-input-list-entry { border-top-color: ${this.styles.list.pickerGroupBorder}; }`); - } - if (this.styles.list.pickerGroupForeground) { - content.push(`.quick-input-list .quick-input-list-separator { color: ${this.styles.list.pickerGroupForeground}; }`); - } - - if ( - this.styles.keybindingLabel.keybindingLabelBackground || - this.styles.keybindingLabel.keybindingLabelBorder || - this.styles.keybindingLabel.keybindingLabelBottomBorder || - this.styles.keybindingLabel.keybindingLabelShadow || - this.styles.keybindingLabel.keybindingLabelForeground - ) { - content.push('.quick-input-list .monaco-keybinding > .monaco-keybinding-key {'); - if (this.styles.keybindingLabel.keybindingLabelBackground) { - content.push(`background-color: ${this.styles.keybindingLabel.keybindingLabelBackground};`); - } - if (this.styles.keybindingLabel.keybindingLabelBorder) { - // Order matters here. `border-color` must come before `border-bottom-color`. - content.push(`border-color: ${this.styles.keybindingLabel.keybindingLabelBorder};`); - } - if (this.styles.keybindingLabel.keybindingLabelBottomBorder) { - content.push(`border-bottom-color: ${this.styles.keybindingLabel.keybindingLabelBottomBorder};`); - } - if (this.styles.keybindingLabel.keybindingLabelShadow) { - content.push(`box-shadow: inset 0 -1px 0 ${this.styles.keybindingLabel.keybindingLabelShadow};`); - } - if (this.styles.keybindingLabel.keybindingLabelForeground) { - content.push(`color: ${this.styles.keybindingLabel.keybindingLabelForeground};`); - } - content.push('}'); - } - - const newStyles = content.join('\n'); - if (newStyles !== this.ui.styleSheet.textContent) { - this.ui.styleSheet.textContent = newStyles; - } - } - } - - private isDisplayed() { - return this.ui && this.ui.container.style.display !== 'none'; - } -} diff --git a/src/vs/base/parts/quickinput/browser/quickInputBox.ts b/src/vs/base/parts/quickinput/browser/quickInputBox.ts deleted file mode 100644 index 0510857d30..0000000000 --- a/src/vs/base/parts/quickinput/browser/quickInputBox.ts +++ /dev/null @@ -1,128 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as dom from 'vs/base/browser/dom'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IInputBoxStyles, InputBox, IRange, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import Severity from 'vs/base/common/severity'; -import 'vs/css!./media/quickInput'; - -const $ = dom.$; - -export class QuickInputBox extends Disposable { - - private container: HTMLElement; - private inputBox: InputBox; - - constructor( - private parent: HTMLElement - ) { - super(); - this.container = dom.append(this.parent, $('.quick-input-box')); - this.inputBox = this._register(new InputBox(this.container, undefined)); - } - - onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => { - return dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { - handler(new StandardKeyboardEvent(e)); - }); - }; - - onMouseDown = (handler: (event: StandardMouseEvent) => void): IDisposable => { - return dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => { - handler(new StandardMouseEvent(e)); - }); - }; - - onDidChange = (handler: (event: string) => void): IDisposable => { - return this.inputBox.onDidChange(handler); - }; - - get value() { - return this.inputBox.value; - } - - set value(value: string) { - this.inputBox.value = value; - } - - select(range: IRange | null = null): void { - this.inputBox.select(range); - } - - isSelectionAtEnd(): boolean { - return this.inputBox.isSelectionAtEnd(); - } - - setPlaceholder(placeholder: string): void { - this.inputBox.setPlaceHolder(placeholder); - } - - get placeholder() { - return this.inputBox.inputElement.getAttribute('placeholder') || ''; - } - - set placeholder(placeholder: string) { - this.inputBox.setPlaceHolder(placeholder); - } - - get ariaLabel() { - return this.inputBox.getAriaLabel(); - } - - set ariaLabel(ariaLabel: string) { - this.inputBox.setAriaLabel(ariaLabel); - } - - get password() { - return this.inputBox.inputElement.type === 'password'; - } - - set password(password: boolean) { - this.inputBox.inputElement.type = password ? 'password' : 'text'; - } - - set enabled(enabled: boolean) { - this.inputBox.setEnabled(enabled); - } - - hasFocus(): boolean { - return this.inputBox.hasFocus(); - } - - setAttribute(name: string, value: string): void { - this.inputBox.inputElement.setAttribute(name, value); - } - - removeAttribute(name: string): void { - this.inputBox.inputElement.removeAttribute(name); - } - - showDecoration(decoration: Severity): void { - if (decoration === Severity.Ignore) { - this.inputBox.hideMessage(); - } else { - this.inputBox.showMessage({ type: decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR, content: '' }); - } - } - - stylesForType(decoration: Severity) { - return this.inputBox.stylesForType(decoration === Severity.Info ? MessageType.INFO : decoration === Severity.Warning ? MessageType.WARNING : MessageType.ERROR); - } - - setFocus(): void { - this.inputBox.focus(); - } - - layout(): void { - this.inputBox.layout(); - } - - style(styles: IInputBoxStyles): void { - this.inputBox.style(styles); - } -} diff --git a/src/vs/base/parts/quickinput/browser/quickInputUtils.ts b/src/vs/base/parts/quickinput/browser/quickInputUtils.ts deleted file mode 100644 index a091e21ac3..0000000000 --- a/src/vs/base/parts/quickinput/browser/quickInputUtils.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as dom from 'vs/base/browser/dom'; -import { IdGenerator } from 'vs/base/common/idGenerator'; -import { URI } from 'vs/base/common/uri'; -import 'vs/css!./media/quickInput'; - -const iconPathToClass: Record<string, string> = {}; -const iconClassGenerator = new IdGenerator('quick-input-button-icon-'); - -export function getIconClass(iconPath: { dark: URI; light?: URI } | undefined): string | undefined { - if (!iconPath) { - return undefined; - } - let iconClass: string; - - const key = iconPath.dark.toString(); - if (iconPathToClass[key]) { - iconClass = iconPathToClass[key]; - } else { - iconClass = iconClassGenerator.nextId(); - dom.createCSSRule(`.${iconClass}, .hc-light .${iconClass}`, `background-image: ${dom.asCSSUrl(iconPath.light || iconPath.dark)}`); - dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: ${dom.asCSSUrl(iconPath.dark)}`); - iconPathToClass[key] = iconClass; - } - - return iconClass; -} diff --git a/src/vs/base/parts/quickinput/common/quickInput.ts b/src/vs/base/parts/quickinput/common/quickInput.ts deleted file mode 100644 index a786f5ef1c..0000000000 --- a/src/vs/base/parts/quickinput/common/quickInput.ts +++ /dev/null @@ -1,428 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import { IMatch } from 'vs/base/common/filters'; -import { IItemAccessor } from 'vs/base/common/fuzzyScorer'; -import { ResolvedKeybinding } from 'vs/base/common/keybindings'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import Severity from 'vs/base/common/severity'; -import { URI } from 'vs/base/common/uri'; - -export interface IQuickPickItemHighlights { - label?: IMatch[]; - description?: IMatch[]; - detail?: IMatch[]; -} - -export interface IQuickPickItem { - type?: 'item'; - id?: string; - label: string; - meta?: string; - ariaLabel?: string; - description?: string; - detail?: string; - /** - * Allows to show a keybinding next to the item to indicate - * how the item can be triggered outside of the picker using - * keyboard shortcut. - */ - keybinding?: ResolvedKeybinding; - iconClasses?: string[]; - italic?: boolean; - strikethrough?: boolean; - highlights?: IQuickPickItemHighlights; - buttons?: IQuickInputButton[]; - picked?: boolean; - alwaysShow?: boolean; -} - -export interface IQuickPickSeparator { - type: 'separator'; - label?: string; -} - -export interface IKeyMods { - readonly ctrlCmd: boolean; - readonly alt: boolean; -} - -export const NO_KEY_MODS: IKeyMods = { ctrlCmd: false, alt: false }; - -export interface IQuickNavigateConfiguration { - keybindings: ResolvedKeybinding[]; -} - -export interface IPickOptions<T extends IQuickPickItem> { - - /** - * an optional string to show as the title of the quick input - */ - title?: string; - - /** - * an optional string to show as placeholder in the input box to guide the user what she picks on - */ - placeHolder?: string; - - /** - * an optional flag to include the description when filtering the picks - */ - matchOnDescription?: boolean; - - /** - * an optional flag to include the detail when filtering the picks - */ - matchOnDetail?: boolean; - - /** - * an optional flag to filter the picks based on label. Defaults to true. - */ - matchOnLabel?: boolean; - - /** - * an option flag to control whether focus is always automatically brought to a list item. Defaults to true. - */ - autoFocusOnList?: boolean; - - /** - * an optional flag to not close the picker on focus lost - */ - ignoreFocusLost?: boolean; - - /** - * an optional flag to make this picker multi-select - */ - canPickMany?: boolean; - - /** - * enables quick navigate in the picker to open an element without typing - */ - quickNavigate?: IQuickNavigateConfiguration; - - /** - * Hides the input box from the picker UI. This is typically used - * in combination with quick-navigation where no search UI should - * be presented. - */ - hideInput?: boolean; - - /** - * a context key to set when this picker is active - */ - contextKey?: string; - - /** - * an optional property for the item to focus initially. - */ - activeItem?: Promise<T> | T; - - onKeyMods?: (keyMods: IKeyMods) => void; - onDidFocus?: (entry: T) => void; - onDidTriggerItemButton?: (context: IQuickPickItemButtonContext<T>) => void; -} - -export interface IInputOptions { - - /** - * an optional string to show as the title of the quick input - */ - title?: string; - - /** - * the value to prefill in the input box - */ - value?: string; - - /** - * the selection of value, default to the whole word - */ - valueSelection?: [number, number]; - - /** - * the text to display underneath the input box - */ - prompt?: string; - - /** - * an optional string to show as placeholder in the input box to guide the user what to type - */ - placeHolder?: string; - - /** - * Controls if a password input is shown. Password input hides the typed text. - */ - password?: boolean; - - ignoreFocusLost?: boolean; - - /** - * an optional function that is used to validate user input. - */ - validateInput?: (input: string) => Promise<string | null | undefined | { content: string; severity: Severity }>; -} - -export enum QuickInputHideReason { - - /** - * Focus moved away from the quick input. - */ - Blur = 1, - - /** - * An explicit user gesture, e.g. pressing Escape key. - */ - Gesture, - - /** - * Anything else. - */ - Other -} - -export interface IQuickInputHideEvent { - reason: QuickInputHideReason; -} - -export interface IQuickInput extends IDisposable { - - readonly onDidHide: Event<IQuickInputHideEvent>; - readonly onDispose: Event<void>; - - title: string | undefined; - - description: string | undefined; - - step: number | undefined; - - totalSteps: number | undefined; - - enabled: boolean; - - contextKey: string | undefined; - - busy: boolean; - - ignoreFocusOut: boolean; - - show(): void; - - hide(): void; -} - -export interface IQuickPickWillAcceptEvent { - - /** - * Allows to disable the default accept handling - * of the picker. If `veto` is called, the picker - * will not trigger the `onDidAccept` event. - */ - veto(): void; -} - -export interface IQuickPickDidAcceptEvent { - - /** - * Signals if the picker item is to be accepted - * in the background while keeping the picker open. - */ - inBackground: boolean; -} - -export enum ItemActivation { - NONE, - FIRST, - SECOND, - LAST -} - -export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput { - - value: string; - - /** - * A method that allows to massage the value used - * for filtering, e.g, to remove certain parts. - */ - filterValue: (value: string) => string; - - ariaLabel: string | undefined; - - placeholder: string | undefined; - - readonly onDidChangeValue: Event<string>; - - readonly onWillAccept: Event<IQuickPickWillAcceptEvent>; - readonly onDidAccept: Event<IQuickPickDidAcceptEvent>; - - /** - * If enabled, will fire the `onDidAccept` event when - * pressing the arrow-right key with the idea of accepting - * the selected item without closing the picker. - */ - canAcceptInBackground: boolean; - - ok: boolean | 'default'; - - readonly onDidCustom: Event<void>; - - customButton: boolean; - - customLabel: string | undefined; - - customHover: string | undefined; - - buttons: ReadonlyArray<IQuickInputButton>; - - readonly onDidTriggerButton: Event<IQuickInputButton>; - - readonly onDidTriggerItemButton: Event<IQuickPickItemButtonEvent<T>>; - - items: ReadonlyArray<T | IQuickPickSeparator>; - - canSelectMany: boolean; - - matchOnDescription: boolean; - - matchOnDetail: boolean; - - matchOnLabel: boolean; - - /** - * The mode to filter label with. Fuzzy will use fuzzy searching and - * contiguous will make filter entries that do not contain the exact string - * (including whitespace). This defaults to `'fuzzy'`. - */ - matchOnLabelMode: 'fuzzy' | 'contiguous'; - - sortByLabel: boolean; - - autoFocusOnList: boolean; - - keepScrollPosition: boolean; - - quickNavigate: IQuickNavigateConfiguration | undefined; - - activeItems: ReadonlyArray<T>; - - readonly onDidChangeActive: Event<T[]>; - - /** - * Allows to control which entry should be activated by default. - */ - itemActivation: ItemActivation; - - selectedItems: ReadonlyArray<T>; - - readonly onDidChangeSelection: Event<T[]>; - - readonly keyMods: IKeyMods; - - valueSelection: Readonly<[number, number]> | undefined; - - validationMessage: string | undefined; - - inputHasFocus(): boolean; - - focusOnInput(): void; - - /** - * Hides the input box from the picker UI. This is typically used - * in combination with quick-navigation where no search UI should - * be presented. - */ - hideInput: boolean; - - hideCheckAll: boolean; -} - -export interface IInputBox extends IQuickInput { - - value: string; - - valueSelection: Readonly<[number, number]> | undefined; - - placeholder: string | undefined; - - password: boolean; - - readonly onDidChangeValue: Event<string>; - - readonly onDidAccept: Event<void>; - - buttons: ReadonlyArray<IQuickInputButton>; - - readonly onDidTriggerButton: Event<IQuickInputButton>; - - prompt: string | undefined; - - validationMessage: string | undefined; - - severity: Severity; -} - -export interface IQuickInputButton { - /** iconPath or iconClass required */ - iconPath?: { dark: URI; light?: URI }; - /** iconPath or iconClass required */ - iconClass?: string; - tooltip?: string; - /** - * Whether to always show the button. By default buttons - * are only visible when hovering over them with the mouse - */ - alwaysVisible?: boolean; -} - -export interface IQuickPickItemButtonEvent<T extends IQuickPickItem> { - button: IQuickInputButton; - item: T; -} - -export interface IQuickPickItemButtonContext<T extends IQuickPickItem> extends IQuickPickItemButtonEvent<T> { - removeItem(): void; -} - -export type QuickPickInput<T = IQuickPickItem> = T | IQuickPickSeparator; - - -//#region Fuzzy Scorer Support - -export type IQuickPickItemWithResource = IQuickPickItem & { resource?: URI }; - -export class QuickPickItemScorerAccessor implements IItemAccessor<IQuickPickItemWithResource> { - - constructor(private options?: { skipDescription?: boolean; skipPath?: boolean }) { } - - getItemLabel(entry: IQuickPickItemWithResource): string { - return entry.label; - } - - getItemDescription(entry: IQuickPickItemWithResource): string | undefined { - if (this.options?.skipDescription) { - return undefined; - } - - return entry.description; - } - - getItemPath(entry: IQuickPickItemWithResource): string | undefined { - if (this.options?.skipPath) { - return undefined; - } - - if (entry.resource?.scheme === Schemas.file) { - return entry.resource.fsPath; - } - - return entry.resource?.path; - } -} - -export const quickPickItemScorerAccessor = new QuickPickItemScorerAccessor(); - -//#endregion diff --git a/src/vs/base/parts/request/browser/request.ts b/src/vs/base/parts/request/browser/request.ts index 7184c4f65f..9084a4782d 100644 --- a/src/vs/base/parts/request/browser/request.ts +++ b/src/vs/base/parts/request/browser/request.ts @@ -9,10 +9,6 @@ import { canceled } from 'vs/base/common/errors'; import { IRequestContext, IRequestOptions, OfflineError } from 'vs/base/parts/request/common/request'; export function request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> { - if (!navigator.onLine) { - throw new OfflineError(); - } - if (options.proxyAuthorization) { options.headers = { ...(options.headers || {}), @@ -27,7 +23,9 @@ export function request(options: IRequestOptions, token: CancellationToken): Pro setRequestHeaders(xhr, options); xhr.responseType = 'arraybuffer'; - xhr.onerror = e => reject(new Error(xhr.statusText && ('XHR failed: ' + xhr.statusText) || 'XHR failed')); + xhr.onerror = e => reject( + navigator.onLine ? new Error(xhr.statusText && ('XHR failed: ' + xhr.statusText) || 'XHR failed') : new OfflineError() + ); xhr.onload = (e) => { resolve({ res: { diff --git a/src/vs/base/parts/sandbox/common/electronTypes.ts b/src/vs/base/parts/sandbox/common/electronTypes.ts index 494e340c28..24d4898d00 100644 --- a/src/vs/base/parts/sandbox/common/electronTypes.ts +++ b/src/vs/base/parts/sandbox/common/electronTypes.ts @@ -232,7 +232,7 @@ export interface OpenDevToolsOptions { activate?: boolean; } -export interface InputEvent { +interface InputEvent { // Docs: https://electronjs.org/docs/api/structures/input-event diff --git a/src/vs/base/parts/sandbox/common/sandboxTypes.ts b/src/vs/base/parts/sandbox/common/sandboxTypes.ts index fada4fa3ce..b5b991b231 100644 --- a/src/vs/base/parts/sandbox/common/sandboxTypes.ts +++ b/src/vs/base/parts/sandbox/common/sandboxTypes.ts @@ -26,7 +26,11 @@ export interface ISandboxConfiguration { windowId: number; /** - * Absolute installation path. + * Root path of the JavaScript sources. + * + * Note: This is NOT the installation root + * directory itself but contained in it at + * a level that is platform dependent. */ appRoot: string; diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 89567fc947..784744df14 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -8,7 +8,7 @@ import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes import { IpcRenderer, ProcessMemoryInfo, WebFrame } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; /** - * In sandboxed renderers we cannot expose all of the `process` global of node.js + * In Electron renderers we cannot expose all of the `process` global of node.js */ export interface ISandboxNodeProcess extends INodeProcess { diff --git a/src/vs/base/parts/sandbox/electron-browser/preload.js b/src/vs/base/parts/sandbox/electron-sandbox/preload.js similarity index 94% rename from src/vs/base/parts/sandbox/electron-browser/preload.js rename to src/vs/base/parts/sandbox/electron-sandbox/preload.js index 4596427566..00bcf225a0 100644 --- a/src/vs/base/parts/sandbox/electron-browser/preload.js +++ b/src/vs/base/parts/sandbox/electron-sandbox/preload.js @@ -116,7 +116,7 @@ // ####################################################################### /** - * @type {import('../electron-sandbox/globals')} + * @type {import('./globals')} */ const globals = { @@ -124,7 +124,7 @@ * A minimal set of methods exposed from Electron's `ipcRenderer` * to support communication to main process. * - * @typedef {import('../electron-sandbox/electronTypes').IpcRenderer} IpcRenderer + * @typedef {import('./electronTypes').IpcRenderer} IpcRenderer * @typedef {import('electron').IpcRendererEvent} IpcRendererEvent * * @type {IpcRenderer} @@ -194,7 +194,7 @@ }, /** - * @type {import('../electron-sandbox/globals').IpcMessagePort} + * @type {import('./globals').IpcMessagePort} */ ipcMessagePort: { @@ -224,7 +224,7 @@ /** * Support for subset of methods of Electron's `webFrame` type. * - * @type {import('../electron-sandbox/electronTypes').WebFrame} + * @type {import('./electronTypes').WebFrame} */ webFrame: { @@ -244,7 +244,7 @@ * Note: when `sandbox` is enabled, the only properties available * are https://github.com/electron/electron/blob/master/docs/api/process.md#sandbox * - * @typedef {import('../electron-sandbox/globals').ISandboxNodeProcess} ISandboxNodeProcess + * @typedef {import('./globals').ISandboxNodeProcess} ISandboxNodeProcess * * @type {ISandboxNodeProcess} */ @@ -252,11 +252,9 @@ get platform() { return process.platform; }, get arch() { return process.arch; }, get env() { return { ...process.env }; }, - get pid() { return process.pid; }, get versions() { return process.versions; }, get type() { return 'renderer'; }, get execPath() { return process.execPath; }, - get sandboxed() { return process.sandboxed; }, /** * @returns {string} @@ -293,7 +291,7 @@ /** * Some information about the context we are running in. * - * @type {import('../electron-sandbox/globals').ISandboxContext} + * @type {import('./globals').ISandboxContext} */ context: { diff --git a/src/vs/base/parts/sandbox/node/electronTypes.ts b/src/vs/base/parts/sandbox/node/electronTypes.ts new file mode 100644 index 0000000000..6d504c81dd --- /dev/null +++ b/src/vs/base/parts/sandbox/node/electronTypes.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface MessagePortMain extends NodeJS.EventEmitter { + + // Docs: https://electronjs.org/docs/api/message-port-main + + /** + * Emitted when the remote end of a MessagePortMain object becomes disconnected. + */ + on(event: 'close', listener: Function): this; + once(event: 'close', listener: Function): this; + addListener(event: 'close', listener: Function): this; + removeListener(event: 'close', listener: Function): this; + /** + * Emitted when a MessagePortMain object receives a message. + */ + on(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + once(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + addListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + removeListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + /** + * Disconnects the port, so it is no longer active. + */ + close(): void; + /** + * Sends a message from the port, and optionally, transfers ownership of objects to + * other browsing contexts. + */ + postMessage(message: any, transfer?: MessagePortMain[]): void; + /** + * Starts the sending of messages queued on the port. Messages will be queued until + * this method is called. + */ + start(): void; +} + +export interface MessageEvent { + data: any; + ports: MessagePortMain[]; +} + +export interface ParentPort extends NodeJS.EventEmitter { + + // Docs: https://electronjs.org/docs/api/parent-port + + /** + * Emitted when the process receives a message. Messages received on this port will + * be queued up until a handler is registered for this event. + */ + on(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + once(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + addListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + removeListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this; + /** + * Sends a message from the process to its parent. + */ + postMessage(message: any): void; +} + +export interface UtilityNodeJSProcess extends NodeJS.Process { + + /** + * A `Electron.ParentPort` property if this is a `UtilityProcess` (or `null` + * otherwise) allowing communication with the parent process. + */ + parentPort: ParentPort; +} + +export function isUtilityProcess(process: NodeJS.Process): process is UtilityNodeJSProcess { + return !!(process as UtilityNodeJSProcess).parentPort; +} diff --git a/src/vs/base/parts/storage/common/storage.ts b/src/vs/base/parts/storage/common/storage.ts index ab30b16ddf..eaa78c1f76 100644 --- a/src/vs/base/parts/storage/common/storage.ts +++ b/src/vs/base/parts/storage/common/storage.ts @@ -6,7 +6,8 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { isUndefinedOrNull } from 'vs/base/common/types'; +import { parse, stringify } from 'vs/base/common/marshalling'; +import { isObject, isUndefinedOrNull } from 'vs/base/common/types'; export enum StorageHint { @@ -14,7 +15,11 @@ export enum StorageHint { // does not exist on disk yet. This allows // the storage library to improve startup // time by not checking the storage for data. - STORAGE_DOES_NOT_EXIST + STORAGE_DOES_NOT_EXIST, + + // A hint to the storage that the storage + // is backed by an in-memory storage. + STORAGE_IN_MEMORY } export interface IStorageOptions { @@ -65,7 +70,10 @@ export interface IStorage extends IDisposable { getNumber(key: string, fallbackValue: number): number; getNumber(key: string, fallbackValue?: number): number | undefined; - set(key: string, value: string | boolean | number | undefined | null): Promise<void>; + getObject<T extends object>(key: string, fallbackValue: T): T; + getObject<T extends object>(key: string, fallbackValue?: T): T | undefined; + + set(key: string, value: string | boolean | number | undefined | null | object): Promise<void>; delete(key: string): Promise<void>; flush(delay?: number): Promise<void>; @@ -209,7 +217,19 @@ export class Storage extends Disposable implements IStorage { return parseInt(value, 10); } - async set(key: string, value: string | boolean | number | null | undefined): Promise<void> { + getObject(key: string, fallbackValue: object): object; + getObject(key: string, fallbackValue?: object | undefined): object | undefined; + getObject(key: string, fallbackValue?: object): object | undefined { + const value = this.get(key); + + if (isUndefinedOrNull(value)) { + return fallbackValue; + } + + return parse(value); + } + + async set(key: string, value: string | boolean | number | null | undefined | object): Promise<void> { if (this.state === StorageState.Closed) { return; // Return early if we are already closed } @@ -220,7 +240,7 @@ export class Storage extends Disposable implements IStorage { } // Otherwise, convert to String and store - const valueStr = String(value); + const valueStr = isObject(value) || Array.isArray(value) ? stringify(value) : String(value); // Return early if value already set const currentValue = this.cache.get(key); @@ -339,6 +359,10 @@ export class Storage extends Disposable implements IStorage { return new Promise(resolve => this.whenFlushedCallbacks.push(resolve)); } + isInMemory(): boolean { + return this.options.hint === StorageHint.STORAGE_IN_MEMORY; + } + override dispose(): void { this.flushDelayer.dispose(); diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.integrationTest.ts similarity index 97% rename from src/vs/base/parts/storage/test/node/storage.test.ts rename to src/vs/base/parts/storage/test/node/storage.integrationTest.ts index 68a304ed5f..097ff42577 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.integrationTest.ts @@ -3,12 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ok, strictEqual } from 'assert'; +import { deepStrictEqual, ok, strictEqual } from 'assert'; import { tmpdir } from 'os'; import { timeout } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { join } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { Promises } from 'vs/base/node/pfs'; import { isStorageItemsChangeEvent, IStorageDatabase, IStorageItemsChangeEvent, Storage } from 'vs/base/parts/storage/common/storage'; @@ -30,6 +31,21 @@ flakySuite('Storage Library', function () { return Promises.rm(testDir); }); + test('objects', () => { + return runWithFakedTimers({}, async function () { + const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db'))); + + await storage.init(); + + ok(!storage.getObject('foo')); + const uri = URI.file('path/to/folder'); + storage.set('foo', { 'bar': uri }); + deepStrictEqual(storage.getObject('foo'), { 'bar': uri }); + + await storage.close(); + }); + }); + test('basics', () => { return runWithFakedTimers({}, async function () { const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db'))); @@ -40,6 +56,7 @@ flakySuite('Storage Library', function () { strictEqual(storage.get('foo', 'bar'), 'bar'); strictEqual(storage.getNumber('foo', 55), 55); strictEqual(storage.getBoolean('foo', true), true); + deepStrictEqual(storage.getObject('foo', { 'bar': 'baz' }), { 'bar': 'baz' }); let changes = new Set<string>(); storage.onDidChangeStorage(key => { @@ -52,6 +69,7 @@ flakySuite('Storage Library', function () { const set1Promise = storage.set('bar', 'foo'); const set2Promise = storage.set('barNumber', 55); const set3Promise = storage.set('barBoolean', true); + const set4Promise = storage.set('barObject', { 'bar': 'baz' }); let flushPromiseResolved = false; storage.whenFlushed().then(() => flushPromiseResolved = true); @@ -59,14 +77,16 @@ flakySuite('Storage Library', function () { strictEqual(storage.get('bar'), 'foo'); strictEqual(storage.getNumber('barNumber'), 55); strictEqual(storage.getBoolean('barBoolean'), true); + deepStrictEqual(storage.getObject('barObject'), { 'bar': 'baz' }); - strictEqual(changes.size, 3); + strictEqual(changes.size, 4); ok(changes.has('bar')); ok(changes.has('barNumber')); ok(changes.has('barBoolean')); + ok(changes.has('barObject')); let setPromiseResolved = false; - await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true); + await Promise.all([set1Promise, set2Promise, set3Promise, set4Promise]).then(() => setPromiseResolved = true); strictEqual(setPromiseResolved, true); strictEqual(flushPromiseResolved, true); @@ -76,21 +96,25 @@ flakySuite('Storage Library', function () { storage.set('bar', 'foo'); storage.set('barNumber', 55); storage.set('barBoolean', true); + storage.set('barObject', { 'bar': 'baz' }); strictEqual(changes.size, 0); // Simple deletes const delete1Promise = storage.delete('bar'); const delete2Promise = storage.delete('barNumber'); const delete3Promise = storage.delete('barBoolean'); + const delete4Promise = storage.delete('barObject'); ok(!storage.get('bar')); ok(!storage.getNumber('barNumber')); ok(!storage.getBoolean('barBoolean')); + ok(!storage.getObject('barObject')); - strictEqual(changes.size, 3); + strictEqual(changes.size, 4); ok(changes.has('bar')); ok(changes.has('barNumber')); ok(changes.has('barBoolean')); + ok(changes.has('barObject')); changes = new Set<string>(); @@ -98,10 +122,11 @@ flakySuite('Storage Library', function () { storage.delete('bar'); storage.delete('barNumber'); storage.delete('barBoolean'); + storage.delete('barObject'); strictEqual(changes.size, 0); let deletePromiseResolved = false; - await Promise.all([delete1Promise, delete2Promise, delete3Promise]).then(() => deletePromiseResolved = true); + await Promise.all([delete1Promise, delete2Promise, delete3Promise, delete4Promise]).then(() => deletePromiseResolved = true); strictEqual(deletePromiseResolved, true); await storage.close(); diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts index 5296b529a8..374a2a8b30 100644 --- a/src/vs/base/test/browser/dom.test.ts +++ b/src/vs/base/test/browser/dom.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { $, h, multibyteAwareBtoa } from 'vs/base/browser/dom'; +import { $, asCssValueWithDefault, h, multibyteAwareBtoa } from 'vs/base/browser/dom'; suite('dom', () => { test.skip('hasClass', () => { // {{SQL CARBON EDIT}} skip test @@ -18,6 +18,8 @@ suite('dom', () => { assert(!element.classList.contains('bar')); assert(!element.classList.contains('foo')); assert(!element.classList.contains('')); + + }); test.skip('removeClass', () => { // {{SQL CARBON EDIT}} skip test @@ -276,4 +278,14 @@ suite('dom', () => { assert.strictEqual(result.editor.className, ''); assert.strictEqual(result.editor.childElementCount, 0); }); + + test('cssValueWithDefault', () => { + assert.strictEqual(asCssValueWithDefault('red', 'blue'), 'red'); + assert.strictEqual(asCssValueWithDefault(undefined, 'blue'), 'blue'); + assert.strictEqual(asCssValueWithDefault('var(--my-var)', 'blue'), 'var(--my-var, blue)'); + assert.strictEqual(asCssValueWithDefault('var(--my-var, red)', 'blue'), 'var(--my-var, red)'); + assert.strictEqual(asCssValueWithDefault('var(--my-var, var(--my-var2))', 'blue'), 'var(--my-var, var(--my-var2, blue))'); + }); + + }); diff --git a/src/vs/base/test/browser/highlightedLabel.test.ts b/src/vs/base/test/browser/highlightedLabel.test.ts index a83cf57fa3..5ac935d32d 100644 --- a/src/vs/base/test/browser/highlightedLabel.test.ts +++ b/src/vs/base/test/browser/highlightedLabel.test.ts @@ -19,12 +19,12 @@ suite('HighlightedLabel', () => { test('no decorations', function () { label.set('hello'); - assert.strictEqual(label.element.innerHTML, '<span>hello</span>'); + assert.strictEqual(label.element.innerHTML, 'hello'); }); test('escape html', function () { label.set('hel<lo'); - assert.strictEqual(label.element.innerHTML, '<span>hel<lo</span>'); + assert.strictEqual(label.element.innerHTML, 'hel<lo'); }); test('everything highlighted', function () { @@ -34,17 +34,17 @@ suite('HighlightedLabel', () => { test('beginning highlighted', function () { label.set('hellothere', [{ start: 0, end: 5 }]); - assert.strictEqual(label.element.innerHTML, '<span class="highlight">hello</span><span>there</span>'); + assert.strictEqual(label.element.innerHTML, '<span class="highlight">hello</span>there'); }); test('ending highlighted', function () { label.set('goodbye', [{ start: 4, end: 7 }]); - assert.strictEqual(label.element.innerHTML, '<span>good</span><span class="highlight">bye</span>'); + assert.strictEqual(label.element.innerHTML, 'good<span class="highlight">bye</span>'); }); test('middle highlighted', function () { label.set('foobarfoo', [{ start: 3, end: 6 }]); - assert.strictEqual(label.element.innerHTML, '<span>foo</span><span class="highlight">bar</span><span>foo</span>'); + assert.strictEqual(label.element.innerHTML, 'foo<span class="highlight">bar</span>foo'); }); test('escapeNewLines', () => { diff --git a/src/vs/base/test/browser/indexedDB.test.ts b/src/vs/base/test/browser/indexedDB.test.ts index def523d8fe..512343453f 100644 --- a/src/vs/base/test/browser/indexedDB.test.ts +++ b/src/vs/base/test/browser/indexedDB.test.ts @@ -16,9 +16,7 @@ flakySuite('IndexedDB', () => { }); teardown(() => { - if (indexedDB) { - indexedDB.close(); - } + indexedDB?.close(); }); test('runInTransaction', async () => { diff --git a/src/vs/base/test/browser/markdownRenderer.test.ts b/src/vs/base/test/browser/markdownRenderer.test.ts index 2cb9d63d33..34131881c6 100644 --- a/src/vs/base/test/browser/markdownRenderer.test.ts +++ b/src/vs/base/test/browser/markdownRenderer.test.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { renderMarkdown, renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; +import { fillInIncompleteTokens, renderMarkdown, renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; +import { marked } from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; import { isWeb } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -68,7 +69,7 @@ suite('MarkdownRenderer', () => { }); suite('Code block renderer', () => { - const simpleCodeBlockRenderer = (code: string): Promise<HTMLElement> => { + const simpleCodeBlockRenderer = (lang: string, code: string): Promise<HTMLElement> => { const element = document.createElement('code'); element.textContent = code; return Promise.resolve(element); @@ -115,6 +116,19 @@ suite('MarkdownRenderer', () => { }, 50); }); }); + + test('Code blocks should use leading language id (#157793)', async () => { + const markdown = { value: '```js some other stuff\n1 + 1;\n```' }; + const lang = await new Promise<string>(resolve => { + renderMarkdown(markdown, { + codeBlockRenderer: async (lang, value) => { + resolve(lang); + return simpleCodeBlockRenderer(lang, value); + } + }); + }); + assert.strictEqual(lang, 'js'); + }); }); suite('ThemeIcons Support On', () => { @@ -312,4 +326,386 @@ suite('MarkdownRenderer', () => { assert.strictEqual(result.innerHTML, `<img src="vscode-file://vscode-app/images/cat.gif">`); }); }); + + suite('fillInIncompleteTokens', () => { + function ignoreRaw(...tokenLists: marked.Token[][]): void { + tokenLists.forEach(tokens => { + tokens.forEach(t => t.raw = ''); + }); + } + + const completeTable = '| a | b |\n| --- | --- |'; + + suite('table', () => { + test('complete table', () => { + const tokens = marked.lexer(completeTable); + const newTokens = fillInIncompleteTokens(tokens); + assert.equal(newTokens, tokens); + }); + + test('full header only', () => { + const incompleteTable = '| a | b |'; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(completeTable); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('full header only with trailing space', () => { + const incompleteTable = '| a | b | '; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(completeTable); + + const newTokens = fillInIncompleteTokens(tokens); + ignoreRaw(newTokens, completeTableTokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('incomplete header', () => { + const incompleteTable = '| a | b'; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(completeTable); + + const newTokens = fillInIncompleteTokens(tokens); + + ignoreRaw(newTokens, completeTableTokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('incomplete header one column', () => { + const incompleteTable = '| a '; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(incompleteTable + '|\n| --- |'); + + const newTokens = fillInIncompleteTokens(tokens); + + ignoreRaw(newTokens, completeTableTokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('full header with extras', () => { + const incompleteTable = '| a **bold** | b _italics_ |'; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(incompleteTable + '\n| --- | --- |'); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('full header with leading text', () => { + // Parsing this gives one token and one 'text' subtoken + const incompleteTable = 'here is a table\n| a | b |'; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(incompleteTable + '\n| --- | --- |'); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('full header with leading other stuff', () => { + // Parsing this gives one token and one 'text' subtoken + const incompleteTable = '```js\nconst xyz = 123;\n```\n| a | b |'; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(incompleteTable + '\n| --- | --- |'); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('full header with incomplete separator', () => { + const incompleteTable = '| a | b |\n| ---'; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(completeTable); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('full header with incomplete separator 2', () => { + const incompleteTable = '| a | b |\n| --- |'; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(completeTable); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('full header with incomplete separator 3', () => { + const incompleteTable = '| a | b |\n|'; + const tokens = marked.lexer(incompleteTable); + const completeTableTokens = marked.lexer(completeTable); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, completeTableTokens); + }); + + test('not a table', () => { + const incompleteTable = '| a | b |\nsome text'; + const tokens = marked.lexer(incompleteTable); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, tokens); + }); + + test('not a table 2', () => { + const incompleteTable = '| a | b |\n| --- |\nsome text'; + const tokens = marked.lexer(incompleteTable); + + const newTokens = fillInIncompleteTokens(tokens); + assert.deepStrictEqual(newTokens, tokens); + }); + }); + + suite('codeblock', () => { + test('complete code block', () => { + const completeCodeblock = '```js\nconst xyz = 123;\n```'; + const tokens = marked.lexer(completeCodeblock); + const newTokens = fillInIncompleteTokens(tokens); + assert.equal(newTokens, tokens); + }); + + test('code block header only', () => { + const incompleteCodeblock = '```js'; + const tokens = marked.lexer(incompleteCodeblock); + const newTokens = fillInIncompleteTokens(tokens); + + const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); + assert.deepStrictEqual(newTokens, completeCodeblockTokens); + }); + + test('code block header no lang', () => { + const incompleteCodeblock = '```'; + const tokens = marked.lexer(incompleteCodeblock); + const newTokens = fillInIncompleteTokens(tokens); + + const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); + assert.deepStrictEqual(newTokens, completeCodeblockTokens); + }); + + test('code block header and some code', () => { + const incompleteCodeblock = '```js\nconst'; + const tokens = marked.lexer(incompleteCodeblock); + const newTokens = fillInIncompleteTokens(tokens); + + const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); + assert.deepStrictEqual(newTokens, completeCodeblockTokens); + }); + + test('code block header with leading text', () => { + const incompleteCodeblock = 'some text\n```js'; + const tokens = marked.lexer(incompleteCodeblock); + const newTokens = fillInIncompleteTokens(tokens); + + const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); + assert.deepStrictEqual(newTokens, completeCodeblockTokens); + }); + + test('code block header with leading text and some code', () => { + const incompleteCodeblock = 'some text\n```js\nconst'; + const tokens = marked.lexer(incompleteCodeblock); + const newTokens = fillInIncompleteTokens(tokens); + + const completeCodeblockTokens = marked.lexer(incompleteCodeblock + '\n```'); + assert.deepStrictEqual(newTokens, completeCodeblockTokens); + }); + }); + + function simpleMarkdownTestSuite(name: string, delimiter: string): void { + test(`incomplete ${name}`, () => { + const incomplete = `${delimiter}code`; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + delimiter); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test(`complete ${name}`, () => { + const text = `leading text ${delimiter}code${delimiter} trailing text`; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + assert.deepStrictEqual(newTokens, tokens); + }); + + test(`${name} with leading text`, () => { + const incomplete = `some text and ${delimiter}some code`; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + delimiter); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test(`single loose "${delimiter}"`, () => { + const text = `some text and ${delimiter}by itself\nmore text here`; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + assert.deepStrictEqual(newTokens, tokens); + }); + + test(`incomplete ${name} after newline`, () => { + const text = `some text\nmore text here and ${delimiter}text`; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(text + delimiter); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test(`incomplete after complete ${name}`, () => { + const text = `leading text ${delimiter}code${delimiter} trailing text and ${delimiter}another`; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(text + delimiter); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test.skip(`incomplete ${name} in list`, () => { + const text = `- list item one\n- list item two and ${delimiter}text`; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(text + delimiter); + assert.deepStrictEqual(newTokens, completeTokens); + }); + } + + suite('codespan', () => { + simpleMarkdownTestSuite('codespan', '`'); + + test(`backtick between letters`, () => { + const text = 'a`b'; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeCodespanTokens = marked.lexer(text + '`'); + assert.deepStrictEqual(newTokens, completeCodespanTokens); + }); + + test(`nested pattern`, () => { + const text = 'sldkfjsd `abc __def__ ghi'; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(text + '`'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + }); + + suite('star', () => { + simpleMarkdownTestSuite('star', '*'); + + test(`star between letters`, () => { + const text = 'sldkfjsd a*b'; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(text + '*'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test(`nested pattern`, () => { + const text = 'sldkfjsd *abc __def__ ghi'; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(text + '*'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + }); + + suite('double star', () => { + simpleMarkdownTestSuite('double star', '**'); + + test(`double star between letters`, () => { + const text = 'a**b'; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(text + '**'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + }); + + suite('underscore', () => { + simpleMarkdownTestSuite('underscore', '_'); + + test(`underscore between letters`, () => { + const text = `this_not_italics`; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + assert.deepStrictEqual(newTokens, tokens); + }); + }); + + suite('double underscore', () => { + simpleMarkdownTestSuite('double underscore', '__'); + + test(`double underscore between letters`, () => { + const text = `this__not__bold`; + const tokens = marked.lexer(text); + const newTokens = fillInIncompleteTokens(tokens); + + assert.deepStrictEqual(newTokens, tokens); + }); + }); + + suite('link', () => { + test('incomplete link text', () => { + const incomplete = 'abc [text'; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + '](about:blank)'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test('incomplete link target', () => { + const incomplete = 'foo [text](http://microsoft'; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + ')'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test.skip('incomplete link in list', () => { + const incomplete = '- [text'; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + const completeTokens = marked.lexer(incomplete + '](about:blank)'); + assert.deepStrictEqual(newTokens, completeTokens); + }); + + test('square brace between letters', () => { + const incomplete = 'a[b'; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + assert.deepStrictEqual(newTokens, tokens); + }); + + test('square brace on previous line', () => { + const incomplete = 'text[\nmore text'; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + assert.deepStrictEqual(newTokens, tokens); + }); + + test('complete link', () => { + const incomplete = 'text [link](http://microsoft.com)'; + const tokens = marked.lexer(incomplete); + const newTokens = fillInIncompleteTokens(tokens); + + assert.deepStrictEqual(newTokens, tokens); + }); + }); + }); }); diff --git a/src/vs/base/test/browser/ui/grid/gridview.test.ts b/src/vs/base/test/browser/ui/grid/gridview.test.ts index 111b1e610b..0da56173c4 100644 --- a/src/vs/base/test/browser/ui/grid/gridview.test.ts +++ b/src/vs/base/test/browser/ui/grid/gridview.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { $ } from 'vs/base/browser/dom'; -import { GridView, IView, Sizing } from 'vs/base/browser/ui/grid/gridview'; +import { GridView, IView, Orientation, Sizing } from 'vs/base/browser/ui/grid/gridview'; import { nodesToArrays, TestView } from './util'; suite('Gridview', function () { @@ -222,4 +222,22 @@ suite('Gridview', function () { assert.deepStrictEqual(view2.size, [400, 300]); assert.deepStrictEqual(view3.size, [400, 300]); }); + + test('flipping orientation should preserve absolute offsets', function () { + const view1 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + gridview.addView(view1, 200, [0]); + + const view2 = new TestView(50, Number.POSITIVE_INFINITY, 50, Number.POSITIVE_INFINITY); + gridview.addView(view2, 200, [1]); + + gridview.layout(800, 600, 100, 200); + + assert.deepStrictEqual([view1.top, view1.left], [100, 200]); + assert.deepStrictEqual([view2.top, view2.left], [100 + 300, 200]); + + gridview.orientation = Orientation.HORIZONTAL; + + assert.deepStrictEqual([view1.top, view1.left], [100, 200]); + assert.deepStrictEqual([view2.top, view2.left], [100, 200 + 400]); + }); }); diff --git a/src/vs/base/test/browser/ui/grid/util.ts b/src/vs/base/test/browser/ui/grid/util.ts index 780e83b24f..0f844af15c 100644 --- a/src/vs/base/test/browser/ui/grid/util.ts +++ b/src/vs/base/test/browser/ui/grid/util.ts @@ -37,10 +37,16 @@ export class TestView implements IView { private _height = 0; get height(): number { return this._height; } + private _top = 0; + get top(): number { return this._top; } + + private _left = 0; + get left(): number { return this._left; } + get size(): [number, number] { return [this.width, this.height]; } - private readonly _onDidLayout = new Emitter<{ width: number; height: number }>(); - readonly onDidLayout: Event<{ width: number; height: number }> = this._onDidLayout.event; + private readonly _onDidLayout = new Emitter<{ width: number; height: number; top: number; left: number }>(); + readonly onDidLayout: Event<{ width: number; height: number; top: number; left: number }> = this._onDidLayout.event; private readonly _onDidFocus = new Emitter<void>(); readonly onDidFocus: Event<void> = this._onDidFocus.event; @@ -55,10 +61,12 @@ export class TestView implements IView { assert(_minimumHeight <= _maximumHeight, 'gridview view minimum height must be <= maximum height'); } - layout(width: number, height: number): void { + layout(width: number, height: number, top: number, left: number): void { this._width = width; this._height = height; - this._onDidLayout.fire({ width, height }); + this._top = top; + this._left = left; + this._onDidLayout.fire({ width, height, top, left }); } focus(): void { diff --git a/src/vs/base/test/browser/ui/menu/menubar.test.ts b/src/vs/base/test/browser/ui/menu/menubar.test.ts index 6eb30cb9f6..4f45654664 100644 --- a/src/vs/base/test/browser/ui/menu/menubar.test.ts +++ b/src/vs/base/test/browser/ui/menu/menubar.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { $ } from 'vs/base/browser/dom'; +import { unthemedMenuStyles } from 'vs/base/browser/ui/menu/menu'; import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; function getButtonElementByAriaLabel(menubarElement: HTMLElement, ariaLabel: string): HTMLElement | null { @@ -65,7 +66,7 @@ suite('Menubar', () => { const menubar = new MenuBar(container, { enableMnemonics: true, visibility: 'visible' - }); + }, unthemedMenuStyles); test('English File menu renders mnemonics', function () { validateMenuBarItem(menubar, container, '&File', 'File', 'F'); 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 cf269fbfb5..3cc64195b5 100644 --- a/src/vs/base/test/browser/ui/splitview/splitview.test.ts +++ b/src/vs/base/test/browser/ui/splitview/splitview.test.ts @@ -62,7 +62,7 @@ class TestView implements IView<number> { } function getSashes(splitview: SplitView): Sash[] { - return (splitview as any).sashItems.map((i: any) => i.sash) as Sash[]; + return splitview.sashItems.map((i: any) => i.sash) as Sash[]; } suite('Splitview', () => { @@ -336,7 +336,7 @@ suite('Splitview', () => { const viewContainers = container.querySelectorAll('.split-view-view'); assert.strictEqual(viewContainers.length, 2, 'there are two view containers'); assert.strictEqual((viewContainers.item(0) as HTMLElement).style.height, '66px', 'second view container is 66px'); - assert.strictEqual((viewContainers.item(1) as HTMLElement).style.height, `${986 - 66}px`, 'first view container is 66px'); + assert.strictEqual<string>((viewContainers.item(1) as HTMLElement).style.height, `${986 - 66}px`, 'first view container is 66px'); splitview.dispose(); view2.dispose(); diff --git a/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts index fb545cb8ce..0e35e19f14 100644 --- a/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/compressedObjectTreeModel.test.ts @@ -17,7 +17,7 @@ interface IResolvedCompressedTreeElement<T> extends ICompressedTreeElement<T> { function resolve<T>(treeElement: ICompressedTreeElement<T>): IResolvedCompressedTreeElement<T> { const result: any = { element: treeElement.element }; - const children = [...Iterable.map(Iterable.from(treeElement.children), resolve)]; + const children = Array.from(Iterable.from(treeElement.children), resolve); if (treeElement.incompressible) { result.incompressible = true; diff --git a/src/vs/base/test/browser/ui/tree/dataTree.test.ts b/src/vs/base/test/browser/ui/tree/dataTree.test.ts index 154c828537..8e0549bc8f 100644 --- a/src/vs/base/test/browser/ui/tree/dataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/dataTree.test.ts @@ -63,9 +63,7 @@ suite('DataTree', function () { } }; - tree = new DataTree<E, E>('test', container, delegate, [renderer], dataSource, { - identityProvider - }); + tree = new DataTree<E, E>('test', container, delegate, [renderer], dataSource, { identityProvider }); tree.layout(200); }); diff --git a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts index e2f6b6b9d0..69de867214 100644 --- a/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/indexTreeModel.test.ts @@ -358,7 +358,7 @@ suite('IndexTreeModel', () => { assert.deepStrictEqual(list.length, 3); - model.setCollapsed([0], false); + model.expandTo([0, 1]); assert.deepStrictEqual(list.length, 6); assert.deepStrictEqual(list[0].element, 0); assert.deepStrictEqual(list[0].collapsed, false); diff --git a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts index 951d781f1e..4ed86ce986 100644 --- a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { ObjectTreeModel } from 'vs/base/browser/ui/tree/objectTreeModel'; -import { ITreeFilter, ITreeNode, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; +import { ITreeFilter, ITreeNode, ObjectTreeElementCollapseState, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; import { timeout } from 'vs/base/common/async'; function toList<T>(arr: T[]): IList<T> { @@ -171,6 +171,27 @@ suite('ObjectTreeModel', function () { assert.deepStrictEqual(toArray(list), ['father']); }); + test('collapse state can be optionally preserved with strict identity', () => { + const list: ITreeNode<string>[] = []; + const model = new ObjectTreeModel<string>('test', toList(list), { collapseByDefault: true }); + const data = [{ element: 'father', collapsed: ObjectTreeElementCollapseState.PreserveOrExpanded, children: [{ element: 'child' }] }]; + + model.setChildren(null, data); + assert.deepStrictEqual(toArray(list), ['father', 'child']); + + model.setCollapsed('father', true); + assert.deepStrictEqual(toArray(list), ['father']); + + model.setChildren(null, data); + assert.deepStrictEqual(toArray(list), ['father']); + + model.setCollapsed('father', false); + assert.deepStrictEqual(toArray(list), ['father', 'child']); + + model.setChildren(null, data); + assert.deepStrictEqual(toArray(list), ['father', 'child']); + }); + test('sorter', () => { const compare: (a: string, b: string) => number = (a, b) => a < b ? -1 : 1; diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index 8d442e813f..93b84a211e 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -404,7 +404,7 @@ suite('Arrays', () => { assert.deepStrictEqual(queue1.takeWhile(x => true), [7, 6]); }); - test('TakeWhile 1', () => { + test('TakeFromEndWhile 1', () => { const queue1 = new arrays.ArrayQueue([9, 8, 1, 7, 6]); assert.deepStrictEqual(queue1.takeFromEndWhile(x => x > 5), [7, 6]); assert.deepStrictEqual(queue1.takeFromEndWhile(x => x < 2), [1]); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index ab22911bba..6e7d63d530 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -5,10 +5,12 @@ import * as assert from 'assert'; import * as async from 'vs/base/common/async'; +import * as MicrotaskDelay from "vs/base/common/symbols"; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { isCancellationError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; suite('Async', () => { @@ -187,7 +189,7 @@ suite('Async', () => { return Promise.resolve(++count); }; - const delayer = new async.Delayer(async.MicrotaskDelay); + const delayer = new async.Delayer(MicrotaskDelay.MicrotaskDelay); const promises: Promise<any>[] = []; assert(!delayer.isTriggered()); @@ -250,7 +252,7 @@ suite('Async', () => { return Promise.resolve(++count); }; - const delayer = new async.Delayer(async.MicrotaskDelay); + const delayer = new async.Delayer(MicrotaskDelay.MicrotaskDelay); assert(!delayer.isTriggered()); @@ -393,51 +395,6 @@ suite('Async', () => { }); suite('Limiter', () => { - test('sync', function () { - const factoryFactory = (n: number) => () => { - return Promise.resolve(n); - }; - - let limiter = new async.Limiter(1); - - let promises: Promise<any>[] = []; - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); - - return Promise.all(promises).then((res) => { - assert.strictEqual(10, res.length); - - limiter = new async.Limiter(100); - - promises = []; - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); - - return Promise.all(promises).then((res) => { - assert.strictEqual(10, res.length); - }); - }); - }); - - test('async', function () { - const factoryFactory = (n: number) => () => async.timeout(0).then(() => n); - - let limiter = new async.Limiter(1); - let promises: Promise<any>[] = []; - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); - - return Promise.all(promises).then((res) => { - assert.strictEqual(10, res.length); - - limiter = new async.Limiter(100); - - promises = []; - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(n => promises.push(limiter.queue(factoryFactory(n)))); - - return Promise.all(promises).then((res) => { - assert.strictEqual(10, res.length); - }); - }); - }); - test('assert degree of paralellism', function () { let activePromises = 0; const factoryFactory = (n: number) => () => { @@ -636,29 +593,33 @@ suite('Async', () => { suite('retry', () => { test('success case', async () => { - let counter = 0; + return runWithFakedTimers({ useFakeTimers: true }, async () => { + let counter = 0; - const res = await async.retry(() => { - counter++; - if (counter < 2) { - return Promise.reject(new Error('fail')); - } + const res = await async.retry(() => { + counter++; + if (counter < 2) { + return Promise.reject(new Error('fail')); + } - return Promise.resolve(true); - }, 10, 3); + return Promise.resolve(true); + }, 10, 3); - assert.strictEqual(res, true); + assert.strictEqual(res, true); + }); }); test('error case', async () => { - const expectedError = new Error('fail'); - try { - await async.retry(() => { - return Promise.reject(expectedError); - }, 10, 3); - } catch (error) { - assert.strictEqual(error, error); - } + return runWithFakedTimers({ useFakeTimers: true }, async () => { + const expectedError = new Error('fail'); + try { + await async.retry(() => { + return Promise.reject(expectedError); + }, 10, 3); + } catch (error) { + assert.strictEqual(error, error); + } + }); }); }); @@ -667,6 +628,7 @@ suite('Async', () => { const sequentializer = new async.TaskSequentializer() as any; // {{SQL CARBON EDIT}} Cast as any to get around compilation issues with the type guards assert.ok(!sequentializer.hasPending()); + assert.ok(!sequentializer.hasNext()); assert.ok(!sequentializer.hasPending(2323)); assert.ok(!sequentializer.pending); @@ -675,11 +637,13 @@ suite('Async', () => { assert.ok(!sequentializer.hasPending()); assert.ok(!sequentializer.hasPending(1)); assert.ok(!sequentializer.pending); + assert.ok(!sequentializer.hasNext()); // pending removes itself after done (use async.timeout) sequentializer.setPending(2, async.timeout(1)); assert.ok(sequentializer.hasPending()); assert.ok(sequentializer.hasPending(2)); + assert.ok(!sequentializer.hasNext()); assert.strictEqual(sequentializer.hasPending(1), false); assert.ok(sequentializer.pending); @@ -699,9 +663,12 @@ suite('Async', () => { let nextDone = false; const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return; })); + assert.ok(sequentializer.hasNext()); + await res; assert.ok(pendingDone); assert.ok(nextDone); + assert.ok(!sequentializer.hasNext()); }); test('pending and next (finishes after timeout)', async function () { @@ -717,6 +684,42 @@ suite('Async', () => { await res; assert.ok(pendingDone); assert.ok(nextDone); + assert.ok(!sequentializer.hasNext()); + }); + + test('join (without next or pending)', async function () { + const sequentializer = new async.TaskSequentializer(); + + await sequentializer.join(); + assert.ok(!sequentializer.hasNext()); + }); + + test('join (without next)', async function () { + const sequentializer = new async.TaskSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); + + await sequentializer.join(); + assert.ok(pendingDone); + assert.ok(!sequentializer.hasPending()); + }); + + test('join (with next and pending)', async function () { + const sequentializer = new async.TaskSequentializer(); + + let pendingDone = false; + sequentializer.setPending(1, async.timeout(1).then(() => { pendingDone = true; return; })); + + // next finishes after async.timeout + let nextDone = false; + sequentializer.setNext(() => async.timeout(1).then(() => { nextDone = true; return; })); + + await sequentializer.join(); + assert.ok(pendingDone); + assert.ok(nextDone); + assert.ok(!sequentializer.hasPending()); + assert.ok(!sequentializer.hasNext()); }); test('pending and multiple next (last one wins)', async function () { diff --git a/src/vs/base/test/common/buffer.test.ts b/src/vs/base/test/common/buffer.test.ts index 08cb744384..f58f965da7 100644 --- a/src/vs/base/test/common/buffer.test.ts +++ b/src/vs/base/test/common/buffer.test.ts @@ -413,6 +413,22 @@ suite('Buffer', () => { } }); + test('indexOf', () => { + const haystack = VSBuffer.fromString('abcaabbccaaabbbccc'); + assert.strictEqual(haystack.indexOf(VSBuffer.fromString('')), 0); + assert.strictEqual(haystack.indexOf(VSBuffer.fromString('a'.repeat(100))), -1); + + assert.strictEqual(haystack.indexOf(VSBuffer.fromString('a')), 0); + assert.strictEqual(haystack.indexOf(VSBuffer.fromString('c')), 2); + + assert.strictEqual(haystack.indexOf(VSBuffer.fromString('abcaa')), 0); + assert.strictEqual(haystack.indexOf(VSBuffer.fromString('caaab')), 8); + assert.strictEqual(haystack.indexOf(VSBuffer.fromString('ccc')), 15); + + assert.strictEqual(haystack.indexOf(VSBuffer.fromString('cccb')), -1); + + }); + suite('base64', () => { /* Generated with: diff --git a/src/vs/base/test/common/cancellation.test.ts b/src/vs/base/test/common/cancellation.test.ts index 8d516aa2c9..68ab18244d 100644 --- a/src/vs/base/test/common/cancellation.test.ts +++ b/src/vs/base/test/common/cancellation.test.ts @@ -108,6 +108,12 @@ suite('CancellationToken', function () { assert.strictEqual(count, 1); }); + test('dispose does not cancel', function () { + const source = new CancellationTokenSource(); + source.dispose(); + assert.strictEqual(source.token.isCancellationRequested, false); + }); + test('parent cancels child', function () { const parent = new CancellationTokenSource(); diff --git a/src/vs/base/test/common/codicons.test.ts b/src/vs/base/test/common/codicons.test.ts deleted file mode 100644 index 192c855f2a..0000000000 --- a/src/vs/base/test/common/codicons.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { getCodiconAriaLabel } from 'vs/base/common/codicons'; - -suite('Codicon', () => { - test('Can get proper aria labels', () => { - // note, the spaces in the results are important - const testCases = new Map<string, string>([ - ['', ''], - ['asdf', 'asdf'], - ['asdf$(squirrel)asdf', 'asdf squirrel asdf'], - ['asdf $(squirrel) asdf', 'asdf squirrel asdf'], - ['$(rocket)asdf', 'rocket asdf'], - ['$(rocket) asdf', 'rocket asdf'], - ['$(rocket)$(rocket)$(rocket)asdf', 'rocket rocket rocket asdf'], - ['$(rocket) asdf $(rocket)', 'rocket asdf rocket'], - ['$(rocket)asdf$(rocket)', 'rocket asdf rocket'], - ]); - - for (const [input, expected] of testCases) { - assert.strictEqual(getCodiconAriaLabel(input), expected); - } - }); -}); diff --git a/src/vs/base/test/common/date.test.ts b/src/vs/base/test/common/date.test.ts new file mode 100644 index 0000000000..62fe85b05f --- /dev/null +++ b/src/vs/base/test/common/date.test.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { strictEqual } from 'assert'; +import { fromNow } from 'vs/base/common/date'; + +suite('Date', () => { + suite('fromNow', () => { + test('appendAgoLabel', () => { + strictEqual(fromNow(Date.now() - 35000), '35 secs'); + strictEqual(fromNow(Date.now() - 35000, false), '35 secs'); + strictEqual(fromNow(Date.now() - 35000, true), '35 secs ago'); + }); + test('useFullTimeWords', () => { + strictEqual(fromNow(Date.now() - 35000), '35 secs'); + strictEqual(fromNow(Date.now() - 35000, undefined, false), '35 secs'); + strictEqual(fromNow(Date.now() - 35000, undefined, true), '35 seconds'); + }); + test('disallowNow', () => { + strictEqual(fromNow(Date.now() - 5000), 'now'); + strictEqual(fromNow(Date.now() - 5000, undefined, undefined, false), 'now'); + strictEqual(fromNow(Date.now() - 5000, undefined, undefined, true), '5 secs'); + }); + }); +}); diff --git a/src/vs/base/test/common/event.test.ts b/src/vs/base/test/common/event.test.ts index a979625dee..d82fe679ab 100644 --- a/src/vs/base/test/common/event.test.ts +++ b/src/vs/base/test/common/event.test.ts @@ -9,6 +9,8 @@ import { errorHandler, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { AsyncEmitter, DebounceEmitter, Emitter, Event, EventBufferer, EventMultiplexer, IWaitUntil, MicrotaskEmitter, PauseableEmitter, Relay } from 'vs/base/common/event'; import { DisposableStore, IDisposable, isDisposable, setDisposableTracker, toDisposable } from 'vs/base/common/lifecycle'; import { observableValue, transaction } from 'vs/base/common/observable'; +import { MicrotaskDelay } from 'vs/base/common/symbols'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; import { DisposableTracker, ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; namespace Samples { @@ -89,7 +91,7 @@ suite('Event utils dispose', function () { test('no leak with debounce-util', function () { const store = new DisposableStore(); const emitter = new Emitter<number>(); - const debounced = Event.debounce(emitter.event, (l) => 0, undefined, undefined, undefined, store); + const debounced = Event.debounce(emitter.event, (l) => 0, undefined, undefined, undefined, undefined, store); assertDisposablesCount(1); // debounce only listens when `debounce` is being listened on let all = 0; @@ -173,8 +175,8 @@ suite('Event', function () { let firstCount = 0; let lastCount = 0; const a = new Emitter({ - onFirstListenerAdd() { firstCount += 1; }, - onLastListenerRemove() { lastCount += 1; } + onWillAddFirstListener() { firstCount += 1; }, + onDidRemoveLastListener() { lastCount += 1; } }); assert.strictEqual(firstCount, 0); @@ -193,6 +195,24 @@ suite('Event', function () { assert.strictEqual(lastCount, 1); }); + test('onWillRemoveListener', () => { + let count = 0; + const a = new Emitter({ + onWillRemoveListener() { count += 1; } + }); + + assert.strictEqual(count, 0); + + let subscription = a.event(function () { }); + assert.strictEqual(count, 0); + + subscription.dispose(); + assert.strictEqual(count, 1); + + subscription = a.event(function () { }); + assert.strictEqual(count, 1); + }); + test('throwingListener', () => { const origErrorHandler = errorHandler.getUnexpectedErrorHandler(); setUnexpectedErrorHandler(() => null); @@ -215,6 +235,27 @@ suite('Event', function () { } }); + test('throwingListener (custom handler)', () => { + + const allError: any[] = []; + + const a = new Emitter<undefined>({ + onListenerError(e) { allError.push(e); } + }); + let hit = false; + a.event(function () { + // eslint-disable-next-line no-throw-literal + throw 9; + }); + a.event(function () { + hit = true; + }); + a.fire(undefined); + assert.strictEqual(hit, true); + assert.deepStrictEqual(allError, [9]); + + }); + test('reusing event function and context', function () { let counter = 0; function listener() { @@ -238,105 +279,30 @@ suite('Event', function () { assert.strictEqual(counter, 3); }); - test('Debounce Event', function (done: () => void) { - const doc = new Samples.Document3(); - - const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => { - if (!prev) { - prev = [cur]; - } else if (prev.indexOf(cur) < 0) { - prev.push(cur); - } - return prev; - }, 10); - - let count = 0; - - onDocDidChange(keys => { - count++; - assert.ok(keys, 'was not expecting keys.'); - if (count === 1) { - doc.setText('4'); - assert.deepStrictEqual(keys, ['1', '2', '3']); - } else if (count === 2) { - assert.deepStrictEqual(keys, ['4']); - done(); - } - }); - - doc.setText('1'); - doc.setText('2'); - doc.setText('3'); - }); - - test('Debounce Event - leading', async function () { - const emitter = new Emitter<void>(); - const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true); - - let calls = 0; - debounced(() => { - calls++; - }); - - // If the source event is fired once, the debounced (on the leading edge) event should be fired only once - emitter.fire(); - - await timeout(1); - assert.strictEqual(calls, 1); - }); - - test('Debounce Event - leading', async function () { - const emitter = new Emitter<void>(); - const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true); - - let calls = 0; - debounced(() => { - calls++; - }); - - // If the source event is fired multiple times, the debounced (on the leading edge) event should be fired twice - emitter.fire(); - emitter.fire(); - emitter.fire(); - await timeout(1); - assert.strictEqual(calls, 2); - }); - - test('Debounce Event - leading reset', async function () { - const emitter = new Emitter<number>(); - const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, /*leading=*/true); - - const calls: number[] = []; - debounced((e) => calls.push(e)); - - emitter.fire(1); - emitter.fire(1); - - await timeout(1); - assert.deepStrictEqual(calls, [1, 1]); - }); - test('DebounceEmitter', async function () { - let callCount = 0; - let sum = 0; - const emitter = new DebounceEmitter<number>({ - merge: arr => { - callCount += 1; - return arr.reduce((p, c) => p + c); - } + return runWithFakedTimers({}, async function () { + + let callCount = 0; + let sum = 0; + const emitter = new DebounceEmitter<number>({ + merge: arr => { + callCount += 1; + return arr.reduce((p, c) => p + c); + } + }); + + emitter.event(e => { sum = e; }); + + const p = Event.toPromise(emitter.event); + + emitter.fire(1); + emitter.fire(2); + + await p; + + assert.strictEqual(callCount, 1); + assert.strictEqual(sum, 3); }); - - emitter.event(e => { sum = e; }); - - const p = Event.toPromise(emitter.event); - - emitter.fire(1); - emitter.fire(2); - - await p; - - assert.strictEqual(callCount, 1); - assert.strictEqual(sum, 3); }); test('Microtask Emitter', (done) => { @@ -410,59 +376,64 @@ suite('AsyncEmitter', function () { }); test('sequential delivery', async function () { + return runWithFakedTimers({}, async function () { - interface E extends IWaitUntil { - foo: boolean; - } + interface E extends IWaitUntil { + foo: boolean; + } - let globalState = 0; - const emitter = new AsyncEmitter<E>(); + let globalState = 0; + const emitter = new AsyncEmitter<E>(); - emitter.event(e => { - e.waitUntil(timeout(10).then(_ => { - assert.strictEqual(globalState, 0); - globalState += 1; - })); + emitter.event(e => { + e.waitUntil(timeout(10).then(_ => { + assert.strictEqual(globalState, 0); + globalState += 1; + })); + }); + + emitter.event(e => { + e.waitUntil(timeout(1).then(_ => { + assert.strictEqual(globalState, 1); + globalState += 1; + })); + }); + + await emitter.fireAsync({ foo: true }, CancellationToken.None); + assert.strictEqual(globalState, 2); }); - - emitter.event(e => { - e.waitUntil(timeout(1).then(_ => { - assert.strictEqual(globalState, 1); - globalState += 1; - })); - }); - - await emitter.fireAsync({ foo: true }, CancellationToken.None); - assert.strictEqual(globalState, 2); }); test('sequential, in-order delivery', async function () { - interface E extends IWaitUntil { - foo: number; - } - const events: number[] = []; - let done = false; - const emitter = new AsyncEmitter<E>(); + return runWithFakedTimers({}, async function () { - // e1 - emitter.event(e => { - e.waitUntil(timeout(10).then(async _ => { - if (e.foo === 1) { - await emitter.fireAsync({ foo: 2 }, CancellationToken.None); - assert.deepStrictEqual(events, [1, 2]); - done = true; - } - })); + interface E extends IWaitUntil { + foo: number; + } + const events: number[] = []; + let done = false; + const emitter = new AsyncEmitter<E>(); + + // e1 + emitter.event(e => { + e.waitUntil(timeout(10).then(async _ => { + if (e.foo === 1) { + await emitter.fireAsync({ foo: 2 }, CancellationToken.None); + assert.deepStrictEqual(events, [1, 2]); + done = true; + } + })); + }); + + // e2 + emitter.event(e => { + events.push(e.foo); + e.waitUntil(timeout(7)); + }); + + await emitter.fireAsync({ foo: 1 }, CancellationToken.None); + assert.ok(done); }); - - // e2 - emitter.event(e => { - events.push(e.foo); - e.waitUntil(timeout(7)); - }); - - await emitter.fireAsync({ foo: 1 }, CancellationToken.None); - assert.ok(done); }); test('catch errors', async function () { @@ -623,6 +594,17 @@ suite('PausableEmitter', function () { assert.deepStrictEqual(data, [1, 1, 2, 2, 3, 3]); }); + + test('empty pause with merge', function () { + const data: number[] = []; + const emitter = new PauseableEmitter<number>({ merge: a => a[0] }); + emitter.event(e => data.push(1)); + + emitter.pause(); + emitter.resume(); + assert.deepStrictEqual(data, []); + }); + }); suite('Event utils - ensureNoDisposablesAreLeakedInTestSuite', function () { @@ -991,7 +973,7 @@ suite('Event utils', () => { test('dispose is reentrant', () => { const emitter = new Emitter<number>({ - onLastListenerRemove: () => { + onDidRemoveLastListener: () => { emitter.dispose(); } }); @@ -1085,4 +1067,226 @@ suite('Event utils', () => { { label: 'dispose', idx: 1 }, ]); }); + + suite('accumulate', () => { + test('should not fire after a listener is disposed with undefined or []', async () => { + const eventEmitter = new Emitter<number>(); + const event = eventEmitter.event; + const accumulated = Event.accumulate(event, 0); + + const calls1: number[][] = []; + const calls2: number[][] = []; + const listener1 = accumulated((e) => calls1.push(e)); + accumulated((e) => calls2.push(e)); + + eventEmitter.fire(1); + await timeout(1); + assert.deepStrictEqual(calls1, [[1]]); + assert.deepStrictEqual(calls2, [[1]]); + + listener1.dispose(); + await timeout(1); + assert.deepStrictEqual(calls1, [[1]]); + assert.deepStrictEqual(calls2, [[1]], 'should not fire after a listener is disposed with undefined or []'); + }); + test('should accumulate a single event', async () => { + const eventEmitter = new Emitter<number>(); + const event = eventEmitter.event; + const accumulated = Event.accumulate(event, 0); + + const results1 = await new Promise<number[]>(r => { + accumulated(r); + eventEmitter.fire(1); + }); + assert.deepStrictEqual(results1, [1]); + + const results2 = await new Promise<number[]>(r => { + accumulated(r); + eventEmitter.fire(2); + }); + assert.deepStrictEqual(results2, [2]); + }); + test('should accumulate multiple events', async () => { + const eventEmitter = new Emitter<number>(); + const event = eventEmitter.event; + const accumulated = Event.accumulate(event, 0); + + const results1 = await new Promise<number[]>(r => { + accumulated(r); + eventEmitter.fire(1); + eventEmitter.fire(2); + eventEmitter.fire(3); + }); + assert.deepStrictEqual(results1, [1, 2, 3]); + + const results2 = await new Promise<number[]>(r => { + accumulated(r); + eventEmitter.fire(4); + eventEmitter.fire(5); + eventEmitter.fire(6); + eventEmitter.fire(7); + eventEmitter.fire(8); + }); + assert.deepStrictEqual(results2, [4, 5, 6, 7, 8]); + }); + }); + + suite('debounce', () => { + test('simple', function (done: () => void) { + const doc = new Samples.Document3(); + + const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => { + if (!prev) { + prev = [cur]; + } else if (prev.indexOf(cur) < 0) { + prev.push(cur); + } + return prev; + }, 10); + + let count = 0; + + onDocDidChange(keys => { + count++; + assert.ok(keys, 'was not expecting keys.'); + if (count === 1) { + doc.setText('4'); + assert.deepStrictEqual(keys, ['1', '2', '3']); + } else if (count === 2) { + assert.deepStrictEqual(keys, ['4']); + done(); + } + }); + + doc.setText('1'); + doc.setText('2'); + doc.setText('3'); + }); + + + test('microtask', function (done: () => void) { + const doc = new Samples.Document3(); + + const onDocDidChange = Event.debounce(doc.onDidChange, (prev: string[] | undefined, cur) => { + if (!prev) { + prev = [cur]; + } else if (prev.indexOf(cur) < 0) { + prev.push(cur); + } + return prev; + }, MicrotaskDelay); + + let count = 0; + + onDocDidChange(keys => { + count++; + assert.ok(keys, 'was not expecting keys.'); + if (count === 1) { + doc.setText('4'); + assert.deepStrictEqual(keys, ['1', '2', '3']); + } else if (count === 2) { + assert.deepStrictEqual(keys, ['4']); + done(); + } + }); + + doc.setText('1'); + doc.setText('2'); + doc.setText('3'); + }); + + + test('leading', async function () { + const emitter = new Emitter<void>(); + const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true); + + let calls = 0; + debounced(() => { + calls++; + }); + + // If the source event is fired once, the debounced (on the leading edge) event should be fired only once + emitter.fire(); + + await timeout(1); + assert.strictEqual(calls, 1); + }); + + test('leading (2)', async function () { + const emitter = new Emitter<void>(); + const debounced = Event.debounce(emitter.event, (l, e) => e, 0, /*leading=*/true); + + let calls = 0; + debounced(() => { + calls++; + }); + + // If the source event is fired multiple times, the debounced (on the leading edge) event should be fired twice + emitter.fire(); + emitter.fire(); + emitter.fire(); + await timeout(1); + assert.strictEqual(calls, 2); + }); + + test('leading reset', async function () { + const emitter = new Emitter<number>(); + const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, /*leading=*/true); + + const calls: number[] = []; + debounced((e) => calls.push(e)); + + emitter.fire(1); + emitter.fire(1); + + await timeout(1); + assert.deepStrictEqual(calls, [1, 1]); + }); + + test('should not flush events when a listener is disposed', async () => { + const emitter = new Emitter<number>(); + const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0); + + const calls: number[] = []; + const listener = debounced((e) => calls.push(e)); + + emitter.fire(1); + listener.dispose(); + + emitter.fire(1); + + await timeout(1); + assert.deepStrictEqual(calls, []); + }); + + test('flushOnListenerRemove - should flush events when a listener is disposed', async () => { + const emitter = new Emitter<number>(); + const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0, undefined, true); + + const calls: number[] = []; + const listener = debounced((e) => calls.push(e)); + + emitter.fire(1); + listener.dispose(); + + emitter.fire(1); + + await timeout(1); + assert.deepStrictEqual(calls, [1], 'should fire with the first event, not the second (after listener dispose)'); + }); + + test('should flush events when the emitter is disposed', async () => { + const emitter = new Emitter<number>(); + const debounced = Event.debounce(emitter.event, (l, e) => l ? l + 1 : 1, 0); + + const calls: number[] = []; + debounced((e) => calls.push(e)); + + emitter.fire(1); + emitter.dispose(); + + await timeout(1); + assert.deepStrictEqual(calls, [1]); + }); + }); }); diff --git a/src/vs/base/test/common/filters.test.ts b/src/vs/base/test/common/filters.test.ts index 3c66087c54..32cf472706 100644 --- a/src/vs/base/test/common/filters.test.ts +++ b/src/vs/base/test/common/filters.test.ts @@ -185,24 +185,28 @@ suite('Filters', () => { assert(matchesWords('Debug Console', 'Open: Debug Console')); filterOk(matchesWords, 'gp', 'Git: Pull', [{ start: 0, end: 1 }, { start: 5, end: 6 }]); - filterOk(matchesWords, 'g p', 'Git: Pull', [{ start: 0, end: 1 }, { start: 3, end: 4 }, { start: 5, end: 6 }]); + filterOk(matchesWords, 'g p', 'Git: Pull', [{ start: 0, end: 1 }, { start: 5, end: 6 }]); filterOk(matchesWords, 'gipu', 'Git: Pull', [{ start: 0, end: 2 }, { start: 5, end: 7 }]); filterOk(matchesWords, 'gp', 'Category: Git: Pull', [{ start: 10, end: 11 }, { start: 15, end: 16 }]); - filterOk(matchesWords, 'g p', 'Category: Git: Pull', [{ start: 10, end: 11 }, { start: 13, end: 14 }, { start: 15, end: 16 }]); + filterOk(matchesWords, 'g p', 'Category: Git: Pull', [{ start: 10, end: 11 }, { start: 15, end: 16 }]); filterOk(matchesWords, 'gipu', 'Category: Git: Pull', [{ start: 10, end: 12 }, { start: 15, end: 17 }]); filterNotOk(matchesWords, 'it', 'Git: Pull'); filterNotOk(matchesWords, 'll', 'Git: Pull'); filterOk(matchesWords, 'git: プル', 'git: プル', [{ start: 0, end: 7 }]); - filterOk(matchesWords, 'git プル', 'git: プル', [{ start: 0, end: 4 }, { start: 5, end: 7 }]); + filterOk(matchesWords, 'git プル', 'git: プル', [{ start: 0, end: 3 }, { start: 5, end: 7 }]); filterOk(matchesWords, 'öäk', 'Öhm: Älles Klar', [{ start: 0, end: 1 }, { start: 5, end: 6 }, { start: 11, end: 12 }]); // Handles issue #123915 filterOk(matchesWords, 'C++', 'C/C++: command', [{ start: 2, end: 5 }]); + // Handles issue #154533 + filterOk(matchesWords, '.', ':', []); + filterOk(matchesWords, '.', '.', [{ start: 0, end: 1 }]); + // assert.ok(matchesWords('gipu', 'Category: Git: Pull', true) === null); // assert.deepStrictEqual(matchesWords('pu', 'Category: Git: Pull', true), [{ start: 15, end: 17 }]); @@ -558,6 +562,11 @@ suite('Filters', () => { assertMatches('.', 'log', 'log', anyScore); }); + test('anyScore should not require a strong first match', function () { + assertMatches('bar', 'foobAr', 'foo^b^A^r', anyScore); + assertMatches('bar', 'foobar', 'foo^b^a^r', anyScore); + }); + test('configurable full match boost', function () { const prefix = 'create'; const a = 'createModelServices'; diff --git a/src/vs/base/test/common/fuzzyScorer.test.ts b/src/vs/base/test/common/fuzzyScorer.test.ts index ceb701fd14..6bc26dfa27 100644 --- a/src/vs/base/test/common/fuzzyScorer.test.ts +++ b/src/vs/base/test/common/fuzzyScorer.test.ts @@ -1066,10 +1066,10 @@ suite('Fuzzy Scorer', () => { }); test('compareFilesByScore - boost shorter prefix match if multiple queries are used', function () { - const resourceA = URI.file('src/vs/workbench/browser/actions/windowActions.ts'); - const resourceB = URI.file('src/vs/workbench/electron-browser/window.ts'); + const resourceA = URI.file('src/vs/workbench/node/actions/windowActions.ts'); + const resourceB = URI.file('src/vs/workbench/electron-node/window.ts'); - for (const query of ['window browser', 'window.ts browser']) { + for (const query of ['window node', 'window.ts node']) { let res = [resourceA, resourceB].sort((r1, r2) => compareItemsByScore(r1, r2, query, true, ResourceAccessor)); assert.strictEqual(res[0], resourceB); assert.strictEqual(res[1], resourceA); diff --git a/src/vs/base/test/common/glob.test.ts b/src/vs/base/test/common/glob.test.ts index 2c009b3fef..e118f995b1 100644 --- a/src/vs/base/test/common/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -1091,6 +1091,22 @@ suite('Glob', () => { } }); + test('relative pattern - trailing slash / backslash (#162498)', function () { + if (isWindows) { + let p: glob.IRelativePattern = { base: 'C:\\', pattern: 'foo.cs' }; + assertGlobMatch(p, 'C:\\foo.cs'); + + p = { base: 'C:\\bar\\', pattern: 'foo.cs' }; + assertGlobMatch(p, 'C:\\bar\\foo.cs'); + } else { + let p: glob.IRelativePattern = { base: '/', pattern: 'foo.cs' }; + assertGlobMatch(p, '/foo.cs'); + + p = { base: '/bar/', pattern: 'foo.cs' }; + assertGlobMatch(p, '/bar/foo.cs'); + } + }); + test('pattern with "base" does not explode - #36081', function () { assert.ok(glob.match({ 'base': true }, 'base')); }); diff --git a/src/vs/base/test/common/history.test.ts b/src/vs/base/test/common/history.test.ts index 929d7a7631..c7fa920a18 100644 --- a/src/vs/base/test/common/history.test.ts +++ b/src/vs/base/test/common/history.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { HistoryNavigator } from 'vs/base/common/history'; +import { HistoryNavigator, HistoryNavigator2 } from 'vs/base/common/history'; suite('History Navigator', () => { @@ -13,12 +13,18 @@ suite('History Navigator', () => { assert.deepStrictEqual(['3', '4'], toArray(testObject)); }); - test('create sets the position to last', () => { + test('create sets the position after last', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 100); assert.strictEqual(testObject.current(), null); + assert.strictEqual(testObject.isNowhere(), true); + assert.strictEqual(testObject.isFirst(), false); + assert.strictEqual(testObject.isLast(), false); assert.strictEqual(testObject.next(), null); assert.strictEqual(testObject.previous(), '4'); + assert.strictEqual(testObject.isNowhere(), false); + assert.strictEqual(testObject.isFirst(), false); + assert.strictEqual(testObject.isLast(), true); }); test('last returns last element', () => { @@ -26,12 +32,16 @@ suite('History Navigator', () => { assert.strictEqual(testObject.first(), '1'); assert.strictEqual(testObject.last(), '4'); + assert.strictEqual(testObject.isFirst(), false); + assert.strictEqual(testObject.isLast(), true); }); test('first returns first element', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); assert.strictEqual('2', testObject.first()); + assert.strictEqual(testObject.isFirst(), true); + assert.strictEqual(testObject.isLast(), false); }); test('next returns next element', () => { @@ -53,23 +63,27 @@ suite('History Navigator', () => { assert.strictEqual(testObject.previous(), null); }); - test('next on last element returs null and remains on last', () => { + test('next on last element returns null and remains on last', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); testObject.first(); testObject.last(); + assert.strictEqual(testObject.isLast(), true); assert.strictEqual(testObject.current(), '4'); assert.strictEqual(testObject.next(), null); + assert.strictEqual(testObject.isLast(), false); // Stepping past the last element, is no longer "last" }); - test('previous on first element returs null and remains on first', () => { + test('previous on first element returns null and remains on first', () => { const testObject = new HistoryNavigator(['1', '2', '3', '4'], 3); testObject.first(); + assert.strictEqual(testObject.isFirst(), true); assert.strictEqual(testObject.current(), '2'); assert.strictEqual(testObject.previous(), null); + assert.strictEqual(testObject.isFirst(), true); }); test('add reduces the input to limit', () => { @@ -95,7 +109,9 @@ suite('History Navigator', () => { testObject.add('5'); assert.strictEqual(testObject.previous(), '5'); + assert.strictEqual(testObject.isLast(), true); assert.strictEqual(testObject.next(), null); + assert.strictEqual(testObject.isLast(), false); }); test('adding an existing item changes the order', () => { @@ -112,6 +128,7 @@ suite('History Navigator', () => { testObject.first(); assert.deepStrictEqual(testObject.previous(), null); + assert.strictEqual(testObject.isFirst(), true); }); test('previous returns object if the current position is not the first one', () => { @@ -128,7 +145,9 @@ suite('History Navigator', () => { testObject.last(); + assert.strictEqual(testObject.isLast(), true); assert.deepStrictEqual(testObject.next(), null); + assert.strictEqual(testObject.isLast(), false); }); test('next returns object if the current position is not the last one', () => { @@ -145,6 +164,7 @@ suite('History Navigator', () => { assert.strictEqual(testObject.previous(), 'c'); testObject.clear(); assert.strictEqual(testObject.current(), null); + assert.strictEqual(testObject.isNowhere(), true); }); function toArray(historyNavigator: HistoryNavigator<string>): Array<string | null> { @@ -158,3 +178,95 @@ suite('History Navigator', () => { return result; } }); + +suite('History Navigator 2', () => { + + test('constructor', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4']); + + assert.strictEqual(testObject.current(), '4'); + assert.strictEqual(testObject.isAtEnd(), true); + }); + + test('constructor - initial history is not empty', () => { + assert.throws(() => new HistoryNavigator2([])); + }); + + test('constructor - capacity limit', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4'], 3); + + assert.strictEqual(testObject.current(), '4'); + assert.strictEqual(testObject.isAtEnd(), true); + assert.strictEqual(testObject.has('1'), false); + }); + + test('constructor - duplicate values', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4', '3', '2', '1']); + + assert.strictEqual(testObject.current(), '1'); + assert.strictEqual(testObject.isAtEnd(), true); + }); + + test('navigation', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4']); + + assert.strictEqual(testObject.current(), '4'); + assert.strictEqual(testObject.isAtEnd(), true); + + assert.strictEqual(testObject.next(), '4'); + assert.strictEqual(testObject.previous(), '3'); + assert.strictEqual(testObject.previous(), '2'); + assert.strictEqual(testObject.previous(), '1'); + assert.strictEqual(testObject.previous(), '1'); + + assert.strictEqual(testObject.current(), '1'); + assert.strictEqual(testObject.next(), '2'); + assert.strictEqual(testObject.resetCursor(), '4'); + }); + + test('add', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4']); + testObject.add('5'); + + assert.strictEqual(testObject.current(), '5'); + assert.strictEqual(testObject.isAtEnd(), true); + }); + + test('add - existing value', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4']); + testObject.add('2'); + + assert.strictEqual(testObject.current(), '2'); + assert.strictEqual(testObject.isAtEnd(), true); + + assert.strictEqual(testObject.previous(), '4'); + assert.strictEqual(testObject.previous(), '3'); + assert.strictEqual(testObject.previous(), '1'); + }); + + test('replaceLast', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4']); + testObject.replaceLast('5'); + + assert.strictEqual(testObject.current(), '5'); + assert.strictEqual(testObject.isAtEnd(), true); + assert.strictEqual(testObject.has('4'), false); + + assert.strictEqual(testObject.previous(), '3'); + assert.strictEqual(testObject.previous(), '2'); + assert.strictEqual(testObject.previous(), '1'); + }); + + test('replaceLast - existing value', () => { + const testObject = new HistoryNavigator2(['1', '2', '3', '4']); + testObject.replaceLast('2'); + + assert.strictEqual(testObject.current(), '2'); + assert.strictEqual(testObject.isAtEnd(), true); + assert.strictEqual(testObject.has('4'), false); + + assert.strictEqual(testObject.previous(), '3'); + assert.strictEqual(testObject.previous(), '1'); + }); + +}); diff --git a/src/vs/base/test/common/iconLabels.test.ts b/src/vs/base/test/common/iconLabels.test.ts index 9f6c5795a9..915e2c26f8 100644 --- a/src/vs/base/test/common/iconLabels.test.ts +++ b/src/vs/base/test/common/iconLabels.test.ts @@ -5,9 +5,9 @@ import * as assert from 'assert'; import { IMatch } from 'vs/base/common/filters'; -import { escapeIcons, IParsedLabelWithIcons, markdownEscapeEscapedIcons, matchesFuzzyIconAware, parseLabelWithIcons, stripIcons } from 'vs/base/common/iconLabels'; +import { escapeIcons, getCodiconAriaLabel, IParsedLabelWithIcons, markdownEscapeEscapedIcons, matchesFuzzyIconAware, parseLabelWithIcons, stripIcons } from 'vs/base/common/iconLabels'; -export interface IIconFilter { +interface IIconFilter { // Returns null if word doesn't match. (query: string, target: IParsedLabelWithIcons): IMatch[] | null; } @@ -21,6 +21,26 @@ function filterOk(filter: IIconFilter, word: string, target: IParsedLabelWithIco } suite('Icon Labels', () => { + + test('Can get proper aria labels', () => { + // note, the spaces in the results are important + const testCases = new Map<string, string>([ + ['', ''], + ['asdf', 'asdf'], + ['asdf$(squirrel)asdf', 'asdf squirrel asdf'], + ['asdf $(squirrel) asdf', 'asdf squirrel asdf'], + ['$(rocket)asdf', 'rocket asdf'], + ['$(rocket) asdf', 'rocket asdf'], + ['$(rocket)$(rocket)$(rocket)asdf', 'rocket rocket rocket asdf'], + ['$(rocket) asdf $(rocket)', 'rocket asdf rocket'], + ['$(rocket)asdf$(rocket)', 'rocket asdf rocket'], + ]); + + for (const [input, expected] of testCases) { + assert.strictEqual(getCodiconAriaLabel(input), expected); + } + }); + test('matchesFuzzyIconAware', () => { // Camel Case diff --git a/src/vs/base/test/common/iterator.test.ts b/src/vs/base/test/common/iterator.test.ts index 98b2a5fd03..f84de063f1 100644 --- a/src/vs/base/test/common/iterator.test.ts +++ b/src/vs/base/test/common/iterator.test.ts @@ -25,11 +25,8 @@ suite('Iterable', function () { assert.strictEqual(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); + test('wrap', function () { + assert.deepStrictEqual([...Iterable.wrap(1)], [1]); + assert.deepStrictEqual([...Iterable.wrap([1, 2, 3])], [1, 2, 3]); }); - }); diff --git a/src/vs/base/test/common/keyCodes.test.ts b/src/vs/base/test/common/keyCodes.test.ts index 7ca2490003..c14805aca3 100644 --- a/src/vs/base/test/common/keyCodes.test.ts +++ b/src/vs/base/test/common/keyCodes.test.ts @@ -5,13 +5,13 @@ import * as assert from 'assert'; import { EVENT_KEY_CODE_MAP, IMMUTABLE_CODE_TO_KEY_CODE, IMMUTABLE_KEY_CODE_TO_CODE, KeyChord, KeyCode, KeyCodeUtils, KeyMod, NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE, ScanCode, ScanCodeUtils } from 'vs/base/common/keyCodes'; -import { ChordKeybinding, createKeybinding, Keybinding, SimpleKeybinding } from 'vs/base/common/keybindings'; +import { decodeKeybinding, KeyCodeChord, Keybinding } from 'vs/base/common/keybindings'; import { OperatingSystem } from 'vs/base/common/platform'; suite('keyCodes', () => { function testBinaryEncoding(expected: Keybinding | null, k: number, OS: OperatingSystem): void { - assert.deepStrictEqual(createKeybinding(k, OS), expected); + assert.deepStrictEqual(decodeKeybinding(k, OS), expected); } test('mapping for Minus', () => { @@ -56,34 +56,34 @@ suite('keyCodes', () => { } test(null, 0); - test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter).toChord(), KeyCode.Enter); - test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter).toChord(), KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyCode.Enter); - test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyCode.Enter); - test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter); - test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, false, false, false, KeyCode.Enter).toKeybinding(), KeyCode.Enter); + test(new KeyCodeChord(true, false, false, false, KeyCode.Enter).toKeybinding(), KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, false, true, false, KeyCode.Enter).toKeybinding(), KeyMod.Alt | KeyCode.Enter); + test(new KeyCodeChord(true, false, true, false, KeyCode.Enter).toKeybinding(), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, true, false, false, KeyCode.Enter).toKeybinding(), KeyMod.Shift | KeyCode.Enter); + test(new KeyCodeChord(true, true, false, false, KeyCode.Enter).toKeybinding(), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, true, true, false, KeyCode.Enter).toKeybinding(), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); + test(new KeyCodeChord(true, true, true, false, KeyCode.Enter).toKeybinding(), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, false, false, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyCode.Enter); + test(new KeyCodeChord(true, false, false, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, false, true, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter); + test(new KeyCodeChord(true, false, true, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, true, false, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter); + test(new KeyCodeChord(true, true, false, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, true, true, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); + test(new KeyCodeChord(true, true, true, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); test( - new ChordKeybinding([ - new SimpleKeybinding(false, false, false, false, KeyCode.Enter), - new SimpleKeybinding(false, false, false, false, KeyCode.Tab) + new Keybinding([ + new KeyCodeChord(false, false, false, false, KeyCode.Enter), + new KeyCodeChord(false, false, false, false, KeyCode.Tab) ]), KeyChord(KeyCode.Enter, KeyCode.Tab) ); test( - new ChordKeybinding([ - new SimpleKeybinding(false, false, false, true, KeyCode.KeyY), - new SimpleKeybinding(false, false, false, false, KeyCode.KeyZ) + new Keybinding([ + new KeyCodeChord(false, false, false, true, KeyCode.KeyY), + new KeyCodeChord(false, false, false, false, KeyCode.KeyZ) ]), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyY, KeyCode.KeyZ) ); @@ -98,34 +98,34 @@ suite('keyCodes', () => { } test(null, 0); - test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter).toChord(), KeyCode.Enter); - test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter).toChord(), KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter).toChord(), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyCode.Enter); - test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyCode.Enter); - test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter); - test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, false, false, false, KeyCode.Enter).toKeybinding(), KeyCode.Enter); + test(new KeyCodeChord(false, false, false, true, KeyCode.Enter).toKeybinding(), KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, false, true, false, KeyCode.Enter).toKeybinding(), KeyMod.Alt | KeyCode.Enter); + test(new KeyCodeChord(false, false, true, true, KeyCode.Enter).toKeybinding(), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, true, false, false, KeyCode.Enter).toKeybinding(), KeyMod.Shift | KeyCode.Enter); + test(new KeyCodeChord(false, true, false, true, KeyCode.Enter).toKeybinding(), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(false, true, true, false, KeyCode.Enter).toKeybinding(), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); + test(new KeyCodeChord(false, true, true, true, KeyCode.Enter).toKeybinding(), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(true, false, false, false, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyCode.Enter); + test(new KeyCodeChord(true, false, false, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(true, false, true, false, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter); + test(new KeyCodeChord(true, false, true, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(true, true, false, false, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter); + test(new KeyCodeChord(true, true, false, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); + test(new KeyCodeChord(true, true, true, false, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); + test(new KeyCodeChord(true, true, true, true, KeyCode.Enter).toKeybinding(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); test( - new ChordKeybinding([ - new SimpleKeybinding(false, false, false, false, KeyCode.Enter), - new SimpleKeybinding(false, false, false, false, KeyCode.Tab) + new Keybinding([ + new KeyCodeChord(false, false, false, false, KeyCode.Enter), + new KeyCodeChord(false, false, false, false, KeyCode.Tab) ]), KeyChord(KeyCode.Enter, KeyCode.Tab) ); test( - new ChordKeybinding([ - new SimpleKeybinding(true, false, false, false, KeyCode.KeyY), - new SimpleKeybinding(false, false, false, false, KeyCode.KeyZ) + new Keybinding([ + new KeyCodeChord(true, false, false, false, KeyCode.KeyY), + new KeyCodeChord(false, false, false, false, KeyCode.KeyZ) ]), KeyChord(KeyMod.CtrlCmd | KeyCode.KeyY, KeyCode.KeyZ) ); diff --git a/src/vs/base/test/common/keybindings.test.ts b/src/vs/base/test/common/keybindings.test.ts new file mode 100644 index 0000000000..f1be9f1da9 --- /dev/null +++ b/src/vs/base/test/common/keybindings.test.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { KeyCode, ScanCode } from 'vs/base/common/keyCodes'; +import { KeyCodeChord, ScanCodeChord } from 'vs/base/common/keybindings'; + +suite('keyCodes', () => { + + test('issue #173325: wrong interpretations of special keys (e.g. [Equal] is mistaken for V)', () => { + const a = new KeyCodeChord(true, false, false, false, KeyCode.KeyV); + const b = new ScanCodeChord(true, false, false, false, ScanCode.Equal); + assert.strictEqual(a.getHashCode() === b.getHashCode(), false); + }); + +}); diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index 22755f5b84..66dd84cc4a 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -59,7 +59,7 @@ suite('Labels', () => { 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\\…']); + assert.deepStrictEqual(labels.shorten(['src\\vs\\workbench\\parts\\execution\\electron-sandbox', 'src\\vs\\workbench\\parts\\execution\\electron-sandbox\\something', 'src\\vs\\workbench\\parts\\terminal\\electron-sandbox']), ['…\\execution\\electron-sandbox', '…\\something', '…\\terminal\\…']); }); (isWindows ? test.skip : test)('shorten - not windows', () => { @@ -137,6 +137,10 @@ suite('Labels', () => { assert.strictEqual(labels.template(t, { dirty: '', activeEditorShort: '', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), 'monaco - Visual Studio Code'); assert.strictEqual(labels.template(t, { dirty: '', activeEditorShort: 'somefile.txt', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), 'somefile.txt - monaco - Visual Studio Code'); assert.strictEqual(labels.template(t, { dirty: '* ', activeEditorShort: 'somefile.txt', rootName: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), '* somefile.txt - monaco - Visual Studio Code'); + + // real world example (other) + t = '${dirty}${activeEditorShort}${separator}${rootNameShort}${separator}${appName}'; + assert.strictEqual(labels.template(t, { dirty: '', activeEditorShort: '', rootName: 'monaco (Workspace)', rootNameShort: 'monaco', appName: 'Visual Studio Code', separator: { label: ' - ' } }), 'monaco - Visual Studio Code'); }); test('mnemonicButtonLabel', () => { diff --git a/src/vs/base/test/common/lazy.test.ts b/src/vs/base/test/common/lazy.test.ts index 2c5005538f..fbb2dae744 100644 --- a/src/vs/base/test/common/lazy.test.ts +++ b/src/vs/base/test/common/lazy.test.ts @@ -12,53 +12,19 @@ suite('Lazy', () => { let counter = 0; const value = new Lazy(() => ++counter); - assert.strictEqual(value.hasValue(), false); - assert.strictEqual(value.getValue(), 1); - assert.strictEqual(value.hasValue(), true); - assert.strictEqual(value.getValue(), 1); // make sure we did not evaluate again + assert.strictEqual(value.hasValue, false); + assert.strictEqual(value.value, 1); + assert.strictEqual(value.hasValue, true); + assert.strictEqual(value.value, 1); // make sure we did not evaluate again }); test('lazy values handle error case', () => { let counter = 0; const value = new Lazy(() => { throw new Error(`${++counter}`); }); - assert.strictEqual(value.hasValue(), false); - assert.throws(() => value.getValue(), /\b1\b/); - assert.strictEqual(value.hasValue(), true); - assert.throws(() => value.getValue(), /\b1\b/); - }); - - test('map should not cause lazy values to be re-resolved', () => { - let outer = 0; - let inner = 10; - const outerLazy = new Lazy(() => ++outer); - const innerLazy = outerLazy.map(x => [x, ++inner]); - - assert.strictEqual(outerLazy.hasValue(), false); - assert.strictEqual(innerLazy.hasValue(), false); - - assert.deepStrictEqual(innerLazy.getValue(), [1, 11]); - assert.strictEqual(outerLazy.hasValue(), true); - assert.strictEqual(innerLazy.hasValue(), true); - assert.strictEqual(outerLazy.getValue(), 1); - - // make sure we did not evaluate again - assert.strictEqual(outerLazy.getValue(), 1); - assert.deepStrictEqual(innerLazy.getValue(), [1, 11]); - }); - - test('map should handle error values', () => { - let outer = 0; - let inner = 10; - const outerLazy = new Lazy(() => { throw new Error(`${++outer}`); }); - const innerLazy = outerLazy.map(x => { throw new Error(`${++inner}`); }); - - assert.strictEqual(outerLazy.hasValue(), false); - assert.strictEqual(innerLazy.hasValue(), false); - - assert.throws(() => innerLazy.getValue(), /\b1\b/); // we should get result from outer - assert.strictEqual(outerLazy.hasValue(), true); - assert.strictEqual(innerLazy.hasValue(), true); - assert.throws(() => outerLazy.getValue(), /\b1\b/); + assert.strictEqual(value.hasValue, false); + assert.throws(() => value.value, /\b1\b/); + assert.strictEqual(value.hasValue, true); + assert.throws(() => value.value, /\b1\b/); }); }); diff --git a/src/vs/base/test/common/lifecycle.test.ts b/src/vs/base/test/common/lifecycle.test.ts index 8f31c050df..912a782135 100644 --- a/src/vs/base/test/common/lifecycle.test.ts +++ b/src/vs/base/test/common/lifecycle.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { Emitter } from 'vs/base/common/event'; -import { DisposableStore, dispose, IDisposable, markAsSingleton, MultiDisposeError, ReferenceCollection, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose, IDisposable, markAsSingleton, ReferenceCollection, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ensureNoDisposablesAreLeakedInTestSuite, throwIfDisposablesAreLeaked } from 'vs/base/test/common/utils'; class Disposable implements IDisposable { @@ -88,10 +88,10 @@ suite('Lifecycle', () => { assert.ok(disposedValues.has(1)); assert.ok(disposedValues.has(4)); - assert.ok(thrownError instanceof MultiDisposeError); - assert.strictEqual((thrownError as MultiDisposeError).errors.length, 2); - assert.strictEqual((thrownError as MultiDisposeError).errors[0].message, 'I am error 1'); - assert.strictEqual((thrownError as MultiDisposeError).errors[1].message, 'I am error 2'); + assert.ok(thrownError instanceof AggregateError); + assert.strictEqual((thrownError as AggregateError).errors.length, 2); + assert.strictEqual((thrownError as AggregateError).errors[0].message, 'I am error 1'); + assert.strictEqual((thrownError as AggregateError).errors[1].message, 'I am error 2'); }); test('Action bar has broken accessibility #100273', function () { @@ -167,10 +167,10 @@ suite('DisposableStore', () => { assert.ok(disposedValues.has(1)); assert.ok(disposedValues.has(4)); - assert.ok(thrownError instanceof MultiDisposeError); - assert.strictEqual((thrownError as MultiDisposeError).errors.length, 2); - assert.strictEqual((thrownError as MultiDisposeError).errors[0].message, 'I am error 1'); - assert.strictEqual((thrownError as MultiDisposeError).errors[1].message, 'I am error 2'); + assert.ok(thrownError instanceof AggregateError); + assert.strictEqual((thrownError as AggregateError).errors.length, 2); + assert.strictEqual((thrownError as AggregateError).errors[0].message, 'I am error 1'); + assert.strictEqual((thrownError as AggregateError).errors[1].message, 'I am error 2'); }); }); diff --git a/src/vs/base/test/common/linkedText.test.ts b/src/vs/base/test/common/linkedText.test.ts index 60e3a8c958..d06d5bb699 100644 --- a/src/vs/base/test/common/linkedText.test.ts +++ b/src/vs/base/test/common/linkedText.test.ts @@ -70,4 +70,14 @@ suite('LinkedText', () => { '...' ]); }); + + test('Should match non-greedily', () => { + assert.deepStrictEqual(parseLinkedText('a [link text 1](http://link.href "title1") b [link text 2](http://link.href "title2") c').nodes, [ + 'a ', + { label: 'link text 1', href: 'http://link.href', title: 'title1' }, + ' b ', + { label: 'link text 2', href: 'http://link.href', title: 'title2' }, + ' c', + ]); + }); }); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 3f4a18d423..9c6c46511b 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -4,11 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { shuffle } from 'vs/base/common/arrays'; -import { randomPath } from 'vs/base/common/extpath'; -import { ConfigKeysIterator, LinkedMap, LRUCache, PathIterator, ResourceMap, StringIterator, TernarySearchTree, Touch, UriIterator } from 'vs/base/common/map'; +import { LinkedMap, LRUCache, ResourceMap, Touch } from 'vs/base/common/map'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; -import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; suite('Map', () => { @@ -328,854 +325,6 @@ suite('Map', () => { assert.strictEqual([...map.keys()][0], '1'); }); - - test('PathIterator', () => { - const iter = new PathIterator(); - iter.reset('file:///usr/bin/file.txt'); - - 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.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - - iter.next(); - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - - iter.next(); - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), false); - - iter.next(); - assert.strictEqual(iter.value(), ''); - assert.strictEqual(iter.hasNext(), false); - iter.next(); - assert.strictEqual(iter.value(), ''); - assert.strictEqual(iter.hasNext(), false); - - // - iter.reset('/foo/bar/'); - assert.strictEqual(iter.value(), 'foo'); - assert.strictEqual(iter.hasNext(), true); - - iter.next(); - assert.strictEqual(iter.value(), 'bar'); - assert.strictEqual(iter.hasNext(), false); - }); - - test('URIIterator', function () { - const iter = new UriIterator(() => false, () => false); - iter.reset(URI.parse('file:///usr/bin/file.txt')); - - 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.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), false); - - - iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); - - // scheme - 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.strictEqual(iter.value(), 'share'); - assert.strictEqual(iter.cmp('SHARe'), 0); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // query - assert.strictEqual(iter.value(), 'foo'); - assert.strictEqual(iter.cmp('z') > 0, true); - assert.strictEqual(iter.cmp('a') < 0, true); - assert.strictEqual(iter.hasNext(), false); - }); - - test('URIIterator - ignore query/fragment', function () { - const iter = new UriIterator(() => false, () => true); - iter.reset(URI.parse('file:///usr/bin/file.txt')); - - 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.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), false); - - - iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); - - // scheme - 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.strictEqual(iter.value(), 'share'); - assert.strictEqual(iter.cmp('SHARe'), 0); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'usr'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'bin'); - assert.strictEqual(iter.hasNext(), true); - iter.next(); - - // path - assert.strictEqual(iter.value(), 'file.txt'); - assert.strictEqual(iter.hasNext(), false); - }); - - function assertTstDfs<E>(trie: TernarySearchTree<string, E>, ...elements: [string, E][]) { - - assert.ok(trie._isBalanced(), 'TST is not balanced'); - - let i = 0; - for (const [key, value] of trie) { - const expected = elements[i++]; - assert.ok(expected); - assert.strictEqual(key, expected[0]); - assert.strictEqual(value, expected[1]); - } - - assert.strictEqual(i, elements.length); - - const map = new Map<string, E>(); - for (const [key, value] of elements) { - map.set(key, value); - } - map.forEach((value, key) => { - assert.strictEqual(trie.get(key), value); - }); - - // forEach - let forEachCount = 0; - trie.forEach((element, key) => { - assert.strictEqual(element, map.get(key)); - forEachCount++; - }); - assert.strictEqual(map.size, forEachCount); - - // iterator - let iterCount = 0; - for (const [key, value] of trie) { - assert.strictEqual(value, map.get(key)); - iterCount++; - } - assert.strictEqual(map.size, iterCount); - - } - - test('TernarySearchTree - set', function () { - - let trie = TernarySearchTree.forStrings<number>(); - trie.set('foobar', 1); - trie.set('foobaz', 2); - - assertTstDfs(trie, ['foobar', 1], ['foobaz', 2]); // longer - - trie = TernarySearchTree.forStrings<number>(); - trie.set('foobar', 1); - trie.set('fooba', 2); - assertTstDfs(trie, ['fooba', 2], ['foobar', 1]); // shorter - - trie = TernarySearchTree.forStrings<number>(); - trie.set('foo', 1); - trie.set('foo', 2); - assertTstDfs(trie, ['foo', 2]); - - trie = TernarySearchTree.forStrings<number>(); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('bar', 3); - trie.set('foob', 4); - trie.set('bazz', 5); - - assertTstDfs(trie, - ['bar', 3], - ['bazz', 5], - ['foo', 1], - ['foob', 4], - ['foobar', 2], - ); - }); - - test('TernarySearchTree - findLongestMatch', function () { - - const trie = TernarySearchTree.forStrings<number>(); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('foobaz', 3); - assertTstDfs(trie, ['foo', 1], ['foobar', 2], ['foobaz', 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 () { - const trie = new TernarySearchTree<string, number>(new StringIterator()); - - trie.set('foo', 1); - trie.set('bar', 2); - trie.set('foobar', 3); - assertTstDfs(trie, ['bar', 2], ['foo', 1], ['foobar', 3]); - - 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.strictEqual(trie.findSubstr('fo'), undefined); - assert.strictEqual(trie.findSubstr('foo'), 1); - assert.strictEqual(trie.findSubstr('foooo'), 1); - - - trie.delete('foobar'); - trie.delete('bar'); - assert.strictEqual(trie.get('foobar'), undefined); - assert.strictEqual(trie.get('bar'), undefined); - - trie.set('foobar', 17); - trie.set('barr', 18); - assert.strictEqual(trie.get('foobar'), 17); - assert.strictEqual(trie.get('barr'), 18); - assert.strictEqual(trie.get('bar'), undefined); - }); - - test('TernarySearchTree - delete & cleanup', function () { - // normal delete - let trie = new TernarySearchTree<string, number>(new StringIterator()); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('bar', 3); - assertTstDfs(trie, ['bar', 3], ['foo', 1], ['foobar', 2]); - trie.delete('foo'); - assertTstDfs(trie, ['bar', 3], ['foobar', 2]); - trie.delete('foobar'); - assertTstDfs(trie, ['bar', 3]); - - // superstr-delete - trie = new TernarySearchTree<string, number>(new StringIterator()); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('bar', 3); - trie.set('foobarbaz', 4); - trie.deleteSuperstr('foo'); - assertTstDfs(trie, ['bar', 3], ['foo', 1]); - - trie = new TernarySearchTree<string, number>(new StringIterator()); - trie.set('foo', 1); - trie.set('foobar', 2); - trie.set('bar', 3); - trie.set('foobarbaz', 4); - trie.deleteSuperstr('fo'); - assertTstDfs(trie, ['bar', 3]); - - // trie = new TernarySearchTree<string, number>(new StringIterator()); - // trie.set('foo', 1); - // trie.set('foobar', 2); - // trie.set('bar', 3); - // trie.deleteSuperStr('f'); - // assertTernarySearchTree(trie, ['bar', 3]); - }); - - test('TernarySearchTree (PathSegments) - basics', function () { - const trie = new TernarySearchTree<string, number>(new PathIterator()); - - trie.set('/user/foo/bar', 1); - trie.set('/user/foo', 2); - trie.set('/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.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 - (AVL) set', function () { - { - // rotate left - const trie = new TernarySearchTree<string, number>(new PathIterator()); - trie.set('/fileA', 1); - trie.set('/fileB', 2); - trie.set('/fileC', 3); - assertTstDfs(trie, ['/fileA', 1], ['/fileB', 2], ['/fileC', 3]); - } - - { - // rotate left (inside middle) - const trie = new TernarySearchTree<string, number>(new PathIterator()); - trie.set('/foo/fileA', 1); - trie.set('/foo/fileB', 2); - trie.set('/foo/fileC', 3); - assertTstDfs(trie, ['/foo/fileA', 1], ['/foo/fileB', 2], ['/foo/fileC', 3]); - } - - { - // rotate right - const trie = new TernarySearchTree<string, number>(new PathIterator()); - trie.set('/fileC', 3); - trie.set('/fileB', 2); - trie.set('/fileA', 1); - assertTstDfs(trie, ['/fileA', 1], ['/fileB', 2], ['/fileC', 3]); - } - - { - // rotate right (inside middle) - const trie = new TernarySearchTree<string, number>(new PathIterator()); - trie.set('/mid/fileC', 3); - trie.set('/mid/fileB', 2); - trie.set('/mid/fileA', 1); - assertTstDfs(trie, ['/mid/fileA', 1], ['/mid/fileB', 2], ['/mid/fileC', 3]); - } - - { - // rotate right, left - const trie = new TernarySearchTree<string, number>(new PathIterator()); - trie.set('/fileD', 7); - trie.set('/fileB', 2); - trie.set('/fileG', 42); - trie.set('/fileF', 24); - trie.set('/fileZ', 73); - trie.set('/fileE', 15); - assertTstDfs(trie, ['/fileB', 2], ['/fileD', 7], ['/fileE', 15], ['/fileF', 24], ['/fileG', 42], ['/fileZ', 73]); - } - - { - // rotate left, right - const trie = new TernarySearchTree<string, number>(new PathIterator()); - trie.set('/fileJ', 42); - trie.set('/fileZ', 73); - trie.set('/fileE', 15); - trie.set('/fileB', 2); - trie.set('/fileF', 7); - trie.set('/fileG', 1); - assertTstDfs(trie, ['/fileB', 2], ['/fileE', 15], ['/fileF', 7], ['/fileG', 1], ['/fileJ', 42], ['/fileZ', 73]); - } - }); - - test('TernarySearchTree - (BST) delete', function () { - - const trie = new TernarySearchTree<string, number>(new StringIterator()); - - // delete root - trie.set('d', 1); - assertTstDfs(trie, ['d', 1]); - trie.delete('d'); - assertTstDfs(trie); - - // delete node with two element - trie.clear(); - trie.set('d', 1); - trie.set('b', 1); - trie.set('f', 1); - assertTstDfs(trie, ['b', 1], ['d', 1], ['f', 1]); - trie.delete('d'); - assertTstDfs(trie, ['b', 1], ['f', 1]); - - // single child node - trie.clear(); - trie.set('d', 1); - trie.set('b', 1); - trie.set('f', 1); - trie.set('e', 1); - assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1], ['f', 1]); - trie.delete('f'); - assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1]); - }); - - test('TernarySearchTree - (AVL) delete', function () { - - const trie = new TernarySearchTree<string, number>(new StringIterator()); - - trie.clear(); - trie.set('d', 1); - trie.set('b', 1); - trie.set('f', 1); - trie.set('e', 1); - trie.set('z', 1); - assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1], ['f', 1], ['z', 1]); - - // right, right - trie.delete('b'); - assertTstDfs(trie, ['d', 1], ['e', 1], ['f', 1], ['z', 1]); - - trie.clear(); - trie.set('d', 1); - trie.set('c', 1); - trie.set('f', 1); - trie.set('a', 1); - trie.set('b', 1); - assertTstDfs(trie, ['a', 1], ['b', 1], ['c', 1], ['d', 1], ['f', 1]); - - // left, left - trie.delete('f'); - assertTstDfs(trie, ['a', 1], ['b', 1], ['c', 1], ['d', 1]); - - // mid - trie.clear(); - trie.set('a', 1); - trie.set('ad', 1); - trie.set('ab', 1); - trie.set('af', 1); - trie.set('ae', 1); - trie.set('az', 1); - assertTstDfs(trie, ['a', 1], ['ab', 1], ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); - - trie.delete('ab'); - assertTstDfs(trie, ['a', 1], ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); - - trie.delete('a'); - assertTstDfs(trie, ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); - }); - - test('TernarySearchTree: Cannot read property \'1\' of undefined #138284', function () { - - const keys = [ - URI.parse('fake-fs:/C'), - URI.parse('fake-fs:/A'), - URI.parse('fake-fs:/D'), - URI.parse('fake-fs:/B'), - ]; - - const tst = TernarySearchTree.forUris<boolean>(); - - for (const item of keys) { - tst.set(item, true); - } - - assert.ok(tst._isBalanced()); - tst.delete(keys[0]); - assert.ok(tst._isBalanced()); - }); - - test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (simple)', function () { - - const keys = ['C', 'A', 'D', 'B',]; - const tst = TernarySearchTree.forStrings<boolean>(); - for (const item of keys) { - tst.set(item, true); - } - assertTstDfs(tst, ['A', true], ['B', true], ['C', true], ['D', true]); - - tst.delete(keys[0]); - assertTstDfs(tst, ['A', true], ['B', true], ['D', true]); - - { - const tst = TernarySearchTree.forStrings<boolean>(); - tst.set('C', true); - tst.set('A', true); - tst.set('B', true); - assertTstDfs(tst, ['A', true], ['B', true], ['C', true]); - } - - }); - - test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (random)', function () { - for (let round = 10; round >= 0; round--) { - const keys: URI[] = []; - for (let i = 0; i < 100; i++) { - keys.push(URI.from({ scheme: 'fake-fs', path: randomPath(undefined, undefined, 10) })); - } - const tst = TernarySearchTree.forUris<boolean>(); - - for (const item of keys) { - tst.set(item, true); - assert.ok(tst._isBalanced(), `SET${item}|${keys.map(String).join()}`); - } - - for (const item of keys) { - tst.delete(item); - assert.ok(tst._isBalanced(), `DEL${item}|${keys.map(String).join()}`); - } - } - }); - - test('TernarySearchTree (PathSegments) - lookup', function () { - - const map = new TernarySearchTree<string, number>(new PathIterator()); - map.set('/user/foo/bar', 1); - map.set('/user/foo', 2); - map.set('/user/foo/flip/flop', 3); - - 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 () { - - const map = new TernarySearchTree<string, number>(new PathIterator()); - map.set('/user/foo/bar', 1); - map.set('/user/foo', 2); - map.set('/user/foo/flip/flop', 3); - map.set('/usr/foo', 4); - - let item: IteratorResult<[string, number]>; - let iter = map.findSuperstr('/user'); - - item = iter!.next(); - assert.strictEqual(item.value[1], 2); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value[1], 1); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value[1], 3); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - iter = map.findSuperstr('/usr'); - item = iter!.next(); - assert.strictEqual(item.value[1], 4); - assert.strictEqual(item.done, false); - - item = iter!.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - assert.strictEqual(map.findSuperstr('/not'), undefined); - assert.strictEqual(map.findSuperstr('/us'), undefined); - assert.strictEqual(map.findSuperstr('/usrr'), undefined); - assert.strictEqual(map.findSuperstr('/userr'), undefined); - }); - - - test('TernarySearchTree (PathSegments) - delete_superstr', function () { - - const map = new TernarySearchTree<string, number>(new PathIterator()); - map.set('/user/foo/bar', 1); - map.set('/user/foo', 2); - map.set('/user/foo/flip/flop', 3); - map.set('/usr/foo', 4); - - assertTstDfs(map, - ['/user/foo', 2], - ['/user/foo/bar', 1], - ['/user/foo/flip/flop', 3], - ['/usr/foo', 4], - ); - - // not a segment - map.deleteSuperstr('/user/fo'); - assertTstDfs(map, - ['/user/foo', 2], - ['/user/foo/bar', 1], - ['/user/foo/flip/flop', 3], - ['/usr/foo', 4], - ); - - // delete a segment - map.set('/user/foo/bar', 1); - map.set('/user/foo', 2); - map.set('/user/foo/flip/flop', 3); - map.set('/usr/foo', 4); - map.deleteSuperstr('/user/foo'); - assertTstDfs(map, - ['/user/foo', 2], - ['/usr/foo', 4], - ); - }); - - test('TernarySearchTree (URI) - basics', function () { - const trie = new TernarySearchTree<URI, number>(new UriIterator(() => false, () => false)); - - trie.set(URI.file('/user/foo/bar'), 1); - trie.set(URI.file('/user/foo'), 2); - trie.set(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.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) - query parameters', function () { - const trie = new TernarySearchTree<URI, number>(new UriIterator(() => false, () => true)); - const root = URI.parse('memfs:/?param=1'); - trie.set(root, 1); - - assert.strictEqual(trie.get(URI.parse('memfs:/?param=1')), 1); - - assert.strictEqual(trie.findSubstr(URI.parse('memfs:/?param=1')), 1); - assert.strictEqual(trie.findSubstr(URI.parse('memfs:/aaa?param=1')), 1); - }); - - test('TernarySearchTree (URI) - lookup', function () { - - const map = new TernarySearchTree<URI, number>(new UriIterator(() => false, () => 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); - map.set(URI.parse('http://foo.bar/user/foo/flip/flop'), 3); - - 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<URI, number>(new UriIterator(uri => /^https?$/.test(uri.scheme), () => false)); - map.set(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.strictEqual(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined); - }); - - test('TernarySearchTree (URI) - superstr', function () { - - const map = new TernarySearchTree<URI, number>(new UriIterator(() => false, () => 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<[URI, number]>; - let iter = map.findSuperstr(URI.file('/user'))!; - - item = iter.next(); - assert.strictEqual(item.value[1], 2); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 1); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 3); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - iter = map.findSuperstr(URI.file('/usr'))!; - item = iter.next(); - assert.strictEqual(item.value[1], 4); - assert.strictEqual(item.done, false); - - item = iter.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - iter = map.findSuperstr(URI.file('/'))!; - item = iter.next(); - assert.strictEqual(item.value[1], 2); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 1); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 3); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value[1], 4); - assert.strictEqual(item.done, false); - item = iter.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - 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 () { - const trie = new TernarySearchTree<string, number>(new ConfigKeysIterator()); - - trie.set('config.foo.bar', 1); - trie.set('config.foo', 2); - trie.set('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.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 () { - - const map = new TernarySearchTree<string, number>(new ConfigKeysIterator()); - map.set('config.foo.bar', 1); - map.set('config.foo', 2); - map.set('config.foo.flip.flop', 3); - - 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 () { - - const map = new TernarySearchTree<string, number>(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]>; - const iter = map.findSuperstr('config'); - - item = iter!.next(); - assert.strictEqual(item.value[1], 2); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value[1], 1); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value[1], 3); - assert.strictEqual(item.done, false); - item = iter!.next(); - assert.strictEqual(item.value, undefined); - assert.strictEqual(item.done, true); - - assert.strictEqual(map.findSuperstr('foo'), undefined); - assert.strictEqual(map.findSuperstr('config.foo.no'), undefined); - assert.strictEqual(map.findSuperstr('config.foop'), undefined); - }); - - - test('TernarySearchTree (ConfigKeySegments) - delete_superstr', function () { - - const map = new TernarySearchTree<string, number>(new ConfigKeysIterator()); - map.set('config.foo.bar', 1); - map.set('config.foo', 2); - map.set('config.foo.flip.flop', 3); - map.set('boo', 4); - - assertTstDfs(map, - ['boo', 4], - ['config.foo', 2], - ['config.foo.bar', 1], - ['config.foo.flip.flop', 3], - ); - - // not a segment - map.deleteSuperstr('config.fo'); - assertTstDfs(map, - ['boo', 4], - ['config.foo', 2], - ['config.foo.bar', 1], - ['config.foo.flip.flop', 3], - ); - - // 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'); - assertTstDfs(map, - ['boo', 4], - ['config.foo', 2], - ); - }); - - test('TST, fill', function () { - const tst = TernarySearchTree.forStrings(); - - const keys = ['foo', 'bar', 'bang', 'bazz']; - Object.freeze(keys); - tst.fill(true, keys); - - for (const key of keys) { - assert.ok(tst.get(key), key); - } - }); - test('ResourceMap - basics', function () { const map = new ResourceMap<any>(); @@ -1336,105 +485,3 @@ suite('Map', () => { }); }); - -suite.skip('TST, perf', function () { - - function createRandomUris(n: number): URI[] { - const uris: URI[] = []; - function randomWord(): string { - let result = ''; - const length = 4 + Math.floor(Math.random() * 4); - for (let i = 0; i < length; i++) { - result += (Math.random() * 26 + 65).toString(36); - } - return result; - } - - // generate 10000 random words - const words: string[] = []; - for (let i = 0; i < 10000; i++) { - words.push(randomWord()); - } - - for (let i = 0; i < n; i++) { - - let len = 4 + Math.floor(Math.random() * 4); - - const segments: string[] = []; - for (; len >= 0; len--) { - segments.push(words[Math.floor(Math.random() * words.length)]); - } - - uris.push(URI.from({ scheme: 'file', path: segments.join('/') })); - } - - return uris; - } - - let tree: TernarySearchTree<URI, boolean>; - let sampleUris: URI[] = []; - let candidates: URI[] = []; - - suiteSetup(() => { - const len = 50_000; - sampleUris = createRandomUris(len); - candidates = [...sampleUris.slice(0, len / 2), ...createRandomUris(len / 2)]; - shuffle(candidates); - }); - - setup(() => { - tree = TernarySearchTree.forUris(); - for (const uri of sampleUris) { - tree.set(uri, true); - } - }); - - const _profile = false; - - function perfTest(name: string, callback: Function) { - test(name, function () { - if (_profile) { console.profile(name); } - const sw = new StopWatch(true); - callback(); - console.log(name, sw.elapsed()); - if (_profile) { console.profileEnd(); } - }); - } - - perfTest('TST, clear', function () { - tree.clear(); - }); - - perfTest('TST, insert', function () { - const insertTree = TernarySearchTree.forUris(); - for (const uri of sampleUris) { - insertTree.set(uri, true); - } - }); - - perfTest('TST, lookup', function () { - let match = 0; - for (const candidate of candidates) { - if (tree.has(candidate)) { - match += 1; - } - } - assert.strictEqual(match, sampleUris.length / 2); - }); - - perfTest('TST, substr', function () { - let match = 0; - for (const candidate of candidates) { - if (tree.findSubstr(candidate)) { - match += 1; - } - } - assert.strictEqual(match, sampleUris.length / 2); - }); - - perfTest('TST, superstr', function () { - for (const candidate of candidates) { - tree.findSuperstr(candidate); - } - }); -}); diff --git a/src/vs/base/test/common/network.test.ts b/src/vs/base/test/common/network.test.ts index f395bb6765..82f3b4da35 100644 --- a/src/vs/base/test/common/network.test.ts +++ b/src/vs/base/test/common/network.test.ts @@ -15,55 +15,55 @@ suite('network', () => { // asCodeUri() & asFileUri(): simple, without authority let originalFileUri = URI.file('network.test.ts'); - let browserUri = FileAccess.asBrowserUri(originalFileUri); + let browserUri = FileAccess.uriToBrowserUri(originalFileUri); assert.ok(browserUri.authority.length > 0); - let fileUri = FileAccess.asFileUri(browserUri); + let fileUri = FileAccess.uriToFileUri(browserUri); 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); + browserUri = FileAccess.uriToBrowserUri(originalFileUri); assert.strictEqual(browserUri.authority, originalFileUri.authority); - fileUri = FileAccess.asFileUri(browserUri); + fileUri = FileAccess.uriToFileUri(browserUri); assert(isEqual(originalFileUri, fileUri)); }); (isWeb ? test.skip : test)('FileAccess: moduleId (native)', () => { - const browserUri = FileAccess.asBrowserUri('vs/base/test/node/network.test', require); + const browserUri = FileAccess.asBrowserUri('vs/base/test/node/network.test'); assert.strictEqual(browserUri.scheme, Schemas.vscodeFileResource); - const fileUri = FileAccess.asFileUri('vs/base/test/node/network.test', require); + const fileUri = FileAccess.asFileUri('vs/base/test/node/network.test'); assert.strictEqual(fileUri.scheme, Schemas.file); }); (isWeb ? test.skip : test)('FileAccess: query and fragment is dropped (native)', () => { const originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' }); - const browserUri = FileAccess.asBrowserUri(originalFileUri); + const browserUri = FileAccess.uriToBrowserUri(originalFileUri); assert.strictEqual(browserUri.query, ''); assert.strictEqual(browserUri.fragment, ''); }); (isWeb ? test.skip : test)('FileAccess: query and fragment is kept if URI is already of same scheme (native)', () => { const originalFileUri = URI.file('network.test.ts').with({ query: 'foo=bar', fragment: 'something' }); - const browserUri = FileAccess.asBrowserUri(originalFileUri.with({ scheme: Schemas.vscodeFileResource })); + const browserUri = FileAccess.uriToBrowserUri(originalFileUri.with({ scheme: Schemas.vscodeFileResource })); assert.strictEqual(browserUri.query, 'foo=bar'); assert.strictEqual(browserUri.fragment, 'something'); - const fileUri = FileAccess.asFileUri(originalFileUri); + const fileUri = FileAccess.uriToFileUri(originalFileUri); assert.strictEqual(fileUri.query, 'foo=bar'); assert.strictEqual(fileUri.fragment, 'something'); }); (isWeb ? test.skip : test)('FileAccess: web', () => { const originalHttpsUri = URI.file('network.test.ts').with({ scheme: 'https' }); - const browserUri = FileAccess.asBrowserUri(originalHttpsUri); + const browserUri = FileAccess.uriToBrowserUri(originalHttpsUri); 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); + const browserUri = FileAccess.uriToBrowserUri(originalRemoteUri); assert.notStrictEqual(originalRemoteUri.scheme, browserUri.scheme); }); }); diff --git a/src/vs/base/test/common/observable.test.ts b/src/vs/base/test/common/observable.test.ts index 7f3120fc58..61139484d4 100644 --- a/src/vs/base/test/common/observable.test.ts +++ b/src/vs/base/test/common/observable.test.ts @@ -5,421 +5,962 @@ import * as assert from 'assert'; import { Emitter } from 'vs/base/common/event'; -import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { ISettableObservable, autorun, derived, ITransaction, observableFromEvent, observableValue, transaction, keepAlive } from 'vs/base/common/observable'; import { BaseObservable, IObservable, IObserver } from 'vs/base/common/observableImpl/base'; -suite('observable integration', () => { - test('basic observable + autorun', () => { - const log = new Log(); - const observable = observableValue('MyObservableValue', 0); +suite('observables', () => { + /** + * Reads these tests to understand how to use observables. + */ + suite('tutorial', () => { + test('observable + autorun', () => { + const log = new Log(); + const myObservable = observableValue('myObservable', 0); - autorun('MyAutorun', (reader) => { - log.log(`value: ${observable.read(reader)}`); - }); - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 0']); + autorun('myAutorun', (reader) => { + log.log(`myAutorun.run(myObservable: ${myObservable.read(reader)})`); + }); + // The autorun runs immediately + assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 0)']); - observable.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 1']); + myObservable.set(1, undefined); + // The autorun runs again when any read observable changed + assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 1)']); - observable.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - transaction((tx) => { - observable.set(2, tx); + myObservable.set(1, undefined); + // But only if the value changed assert.deepStrictEqual(log.getAndClearEntries(), []); - observable.set(3, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); + // Transactions batch autorun runs + transaction((tx) => { + myObservable.set(2, tx); + // No auto-run ran yet, even though the value changed + assert.deepStrictEqual(log.getAndClearEntries(), []); + + myObservable.set(3, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + // Only at the end of the transaction the autorun re-runs + assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 3)']); }); - assert.deepStrictEqual(log.getAndClearEntries(), ['value: 3']); - }); + test('computed + autorun', () => { + const log = new Log(); + const observable1 = observableValue('myObservable1', 0); + const observable2 = observableValue('myObservable2', 0); - test('basic computed + autorun', () => { - const log = new Log(); - const observable1 = observableValue('MyObservableValue1', 0); - const observable2 = observableValue('MyObservableValue2', 0); + const myDerived = derived('myDerived', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`myDerived.recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); - const computed = derived('computed', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - autorun('MyAutorun', (reader) => { - log.log(`value: ${computed.read(reader)}`); - }); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 0 + 0 = 0', - 'value: 0', - ]); - - observable1.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 1 + 0 = 1', - 'value: 1', - ]); - - observable2.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 1 + 1 = 2', - 'value: 2', - ]); - - transaction((tx) => { - observable1.set(5, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable2.set(5, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 5 + 5 = 10', - 'value: 10', - ]); - - transaction((tx) => { - observable1.set(6, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable2.set(4, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), ['recompute: 6 + 4 = 10']); - }); - - test('read during transaction', () => { - const log = new Log(); - const observable1 = observableValue('MyObservableValue1', 0); - const observable2 = observableValue('MyObservableValue2', 0); - - const computed = derived('computed', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); - const sum = value1 + value2; - log.log(`recompute: ${value1} + ${value2} = ${sum}`); - return sum; - }); - - autorun('MyAutorun', (reader) => { - log.log(`value: ${computed.read(reader)}`); - }); - - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: 0 + 0 = 0', - 'value: 0', - ]); - - log.log(`computed is ${computed.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['computed is 0']); - - transaction((tx) => { - observable1.set(-1, tx); - log.log(`computed is ${computed.get()}`); + autorun('myAutorun', (reader) => { + log.log(`myAutorun(myDerived: ${myDerived.read(reader)})`); + }); + // autorun runs immediately assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: -1 + 0 = -1', - 'computed is -1', + "myDerived.recompute: 0 + 0 = 0", + "myAutorun(myDerived: 0)", ]); - log.log(`computed is ${computed.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['computed is -1']); + observable1.set(1, undefined); + // and on changes... + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.recompute: 1 + 0 = 1", + "myAutorun(myDerived: 1)", + ]); - observable2.set(1, tx); - assert.deepStrictEqual(log.getAndClearEntries(), []); + observable2.set(1, undefined); + // ... of any dependency. + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.recompute: 1 + 1 = 2", + "myAutorun(myDerived: 2)", + ]); + + transaction((tx) => { + observable1.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(5, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + // When changing multiple observables in a transaction, + // deriveds are only recomputed on demand. + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.recompute: 5 + 5 = 10", + "myAutorun(myDerived: 10)", + ]); + + transaction((tx) => { + observable1.set(6, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable2.set(4, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + // Now the autorun didn't run again, because its dependency changed from 10 to 10 (= no change). + assert.deepStrictEqual(log.getAndClearEntries(), (["myDerived.recompute: 6 + 4 = 10"])); + }); + + test('read during transaction', () => { + const log = new Log(); + const observable1 = observableValue('myObservable1', 0); + const observable2 = observableValue('myObservable2', 0); + + const myDerived = derived('myDerived', (reader) => { + const value1 = observable1.read(reader); + const value2 = observable2.read(reader); + const sum = value1 + value2; + log.log(`myDerived.recompute: ${value1} + ${value2} = ${sum}`); + return sum; + }); + + autorun('myAutorun', (reader) => { + log.log(`myAutorun(myDerived: ${myDerived.read(reader)})`); + }); + // autorun runs immediately + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.recompute: 0 + 0 = 0", + "myAutorun(myDerived: 0)", + ]); + + transaction((tx) => { + observable1.set(-10, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + myDerived.get(); // This forces a (sync) recomputation of the current value + assert.deepStrictEqual(log.getAndClearEntries(), (["myDerived.recompute: -10 + 0 = -10"])); + + observable2.set(10, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + // This autorun runs again, because its dependency changed from 0 to -10 and then back to 0. + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.recompute: -10 + 10 = 0", + "myAutorun(myDerived: 0)", + ]); + }); + + test('get without observers', () => { + const log = new Log(); + const observable1 = observableValue('myObservableValue1', 0); + const computed1 = derived('computed', (reader) => { + const value1 = observable1.read(reader); + const result = value1 % 3; + log.log(`recompute1: ${value1} % 3 = ${result}`); + return result; + }); + const computed2 = derived('computed', (reader) => { + const value1 = computed1.read(reader); + const result = value1 * 2; + log.log(`recompute2: ${value1} * 2 = ${result}`); + return result; + }); + const computed3 = derived('computed', (reader) => { + const value1 = computed1.read(reader); + const result = value1 * 3; + log.log(`recompute3: ${value1} * 3 = ${result}`); + return result; + }); + const computedSum = derived('computed', (reader) => { + const value1 = computed2.read(reader); + const value2 = computed3.read(reader); + const result = value1 + value2; + log.log(`recompute4: ${value1} + ${value2} = ${result}`); + return result; + }); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + observable1.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + + log.log(`value: ${computedSum.get()}`); + // Because there are no observers, the derived values are not cached, but computed from scratch. + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + + const disposable = keepAlive(computedSum); // Use keepAlive to keep the cache + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'recompute1: 1 % 3 = 1', + 'recompute2: 1 * 2 = 2', + 'recompute3: 1 * 3 = 3', + 'recompute4: 2 + 3 = 5', + 'value: 5', + ]); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'value: 5', + ]); + + observable1.set(2, undefined); + // The keep alive does not force deriveds to be recomputed + assert.deepStrictEqual(log.getAndClearEntries(), ([])); + + log.log(`value: ${computedSum.get()}`); + // Those deriveds are recomputed on demand + assert.deepStrictEqual(log.getAndClearEntries(), [ + "recompute1: 2 % 3 = 2", + "recompute2: 2 * 2 = 4", + "recompute3: 2 * 3 = 6", + "recompute4: 4 + 6 = 10", + "value: 10", + ]); + log.log(`value: ${computedSum.get()}`); + // ... and then cached again + assert.deepStrictEqual(log.getAndClearEntries(), (["value: 10"])); + + disposable.dispose(); // Don't forget to dispose the keepAlive to prevent memory leaks + + log.log(`value: ${computedSum.get()}`); + // Which disables the cache again + assert.deepStrictEqual(log.getAndClearEntries(), [ + "recompute1: 2 % 3 = 2", + "recompute2: 2 * 2 = 4", + "recompute3: 2 * 3 = 6", + "recompute4: 4 + 6 = 10", + "value: 10", + ]); + + log.log(`value: ${computedSum.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "recompute1: 2 % 3 = 2", + "recompute2: 2 * 2 = 4", + "recompute3: 2 * 3 = 6", + "recompute4: 4 + 6 = 10", + "value: 10", + ]); }); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute: -1 + 1 = 0', - 'value: 0', - ]); }); test('topological order', () => { const log = new Log(); - const observable1 = observableValue('MyObservableValue1', 0); - const observable2 = observableValue('MyObservableValue2', 0); + const myObservable1 = observableValue('myObservable1', 0); + const myObservable2 = observableValue('myObservable2', 0); - const computed1 = derived('computed1', (reader) => { - const value1 = observable1.read(reader); - const value2 = observable2.read(reader); + const myComputed1 = derived('myComputed1', (reader) => { + const value1 = myObservable1.read(reader); + const value2 = myObservable2.read(reader); const sum = value1 + value2; - log.log(`recompute1: ${value1} + ${value2} = ${sum}`); + log.log(`myComputed1.recompute(myObservable1: ${value1} + myObservable2: ${value2} = ${sum})`); return sum; }); - const computed2 = derived('computed2', (reader) => { - const value1 = computed1.read(reader); - const value2 = observable1.read(reader); - const value3 = observable2.read(reader); + const myComputed2 = derived('myComputed2', (reader) => { + const value1 = myComputed1.read(reader); + const value2 = myObservable1.read(reader); + const value3 = myObservable2.read(reader); const sum = value1 + value2 + value3; - log.log(`recompute2: ${value1} + ${value2} + ${value3} = ${sum}`); + log.log(`myComputed2.recompute(myComputed1: ${value1} + myObservable1: ${value2} + myObservable2: ${value3} = ${sum})`); return sum; }); - const computed3 = derived('computed3', (reader) => { - const value1 = computed2.read(reader); - const value2 = observable1.read(reader); - const value3 = observable2.read(reader); + const myComputed3 = derived('myComputed3', (reader) => { + const value1 = myComputed2.read(reader); + const value2 = myObservable1.read(reader); + const value3 = myObservable2.read(reader); const sum = value1 + value2 + value3; - log.log(`recompute3: ${value1} + ${value2} + ${value3} = ${sum}`); + log.log(`myComputed3.recompute(myComputed2: ${value1} + myObservable1: ${value2} + myObservable2: ${value3} = ${sum})`); return sum; }); - autorun('MyAutorun', (reader) => { - log.log(`value: ${computed3.read(reader)}`); + autorun('myAutorun', (reader) => { + log.log(`myAutorun.run(myComputed3: ${myComputed3.read(reader)})`); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 0 + 0 = 0', - 'recompute2: 0 + 0 + 0 = 0', - 'recompute3: 0 + 0 + 0 = 0', - 'value: 0', + "myComputed1.recompute(myObservable1: 0 + myObservable2: 0 = 0)", + "myComputed2.recompute(myComputed1: 0 + myObservable1: 0 + myObservable2: 0 = 0)", + "myComputed3.recompute(myComputed2: 0 + myObservable1: 0 + myObservable2: 0 = 0)", + "myAutorun.run(myComputed3: 0)", ]); - observable1.set(1, undefined); + myObservable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 + 0 = 1', - 'recompute2: 1 + 1 + 0 = 2', - 'recompute3: 2 + 1 + 0 = 3', - 'value: 3', + "myComputed1.recompute(myObservable1: 1 + myObservable2: 0 = 1)", + "myComputed2.recompute(myComputed1: 1 + myObservable1: 1 + myObservable2: 0 = 2)", + "myComputed3.recompute(myComputed2: 2 + myObservable1: 1 + myObservable2: 0 = 3)", + "myAutorun.run(myComputed3: 3)", ]); transaction((tx) => { - observable1.set(2, tx); - log.log(`computed2: ${computed2.get()}`); + myObservable1.set(2, tx); + myComputed2.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 2 + 0 = 2', - 'recompute2: 2 + 2 + 0 = 4', - 'computed2: 4', + "myComputed1.recompute(myObservable1: 2 + myObservable2: 0 = 2)", + "myComputed2.recompute(myComputed1: 2 + myObservable1: 2 + myObservable2: 0 = 4)", ]); - observable1.set(3, tx); - log.log(`computed2: ${computed2.get()}`); + myObservable1.set(3, tx); + myComputed2.get(); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 3 + 0 = 3', - 'recompute2: 3 + 3 + 0 = 6', - 'computed2: 6', + "myComputed1.recompute(myObservable1: 3 + myObservable2: 0 = 3)", + "myComputed2.recompute(myComputed1: 3 + myObservable1: 3 + myObservable2: 0 = 6)", ]); }); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute3: 6 + 3 + 0 = 9', - 'value: 9', + "myComputed3.recompute(myComputed2: 6 + myObservable1: 3 + myObservable2: 0 = 9)", + "myAutorun.run(myComputed3: 9)", ]); }); + suite('from event', () => { + + function init(): { log: Log; setValue: (value: number | undefined) => void; observable: IObservable<number | undefined> } { + const log = new Log(); + + let value: number | undefined = 0; + const eventEmitter = new Emitter<void>(); + + let id = 0; + const observable = observableFromEvent( + (handler) => { + const curId = id++; + log.log(`subscribed handler ${curId}`); + const disposable = eventEmitter.event(handler); + + return { + dispose: () => { + log.log(`unsubscribed handler ${curId}`); + disposable.dispose(); + }, + }; + }, + () => { + log.log(`compute value ${value}`); + return value; + } + ); + + return { + log, + setValue: (newValue) => { + value = newValue; + eventEmitter.fire(); + }, + observable, + }; + } + + test('Handle undefined', () => { + const { log, setValue, observable } = init(); + + setValue(undefined); + + const autorunDisposable = autorun('MyAutorun', (reader) => { + observable.read(reader); + log.log( + `autorun, value: ${observable.read(reader)}` + ); + }); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + "subscribed handler 0", + "compute value undefined", + "autorun, value: undefined", + ]); + + setValue(1); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + "compute value 1", + "autorun, value: 1" + ]); + + autorunDisposable.dispose(); + + assert.deepStrictEqual(log.getAndClearEntries(), [ + "unsubscribed handler 0" + ]); + }); + + test('basic', () => { + const { log, setValue, observable } = init(); + + const shouldReadObservable = observableValue('shouldReadObservable', true); + + const autorunDisposable = autorun('MyAutorun', (reader) => { + if (shouldReadObservable.read(reader)) { + observable.read(reader); + log.log( + `autorun, should read: true, value: ${observable.read(reader)}` + ); + } else { + log.log(`autorun, should read: false`); + } + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 0', + 'compute value 0', + 'autorun, should read: true, value: 0', + ]); + + // Cached get + log.log(`get value: ${observable.get()}`); + assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); + + setValue(1); + // Trigger autorun, no unsub/sub + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + // Unsubscribe when not read + shouldReadObservable.set(false, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'autorun, should read: false', + 'unsubscribed handler 0', + ]); + + shouldReadObservable.set(true, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'subscribed handler 1', + 'compute value 1', + 'autorun, should read: true, value: 1', + ]); + + autorunDisposable.dispose(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'unsubscribed handler 1', + ]); + }); + + test('get without observers', () => { + const { log, observable } = init(); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + log.log(`get value: ${observable.get()}`); + // Not cached or subscribed + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + + log.log(`get value: ${observable.get()}`); + // Still not cached or subscribed + assert.deepStrictEqual(log.getAndClearEntries(), [ + 'compute value 0', + 'get value: 0', + ]); + }); + }); + + test('reading derived in transaction unsubscribes unnecessary observables', () => { + const log = new Log(); + + const shouldReadObservable = observableValue('shouldReadMyObs1', true); + const myObs1 = new LoggingObservableValue('myObs1', 0, log); + const myComputed = derived('myComputed', reader => { + log.log('myComputed.recompute'); + if (shouldReadObservable.read(reader)) { + return myObs1.read(reader); + } + return 1; + }); + autorun('myAutorun', reader => { + const value = myComputed.read(reader); + log.log(`myAutorun: ${value}`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myComputed.recompute", + "myObs1.firstObserverAdded", + "myObs1.get", + "myAutorun: 0", + ]); + + transaction(tx => { + myObs1.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), (["myObs1.set (value 1)"])); + + shouldReadObservable.set(false, tx); + assert.deepStrictEqual(log.getAndClearEntries(), ([])); + + myComputed.get(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myComputed.recompute", + "myObs1.lastObserverRemoved", + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), (["myAutorun: 1"])); + }); + + test('avoid recomputation of deriveds that are no longer read', () => { + const log = new Log(); + + const myObsShouldRead = new LoggingObservableValue('myObsShouldRead', true, log); + const myObs1 = new LoggingObservableValue('myObs1', 0, log); + + const myComputed1 = derived('myComputed1', reader => { + const myObs1Val = myObs1.read(reader); + const result = myObs1Val % 10; + log.log(`myComputed1(myObs1: ${myObs1Val}): Computed ${result}`); + return myObs1Val; + }); + + autorun('myAutorun', reader => { + const shouldRead = myObsShouldRead.read(reader); + if (shouldRead) { + const v = myComputed1.read(reader); + log.log(`myAutorun(shouldRead: true, myComputed1: ${v}): run`); + } else { + log.log(`myAutorun(shouldRead: false): run`); + } + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObsShouldRead.firstObserverAdded", + "myObsShouldRead.get", + "myObs1.firstObserverAdded", + "myObs1.get", + "myComputed1(myObs1: 0): Computed 0", + "myAutorun(shouldRead: true, myComputed1: 0): run", + ]); + + transaction(tx => { + myObsShouldRead.set(false, tx); + myObs1.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObsShouldRead.set (value false)", + "myObs1.set (value 1)", + ]); + }); + // myComputed1 should not be recomputed here, even though its dependency myObs1 changed! + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObsShouldRead.get", + "myAutorun(shouldRead: false): run", + "myObs1.lastObserverRemoved", + ]); + + transaction(tx => { + myObsShouldRead.set(true, tx); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObsShouldRead.set (value true)", + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObsShouldRead.get", + "myObs1.firstObserverAdded", + "myObs1.get", + "myComputed1(myObs1: 1): Computed 1", + "myAutorun(shouldRead: true, myComputed1: 1): run", + ]); + }); + + suite('autorun rerun on neutral change', () => { + test('autorun reruns on neutral observable double change', () => { + const log = new Log(); + const myObservable = observableValue('myObservable', 0); + + autorun('myAutorun', (reader) => { + log.log(`myAutorun.run(myObservable: ${myObservable.read(reader)})`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 0)']); + + + transaction((tx) => { + myObservable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + myObservable.set(0, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + assert.deepStrictEqual(log.getAndClearEntries(), ['myAutorun.run(myObservable: 0)']); + }); + + test('autorun does not rerun on indirect neutral observable double change', () => { + const log = new Log(); + const myObservable = observableValue('myObservable', 0); + const myDerived = derived('myDerived', (reader) => { + const val = myObservable.read(reader); + log.log(`myDerived.read(myObservable: ${val})`); + return val; + }); + + autorun('myAutorun', (reader) => { + log.log(`myAutorun.run(myDerived: ${myDerived.read(reader)})`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.read(myObservable: 0)", + "myAutorun.run(myDerived: 0)" + ]); + + transaction((tx) => { + myObservable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + myObservable.set(0, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.read(myObservable: 0)" + ]); + }); + + test('autorun reruns on indirect neutral observable double change when changes propagate', () => { + const log = new Log(); + const myObservable = observableValue('myObservable', 0); + const myDerived = derived('myDerived', (reader) => { + const val = myObservable.read(reader); + log.log(`myDerived.read(myObservable: ${val})`); + return val; + }); + + autorun('myAutorun', (reader) => { + log.log(`myAutorun.run(myDerived: ${myDerived.read(reader)})`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.read(myObservable: 0)", + "myAutorun.run(myDerived: 0)" + ]); + + transaction((tx) => { + myObservable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + + myDerived.get(); // This marks the auto-run as changed + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.read(myObservable: 2)" + ]); + + myObservable.set(0, tx); + assert.deepStrictEqual(log.getAndClearEntries(), []); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myDerived.read(myObservable: 0)", + "myAutorun.run(myDerived: 0)" + ]); + }); + }); + test('self-disposing autorun', () => { const log = new Log(); - const observable1 = new LoggingObservableValue('MyObservableValue1', 0, log); - const observable2 = new LoggingObservableValue('MyObservableValue2', 0, log); - const observable3 = new LoggingObservableValue('MyObservableValue3', 0, log); + const observable1 = new LoggingObservableValue('myObservable1', 0, log); + const myObservable2 = new LoggingObservableValue('myObservable2', 0, log); + const myObservable3 = new LoggingObservableValue('myObservable3', 0, log); const d = autorun('autorun', (reader) => { if (observable1.read(reader) >= 2) { - observable2.read(reader); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable1.set (value 2)", + "myObservable1.get", + ]); + + myObservable2.read(reader); + // First time this observable is read + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable2.firstObserverAdded", + "myObservable2.get", + ]); + d.dispose(); - observable3.read(reader); + // Disposing removes all observers + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable1.lastObserverRemoved", + "myObservable2.lastObserverRemoved", + ]); + + myObservable3.read(reader); + // This does not subscribe the observable, because the autorun is disposed + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable3.get", + ]); } }); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'MyObservableValue1.firstObserverAdded', - 'MyObservableValue1.get', + 'myObservable1.firstObserverAdded', + 'myObservable1.get', ]); observable1.set(1, undefined); assert.deepStrictEqual(log.getAndClearEntries(), [ - 'MyObservableValue1.set (value 1)', - 'MyObservableValue1.get', + 'myObservable1.set (value 1)', + 'myObservable1.get', ]); observable1.set(2, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'MyObservableValue1.set (value 2)', - 'MyObservableValue1.get', - 'MyObservableValue2.firstObserverAdded', - 'MyObservableValue2.get', - 'MyObservableValue1.lastObserverRemoved', - 'MyObservableValue2.lastObserverRemoved', - 'MyObservableValue3.get', - ]); + // See asserts in the autorun + assert.deepStrictEqual(log.getAndClearEntries(), ([])); }); - test('from event', () => { + test('changing observables in endUpdate', () => { const log = new Log(); - let value = 0; - const eventEmitter = new Emitter<void>(); + const myObservable1 = new LoggingObservableValue('myObservable1', 0, log); + const myObservable2 = new LoggingObservableValue('myObservable2', 0, log); - let id = 0; - const observable = observableFromEvent( - (handler) => { - const curId = id++; - log.log(`subscribed handler ${curId}`); - const disposable = eventEmitter.event(handler); + const myDerived1 = derived('myDerived1', (reader) => { + const val = myObservable1.read(reader); + log.log(`myDerived1.read(myObservable: ${val})`); + return val; + }); - return { - dispose: () => { - log.log(`unsubscribed handler ${curId}`); - disposable.dispose(); - }, - }; - }, - () => { - log.log(`compute value ${value}`); - return value; + const myDerived2 = derived('myDerived2', (reader) => { + const val = myObservable2.read(reader); + if (val === 1) { + myDerived1.read(reader); } - ); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 0', - 'get value: 0', - ]); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 0', - 'get value: 0', - ]); - - const shouldReadObservable = observableValue('shouldReadObservable', true); - - const autorunDisposable = autorun('MyAutorun', (reader) => { - if (shouldReadObservable.read(reader)) { - observable.read(reader); - log.log( - `autorun, should read: true, value: ${observable.read(reader)}` - ); - } else { - log.log(`autorun, should read: false`); - } - }); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'subscribed handler 0', - 'compute value 0', - 'autorun, should read: true, value: 0', - ]); - - log.log(`get value: ${observable.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), ['get value: 0']); - - value = 1; - eventEmitter.fire(); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'compute value 1', - 'autorun, should read: true, value: 1', - ]); - - shouldReadObservable.set(false, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'autorun, should read: false', - 'unsubscribed handler 0', - ]); - - shouldReadObservable.set(true, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'subscribed handler 1', - 'compute value 1', - 'autorun, should read: true, value: 1', - ]); - - autorunDisposable.dispose(); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'unsubscribed handler 1', - ]); - }); - - test('get without observers', () => { - // Maybe this scenario should not be supported. - - const log = new Log(); - const observable1 = observableValue('MyObservableValue1', 0); - const computed1 = derived('computed', (reader) => { - const value1 = observable1.read(reader); - const result = value1 % 3; - log.log(`recompute1: ${value1} % 3 = ${result}`); - return result; - }); - const computed2 = derived('computed', (reader) => { - const value1 = computed1.read(reader); - - const result = value1 * 2; - log.log(`recompute2: ${value1} * 2 = ${result}`); - return result; - }); - const computed3 = derived('computed', (reader) => { - const value1 = computed1.read(reader); - - const result = value1 * 3; - log.log(`recompute3: ${value1} * 3 = ${result}`); - return result; - }); - const computedSum = derived('computed', (reader) => { - const value1 = computed2.read(reader); - const value2 = computed3.read(reader); - - const result = value1 + value2; - log.log(`recompute4: ${value1} + ${value2} = ${result}`); - return result; - }); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - observable1.set(1, undefined); - assert.deepStrictEqual(log.getAndClearEntries(), []); - - log.log(`value: ${computedSum.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 % 3 = 1', - 'recompute2: 1 * 2 = 2', - 'recompute3: 1 * 3 = 3', - 'recompute4: 2 + 3 = 5', - 'value: 5', - ]); - - log.log(`value: ${computedSum.get()}`); - assert.deepStrictEqual(log.getAndClearEntries(), [ - 'recompute1: 1 % 3 = 1', - 'recompute2: 1 * 2 = 2', - 'recompute3: 1 * 3 = 3', - 'recompute4: 2 + 3 = 5', - 'value: 5', - ]); - }); -}); - -suite('observable details', () => { - test('1', () => { - const log = new Log(); - - const shouldReadObservable = observableValue('shouldReadObservable', true); - const observable = new LoggingObservableValue('observable', 0, log); - const computed = derived('test', reader => { - if (shouldReadObservable.read(reader)) { - return observable.read(reader) * 2; - } - return 1; - }); - autorun('test', reader => { - const value = computed.read(reader); - log.log(`autorun: ${value}`); + log.log(`myDerived2.read(myObservable: ${val})`); + return val; }); - assert.deepStrictEqual(log.getAndClearEntries(), (["observable.firstObserverAdded", "observable.get", "autorun: 0"])); + autorun('myAutorun', (reader) => { + const myDerived1Val = myDerived1.read(reader); + const myDerived2Val = myDerived2.read(reader); + log.log(`myAutorun.run(myDerived1: ${myDerived1Val}, myDerived2: ${myDerived2Val})`); + }); transaction(tx => { - observable.set(1, tx); - assert.deepStrictEqual(log.getAndClearEntries(), (["observable.set (value 1)"])); - - shouldReadObservable.set(false, tx); - assert.deepStrictEqual(log.getAndClearEntries(), ([])); - - computed.get(); - assert.deepStrictEqual(log.getAndClearEntries(), (["observable.lastObserverRemoved"])); + myObservable2.set(1, tx); + // end update of this observable will trigger endUpdate of myDerived1 and + // the autorun and the autorun will add myDerived2 as observer to myDerived1 + myObservable1.set(1, tx); + }); + }); + + test('set dependency in derived', () => { + const log = new Log(); + + const myObservable = new LoggingObservableValue('myObservable', 0, log); + const myComputed = derived('myComputed', reader => { + let value = myObservable.read(reader); + const origValue = value; + log.log(`myComputed(myObservable: ${origValue}): start computing`); + if (value % 3 !== 0) { + value++; + myObservable.set(value, undefined); + } + log.log(`myComputed(myObservable: ${origValue}): finished computing`); + return value; + }); + + autorun('myAutorun', reader => { + const value = myComputed.read(reader); + log.log(`myAutorun(myComputed: ${value})`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.firstObserverAdded", + "myObservable.get", + "myComputed(myObservable: 0): start computing", + "myComputed(myObservable: 0): finished computing", + "myAutorun(myComputed: 0)" + ]); + + myObservable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.set (value 1)", + "myObservable.get", + "myComputed(myObservable: 1): start computing", + "myObservable.set (value 2)", + "myComputed(myObservable: 1): finished computing", + "myObservable.get", + "myComputed(myObservable: 2): start computing", + "myObservable.set (value 3)", + "myComputed(myObservable: 2): finished computing", + "myObservable.get", + "myComputed(myObservable: 3): start computing", + "myComputed(myObservable: 3): finished computing", + "myAutorun(myComputed: 3)", + ]); + }); + + test('set dependency in autorun', () => { + const log = new Log(); + const myObservable = new LoggingObservableValue('myObservable', 0, log); + + autorun('myAutorun', reader => { + const value = myObservable.read(reader); + log.log(`myAutorun(myObservable: ${value}): start`); + if (value !== 0 && value < 4) { + myObservable.set(value + 1, undefined); + } + log.log(`myAutorun(myObservable: ${value}): end`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.firstObserverAdded", + "myObservable.get", + "myAutorun(myObservable: 0): start", + "myAutorun(myObservable: 0): end", + ]); + + myObservable.set(1, undefined); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.set (value 1)", + "myObservable.get", + "myAutorun(myObservable: 1): start", + "myObservable.set (value 2)", + "myAutorun(myObservable: 1): end", + "myObservable.get", + "myAutorun(myObservable: 2): start", + "myObservable.set (value 3)", + "myAutorun(myObservable: 2): end", + "myObservable.get", + "myAutorun(myObservable: 3): start", + "myObservable.set (value 4)", + "myAutorun(myObservable: 3): end", + "myObservable.get", + "myAutorun(myObservable: 4): start", + "myAutorun(myObservable: 4): end", + ]); + }); + + test('get in transaction between sets', () => { + const log = new Log(); + const myObservable = new LoggingObservableValue('myObservable', 0, log); + + const myDerived1 = derived('myDerived1', reader => { + const value = myObservable.read(reader); + log.log(`myDerived1(myObservable: ${value}): start computing`); + return value; + }); + + const myDerived2 = derived('myDerived2', reader => { + const value = myDerived1.read(reader); + log.log(`myDerived2(myDerived1: ${value}): start computing`); + return value; + }); + + autorun('myAutorun', reader => { + const value = myDerived2.read(reader); + log.log(`myAutorun(myDerived2: ${value})`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.firstObserverAdded", + "myObservable.get", + "myDerived1(myObservable: 0): start computing", + "myDerived2(myDerived1: 0): start computing", + "myAutorun(myDerived2: 0)", + ]); + + transaction(tx => { + myObservable.set(1, tx); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.set (value 1)", + ]); + + myDerived2.get(); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.get", + "myDerived1(myObservable: 1): start computing", + "myDerived2(myDerived1: 1): start computing", + ]); + + myObservable.set(2, tx); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.set (value 2)", + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable.get", + "myDerived1(myObservable: 2): start computing", + "myDerived2(myDerived1: 2): start computing", + "myAutorun(myDerived2: 2)", + ]); + }); + + test('bug: Dont reset states', () => { + const log = new Log(); + const myObservable1 = new LoggingObservableValue('myObservable1', 0, log); + + const myObservable2 = new LoggingObservableValue('myObservable2', 0, log); + const myDerived2 = derived('myDerived2', reader => { + const val = myObservable2.read(reader); + log.log(`myDerived2.computed(myObservable2: ${val})`); + return val % 10; + }); + + const myDerived3 = derived('myDerived3', reader => { + const val1 = myObservable1.read(reader); + const val2 = myDerived2.read(reader); + log.log(`myDerived3.computed(myDerived1: ${val1}, myDerived2: ${val2})`); + return `${val1} + ${val2}`; + }); + + autorun('myAutorun', reader => { + const val = myDerived3.read(reader); + log.log(`myAutorun(myDerived3: ${val})`); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable1.firstObserverAdded", + "myObservable1.get", + "myObservable2.firstObserverAdded", + "myObservable2.get", + "myDerived2.computed(myObservable2: 0)", + "myDerived3.computed(myDerived1: 0, myDerived2: 0)", + "myAutorun(myDerived3: 0 + 0)", + ]); + + transaction(tx => { + myObservable1.set(1, tx); // Mark myDerived 3 as stale + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable1.set (value 1)", + ]); + + myObservable2.set(10, tx); // This is a non-change. myDerived3 should not be marked as possibly-depedency-changed! + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable2.set (value 10)", + ]); + }); + assert.deepStrictEqual(log.getAndClearEntries(), [ + "myObservable1.get", + "myObservable2.get", + "myDerived2.computed(myObservable2: 10)", + 'myDerived3.computed(myDerived1: 1, myDerived2: 0)', + 'myAutorun(myDerived3: 1 + 0)', + ]); + }); + + test('bug: Add observable in endUpdate', () => { + const myObservable1 = observableValue('myObservable1', 0); + const myObservable2 = observableValue('myObservable2', 0); + + const myDerived1 = derived('myDerived1', reader => { + return myObservable1.read(reader); + }); + + const myDerived2 = derived('myDerived2', reader => { + return myObservable2.read(reader); + }); + + const myDerivedA1 = derived('myDerivedA1', reader => { + const d1 = myDerived1.read(reader); + if (d1 === 1) { + // This adds an observer while myDerived is still in update mode. + // When myDerived exits update mode, the observer shouldn't receive + // more endUpdate than beginUpdate calls. + myDerived2.read(reader); + } + }); + + autorun('myAutorun1', reader => { + myDerivedA1.read(reader); + }); + + autorun('myAutorun2', reader => { + myDerived2.read(reader); + }); + + transaction(tx => { + myObservable1.set(1, tx); + myObservable2.set(1, tx); }); - assert.deepStrictEqual(log.getAndClearEntries(), (["autorun: 1"])); }); }); @@ -433,13 +974,16 @@ export class LoggingObserver implements IObserver { this.count++; this.log.log(`${this.debugName}.beginUpdate (count ${this.count})`); } - handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void { - this.log.log(`${this.debugName}.handleChange (count ${this.count})`); - } endUpdate<T>(observable: IObservable<T, void>): void { this.log.log(`${this.debugName}.endUpdate (count ${this.count})`); this.count--; } + handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void { + this.log.log(`${this.debugName}.handleChange (count ${this.count})`); + } + handlePossibleChange<T>(observable: IObservable<T, unknown>): void { + this.log.log(`${this.debugName}.handlePossibleChange`); + } } export class LoggingObservableValue<T, TChange = void> diff --git a/src/vs/base/test/common/path.test.ts b/src/vs/base/test/common/path.test.ts index 80506077ed..6d3dbdecae 100644 --- a/src/vs/base/test/common/path.test.ts +++ b/src/vs/base/test/common/path.test.ts @@ -360,7 +360,7 @@ suite('Paths (Node Implementation)', () => { assert.strictEqual(path.extname('far.boo/boo'), ''); }); - (isWeb && isWindows ? test.skip : test)('resolve', () => { // TODO@sbatten fails on windows & browser only + test('resolve', () => { const failures = [] as string[]; const slashRE = /\//g; const backslashRE = /\\/g; @@ -372,7 +372,6 @@ suite('Paths (Node Implementation)', () => { [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'], [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], - [['.'], process.cwd()], [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], [['c:/', '//'], 'c:\\'], [['c:/', '//dir'], 'c:\\dir'], @@ -387,12 +386,16 @@ suite('Paths (Node Implementation)', () => { // arguments result [[['/var/lib', '../', 'file/'], '/var/file'], [['/var/lib', '/../', 'file/'], '/file'], - [['a/b/c/', '../../..'], process.cwd()], - [['.'], process.cwd()], [['/some/dir', '.', '/absolute/'], '/absolute'], [['/foo/tmp.3/', '../tmp.3/cycles/root.js'], '/foo/tmp.3/cycles/root.js'] ] + ], + [(isWeb ? path.posix.resolve : path.resolve), + // arguments result + [[['.'], process.cwd()], + [['a/b/c', '../../..'], process.cwd()] ] + ], ]; resolveTests.forEach((test) => { const resolve = test[0]; diff --git a/src/vs/base/test/common/processes.test.ts b/src/vs/base/test/common/processes.test.ts index 8d9badfa6c..3e29c7b8c5 100644 --- a/src/vs/base/test/common/processes.test.ts +++ b/src/vs/base/test/common/processes.test.ts @@ -19,7 +19,7 @@ suite('Processes', () => { VSCODE_DEV: 'x', VSCODE_IPC_HOOK: 'x', VSCODE_NLS_CONFIG: 'x', - VSCODE_PORTABLE: 'x', + VSCODE_PORTABLE: '3', VSCODE_PID: 'x', VSCODE_SHELL_LOGIN: '1', VSCODE_CODE_CACHE_PATH: 'x', @@ -30,6 +30,7 @@ suite('Processes', () => { processes.sanitizeProcessEnvironment(env); assert.strictEqual(env['FOO'], 'bar'); assert.strictEqual(env['VSCODE_SHELL_LOGIN'], '1'); - assert.strictEqual(Object.keys(env).length, 2); + assert.strictEqual(env['VSCODE_PORTABLE'], '3'); + assert.strictEqual(Object.keys(env).length, 3); }); }); diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index bf55a12004..5c5ba367e4 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -395,4 +395,130 @@ suite('Strings', () => { return `${i++}${after}`; }), 'a0ca1ca2ca3c'); }); + + test('removeAnsiEscapeCodes', () => { + const CSI = '\x1b\['; + const sequences = [ + // Base cases from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_ + `${CSI}42@`, + `${CSI}42 @`, + `${CSI}42A`, + `${CSI}42 A`, + `${CSI}42B`, + `${CSI}42C`, + `${CSI}42D`, + `${CSI}42E`, + `${CSI}42F`, + `${CSI}42G`, + `${CSI}42;42H`, + `${CSI}42I`, + `${CSI}42J`, + `${CSI}?42J`, + `${CSI}42K`, + `${CSI}?42K`, + `${CSI}42L`, + `${CSI}42M`, + `${CSI}42P`, + `${CSI}#P`, + `${CSI}3#P`, + `${CSI}#Q`, + `${CSI}3#Q`, + `${CSI}#R`, + `${CSI}42S`, + `${CSI}?1;2;3S`, + `${CSI}42T`, + `${CSI}42;42;42;42;42T`, + `${CSI}>3T`, + `${CSI}42X`, + `${CSI}42Z`, + `${CSI}42^`, + `${CSI}42\``, + `${CSI}42a`, + `${CSI}42b`, + `${CSI}42c`, + `${CSI}=42c`, + `${CSI}>42c`, + `${CSI}42d`, + `${CSI}42e`, + `${CSI}42;42f`, + `${CSI}42g`, + `${CSI}3h`, + `${CSI}?3h`, + `${CSI}42i`, + `${CSI}?42i`, + `${CSI}3l`, + `${CSI}?3l`, + `${CSI}3m`, + `${CSI}>0;0m`, + `${CSI}>0m`, + `${CSI}?0m`, + `${CSI}42n`, + `${CSI}>42n`, + `${CSI}?42n`, + `${CSI}>42p`, + `${CSI}!p`, + `${CSI}0;0"p`, + `${CSI}42$p`, + `${CSI}?42$p`, + `${CSI}#p`, + `${CSI}3#p`, + `${CSI}>42q`, + `${CSI}42q`, + `${CSI}42 q`, + `${CSI}42"q`, + `${CSI}#q`, + `${CSI}42;42r`, + `${CSI}?3r`, + `${CSI}0;0;0;0;3$r`, + `${CSI}s`, + `${CSI}0;0s`, + `${CSI}>42s`, + `${CSI}?3s`, + `${CSI}42;42;42t`, + `${CSI}>3t`, + `${CSI}42 t`, + `${CSI}0;0;0;0;3$t`, + `${CSI}u`, + `${CSI}42 u`, + `${CSI}0;0;0;0;0;0;0;0$v`, + `${CSI}42$w`, + `${CSI}0;0;0;0'w`, + `${CSI}42x`, + `${CSI}42*x`, + `${CSI}0;0;0;0;0$x`, + `${CSI}42#y`, + `${CSI}0;0;0;0;0;0*y`, + `${CSI}42;0'z`, + `${CSI}0;1;2;4$z`, + `${CSI}3'{`, + `${CSI}#{`, + `${CSI}3#{`, + `${CSI}0;0;0;0\${`, + `${CSI}0;0;0;0#|`, + `${CSI}42$|`, + `${CSI}42'|`, + `${CSI}42*|`, + `${CSI}#}`, + `${CSI}42'}`, + `${CSI}42$}`, + `${CSI}42'~`, + `${CSI}42$~`, + + // Common SGR cases: + `${CSI}1;31m`, // multiple attrs + `${CSI}105m`, // bright background + `${CSI}48:5:128m`, // 256 indexed color + `${CSI}48;5;128m`, // 256 indexed color alt + `${CSI}38:2:0:255:255:255m`, // truecolor + `${CSI}38;2;255;255;255m`, // truecolor alt + + // Custom sequences: + '\x1b]633;SetMark;\x07', + '\x1b]633;P;Cwd=/foo\x07', + ]; + + for (const sequence of sequences) { + assert.strictEqual(strings.removeAnsiEscapeCodes(`hello${sequence}world`), 'helloworld', `expect to remove ${JSON.stringify(sequence)}`); + } + }); }); diff --git a/src/vs/base/test/common/ternarySearchtree.test.ts b/src/vs/base/test/common/ternarySearchtree.test.ts new file mode 100644 index 0000000000..a334d6ed59 --- /dev/null +++ b/src/vs/base/test/common/ternarySearchtree.test.ts @@ -0,0 +1,1007 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { shuffle } from 'vs/base/common/arrays'; +import { randomPath } from 'vs/base/common/extpath'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { ConfigKeysIterator, PathIterator, StringIterator, TernarySearchTree, UriIterator } from 'vs/base/common/ternarySearchTree'; +import { URI } from 'vs/base/common/uri'; + +suite('Ternary Search Tree', () => { + + test('PathIterator', () => { + const iter = new PathIterator(); + iter.reset('file:///usr/bin/file.txt'); + + 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.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + + iter.next(); + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + + iter.next(); + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); + + iter.next(); + assert.strictEqual(iter.value(), ''); + assert.strictEqual(iter.hasNext(), false); + iter.next(); + assert.strictEqual(iter.value(), ''); + assert.strictEqual(iter.hasNext(), false); + + // + iter.reset('/foo/bar/'); + assert.strictEqual(iter.value(), 'foo'); + assert.strictEqual(iter.hasNext(), true); + + iter.next(); + assert.strictEqual(iter.value(), 'bar'); + assert.strictEqual(iter.hasNext(), false); + }); + + test('URIIterator', function () { + const iter = new UriIterator(() => false, () => false); + iter.reset(URI.parse('file:///usr/bin/file.txt')); + + 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.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); + + + iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); + + // scheme + 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.strictEqual(iter.value(), 'share'); + assert.strictEqual(iter.cmp('SHARe'), 0); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // query + assert.strictEqual(iter.value(), 'foo'); + assert.strictEqual(iter.cmp('z') > 0, true); + assert.strictEqual(iter.cmp('a') < 0, true); + assert.strictEqual(iter.hasNext(), false); + }); + + test('URIIterator - ignore query/fragment', function () { + const iter = new UriIterator(() => false, () => true); + iter.reset(URI.parse('file:///usr/bin/file.txt')); + + 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.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); + + + iter.reset(URI.parse('file://share/usr/bin/file.txt?foo')); + + // scheme + 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.strictEqual(iter.value(), 'share'); + assert.strictEqual(iter.cmp('SHARe'), 0); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'usr'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'bin'); + assert.strictEqual(iter.hasNext(), true); + iter.next(); + + // path + assert.strictEqual(iter.value(), 'file.txt'); + assert.strictEqual(iter.hasNext(), false); + }); + + function assertTstDfs<E>(trie: TernarySearchTree<string, E>, ...elements: [string, E][]) { + + assert.ok(trie._isBalanced(), 'TST is not balanced'); + + let i = 0; + for (const [key, value] of trie) { + const expected = elements[i++]; + assert.ok(expected); + assert.strictEqual(key, expected[0]); + assert.strictEqual(value, expected[1]); + } + + assert.strictEqual(i, elements.length); + + const map = new Map<string, E>(); + for (const [key, value] of elements) { + map.set(key, value); + } + map.forEach((value, key) => { + assert.strictEqual(trie.get(key), value); + }); + + // forEach + let forEachCount = 0; + trie.forEach((element, key) => { + assert.strictEqual(element, map.get(key)); + forEachCount++; + }); + assert.strictEqual(map.size, forEachCount); + + // iterator + let iterCount = 0; + for (const [key, value] of trie) { + assert.strictEqual(value, map.get(key)); + iterCount++; + } + assert.strictEqual(map.size, iterCount); + + } + + test('TernarySearchTree - set', function () { + + let trie = TernarySearchTree.forStrings<number>(); + trie.set('foobar', 1); + trie.set('foobaz', 2); + + assertTstDfs(trie, ['foobar', 1], ['foobaz', 2]); // longer + + trie = TernarySearchTree.forStrings<number>(); + trie.set('foobar', 1); + trie.set('fooba', 2); + assertTstDfs(trie, ['fooba', 2], ['foobar', 1]); // shorter + + trie = TernarySearchTree.forStrings<number>(); + trie.set('foo', 1); + trie.set('foo', 2); + assertTstDfs(trie, ['foo', 2]); + + trie = TernarySearchTree.forStrings<number>(); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('bar', 3); + trie.set('foob', 4); + trie.set('bazz', 5); + + assertTstDfs(trie, + ['bar', 3], + ['bazz', 5], + ['foo', 1], + ['foob', 4], + ['foobar', 2], + ); + }); + + test('TernarySearchTree - findLongestMatch', function () { + + const trie = TernarySearchTree.forStrings<number>(); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('foobaz', 3); + assertTstDfs(trie, ['foo', 1], ['foobar', 2], ['foobaz', 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 () { + const trie = new TernarySearchTree<string, number>(new StringIterator()); + + trie.set('foo', 1); + trie.set('bar', 2); + trie.set('foobar', 3); + assertTstDfs(trie, ['bar', 2], ['foo', 1], ['foobar', 3]); + + 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.strictEqual(trie.findSubstr('fo'), undefined); + assert.strictEqual(trie.findSubstr('foo'), 1); + assert.strictEqual(trie.findSubstr('foooo'), 1); + + + trie.delete('foobar'); + trie.delete('bar'); + assert.strictEqual(trie.get('foobar'), undefined); + assert.strictEqual(trie.get('bar'), undefined); + + trie.set('foobar', 17); + trie.set('barr', 18); + assert.strictEqual(trie.get('foobar'), 17); + assert.strictEqual(trie.get('barr'), 18); + assert.strictEqual(trie.get('bar'), undefined); + }); + + test('TernarySearchTree - delete & cleanup', function () { + // normal delete + let trie = new TernarySearchTree<string, number>(new StringIterator()); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('bar', 3); + assertTstDfs(trie, ['bar', 3], ['foo', 1], ['foobar', 2]); + trie.delete('foo'); + assertTstDfs(trie, ['bar', 3], ['foobar', 2]); + trie.delete('foobar'); + assertTstDfs(trie, ['bar', 3]); + + // superstr-delete + trie = new TernarySearchTree<string, number>(new StringIterator()); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('bar', 3); + trie.set('foobarbaz', 4); + trie.deleteSuperstr('foo'); + assertTstDfs(trie, ['bar', 3], ['foo', 1]); + + trie = new TernarySearchTree<string, number>(new StringIterator()); + trie.set('foo', 1); + trie.set('foobar', 2); + trie.set('bar', 3); + trie.set('foobarbaz', 4); + trie.deleteSuperstr('fo'); + assertTstDfs(trie, ['bar', 3]); + + // trie = new TernarySearchTree<string, number>(new StringIterator()); + // trie.set('foo', 1); + // trie.set('foobar', 2); + // trie.set('bar', 3); + // trie.deleteSuperStr('f'); + // assertTernarySearchTree(trie, ['bar', 3]); + }); + + test('TernarySearchTree (PathSegments) - basics', function () { + const trie = new TernarySearchTree<string, number>(new PathIterator()); + + trie.set('/user/foo/bar', 1); + trie.set('/user/foo', 2); + trie.set('/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.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 - (AVL) set', function () { + { + // rotate left + const trie = new TernarySearchTree<string, number>(new PathIterator()); + trie.set('/fileA', 1); + trie.set('/fileB', 2); + trie.set('/fileC', 3); + assertTstDfs(trie, ['/fileA', 1], ['/fileB', 2], ['/fileC', 3]); + } + + { + // rotate left (inside middle) + const trie = new TernarySearchTree<string, number>(new PathIterator()); + trie.set('/foo/fileA', 1); + trie.set('/foo/fileB', 2); + trie.set('/foo/fileC', 3); + assertTstDfs(trie, ['/foo/fileA', 1], ['/foo/fileB', 2], ['/foo/fileC', 3]); + } + + { + // rotate right + const trie = new TernarySearchTree<string, number>(new PathIterator()); + trie.set('/fileC', 3); + trie.set('/fileB', 2); + trie.set('/fileA', 1); + assertTstDfs(trie, ['/fileA', 1], ['/fileB', 2], ['/fileC', 3]); + } + + { + // rotate right (inside middle) + const trie = new TernarySearchTree<string, number>(new PathIterator()); + trie.set('/mid/fileC', 3); + trie.set('/mid/fileB', 2); + trie.set('/mid/fileA', 1); + assertTstDfs(trie, ['/mid/fileA', 1], ['/mid/fileB', 2], ['/mid/fileC', 3]); + } + + { + // rotate right, left + const trie = new TernarySearchTree<string, number>(new PathIterator()); + trie.set('/fileD', 7); + trie.set('/fileB', 2); + trie.set('/fileG', 42); + trie.set('/fileF', 24); + trie.set('/fileZ', 73); + trie.set('/fileE', 15); + assertTstDfs(trie, ['/fileB', 2], ['/fileD', 7], ['/fileE', 15], ['/fileF', 24], ['/fileG', 42], ['/fileZ', 73]); + } + + { + // rotate left, right + const trie = new TernarySearchTree<string, number>(new PathIterator()); + trie.set('/fileJ', 42); + trie.set('/fileZ', 73); + trie.set('/fileE', 15); + trie.set('/fileB', 2); + trie.set('/fileF', 7); + trie.set('/fileG', 1); + assertTstDfs(trie, ['/fileB', 2], ['/fileE', 15], ['/fileF', 7], ['/fileG', 1], ['/fileJ', 42], ['/fileZ', 73]); + } + }); + + test('TernarySearchTree - (BST) delete', function () { + + const trie = new TernarySearchTree<string, number>(new StringIterator()); + + // delete root + trie.set('d', 1); + assertTstDfs(trie, ['d', 1]); + trie.delete('d'); + assertTstDfs(trie); + + // delete node with two element + trie.clear(); + trie.set('d', 1); + trie.set('b', 1); + trie.set('f', 1); + assertTstDfs(trie, ['b', 1], ['d', 1], ['f', 1]); + trie.delete('d'); + assertTstDfs(trie, ['b', 1], ['f', 1]); + + // single child node + trie.clear(); + trie.set('d', 1); + trie.set('b', 1); + trie.set('f', 1); + trie.set('e', 1); + assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1], ['f', 1]); + trie.delete('f'); + assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1]); + }); + + test('TernarySearchTree - (AVL) delete', function () { + + const trie = new TernarySearchTree<string, number>(new StringIterator()); + + trie.clear(); + trie.set('d', 1); + trie.set('b', 1); + trie.set('f', 1); + trie.set('e', 1); + trie.set('z', 1); + assertTstDfs(trie, ['b', 1], ['d', 1], ['e', 1], ['f', 1], ['z', 1]); + + // right, right + trie.delete('b'); + assertTstDfs(trie, ['d', 1], ['e', 1], ['f', 1], ['z', 1]); + + trie.clear(); + trie.set('d', 1); + trie.set('c', 1); + trie.set('f', 1); + trie.set('a', 1); + trie.set('b', 1); + assertTstDfs(trie, ['a', 1], ['b', 1], ['c', 1], ['d', 1], ['f', 1]); + + // left, left + trie.delete('f'); + assertTstDfs(trie, ['a', 1], ['b', 1], ['c', 1], ['d', 1]); + + // mid + trie.clear(); + trie.set('a', 1); + trie.set('ad', 1); + trie.set('ab', 1); + trie.set('af', 1); + trie.set('ae', 1); + trie.set('az', 1); + assertTstDfs(trie, ['a', 1], ['ab', 1], ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); + + trie.delete('ab'); + assertTstDfs(trie, ['a', 1], ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); + + trie.delete('a'); + assertTstDfs(trie, ['ad', 1], ['ae', 1], ['af', 1], ['az', 1]); + }); + + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284', function () { + + const keys = [ + URI.parse('fake-fs:/C'), + URI.parse('fake-fs:/A'), + URI.parse('fake-fs:/D'), + URI.parse('fake-fs:/B'), + ]; + + const tst = TernarySearchTree.forUris<boolean>(); + + for (const item of keys) { + tst.set(item, true); + } + + assert.ok(tst._isBalanced()); + tst.delete(keys[0]); + assert.ok(tst._isBalanced()); + }); + + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (simple)', function () { + + const keys = ['C', 'A', 'D', 'B',]; + const tst = TernarySearchTree.forStrings<boolean>(); + for (const item of keys) { + tst.set(item, true); + } + assertTstDfs(tst, ['A', true], ['B', true], ['C', true], ['D', true]); + + tst.delete(keys[0]); + assertTstDfs(tst, ['A', true], ['B', true], ['D', true]); + + { + const tst = TernarySearchTree.forStrings<boolean>(); + tst.set('C', true); + tst.set('A', true); + tst.set('B', true); + assertTstDfs(tst, ['A', true], ['B', true], ['C', true]); + } + + }); + + test('TernarySearchTree: Cannot read property \'1\' of undefined #138284 (random)', function () { + for (let round = 10; round >= 0; round--) { + const keys: URI[] = []; + for (let i = 0; i < 100; i++) { + keys.push(URI.from({ scheme: 'fake-fs', path: randomPath(undefined, undefined, 10) })); + } + const tst = TernarySearchTree.forUris<boolean>(); + + for (const item of keys) { + tst.set(item, true); + assert.ok(tst._isBalanced(), `SET${item}|${keys.map(String).join()}`); + } + + for (const item of keys) { + tst.delete(item); + assert.ok(tst._isBalanced(), `DEL${item}|${keys.map(String).join()}`); + } + } + }); + + test('TernarySearchTree: Cannot read properties of undefined (reading \'length\'): #161618 (simple)', function () { + const raw = 'config.debug.toolBarLocation,floating,config.editor.renderControlCharacters,true,config.editor.renderWhitespace,selection,config.files.autoSave,off,config.git.enabled,true,config.notebook.globalToolbar,true,config.terminal.integrated.tabs.enabled,true,config.terminal.integrated.tabs.showActions,singleTerminalOrNarrow,config.terminal.integrated.tabs.showActiveTerminal,singleTerminalOrNarrow,config.workbench.activityBar.visible,true,config.workbench.experimental.settingsProfiles.enabled,true,config.workbench.layoutControl.type,both,config.workbench.sideBar.location,left,config.workbench.statusBar.visible,true'; + const array = raw.split(','); + const tuples: [string, string][] = []; + for (let i = 0; i < array.length; i += 2) { + tuples.push([array[i], array[i + 1]]); + } + + const map = TernarySearchTree.forConfigKeys<string>(); + map.fill(tuples); + + assert.strictEqual([...map].join(), raw); + assert.ok(map.has('config.editor.renderWhitespace')); + + const len = [...map].length; + map.delete('config.editor.renderWhitespace'); + assert.ok(map._isBalanced()); + assert.strictEqual([...map].length, len - 1); + }); + + test('TernarySearchTree: Cannot read properties of undefined (reading \'length\'): #161618 (random)', function () { + const raw = 'config.debug.toolBarLocation,floating,config.editor.renderControlCharacters,true,config.editor.renderWhitespace,selection,config.files.autoSave,off,config.git.enabled,true,config.notebook.globalToolbar,true,config.terminal.integrated.tabs.enabled,true,config.terminal.integrated.tabs.showActions,singleTerminalOrNarrow,config.terminal.integrated.tabs.showActiveTerminal,singleTerminalOrNarrow,config.workbench.activityBar.visible,true,config.workbench.experimental.settingsProfiles.enabled,true,config.workbench.layoutControl.type,both,config.workbench.sideBar.location,left,config.workbench.statusBar.visible,true'; + const array = raw.split(','); + const tuples: [string, string][] = []; + for (let i = 0; i < array.length; i += 2) { + tuples.push([array[i], array[i + 1]]); + } + + for (let round = 100; round >= 0; round--) { + shuffle(tuples); + const map = TernarySearchTree.forConfigKeys<string>(); + map.fill(tuples); + + assert.strictEqual([...map].join(), raw); + assert.ok(map.has('config.editor.renderWhitespace')); + + const len = [...map].length; + map.delete('config.editor.renderWhitespace'); + assert.ok(map._isBalanced()); + assert.strictEqual([...map].length, len - 1); + } + }); + + test('TernarySearchTree (PathSegments) - lookup', function () { + + const map = new TernarySearchTree<string, number>(new PathIterator()); + map.set('/user/foo/bar', 1); + map.set('/user/foo', 2); + map.set('/user/foo/flip/flop', 3); + + 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 () { + + const map = new TernarySearchTree<string, number>(new PathIterator()); + map.set('/user/foo/bar', 1); + map.set('/user/foo', 2); + map.set('/user/foo/flip/flop', 3); + map.set('/usr/foo', 4); + + let item: IteratorResult<[string, number]>; + let iter = map.findSuperstr('/user'); + + item = iter!.next(); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + iter = map.findSuperstr('/usr'); + item = iter!.next(); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); + + item = iter!.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + assert.strictEqual(map.findSuperstr('/not'), undefined); + assert.strictEqual(map.findSuperstr('/us'), undefined); + assert.strictEqual(map.findSuperstr('/usrr'), undefined); + assert.strictEqual(map.findSuperstr('/userr'), undefined); + }); + + + test('TernarySearchTree (PathSegments) - delete_superstr', function () { + + const map = new TernarySearchTree<string, number>(new PathIterator()); + map.set('/user/foo/bar', 1); + map.set('/user/foo', 2); + map.set('/user/foo/flip/flop', 3); + map.set('/usr/foo', 4); + + assertTstDfs(map, + ['/user/foo', 2], + ['/user/foo/bar', 1], + ['/user/foo/flip/flop', 3], + ['/usr/foo', 4], + ); + + // not a segment + map.deleteSuperstr('/user/fo'); + assertTstDfs(map, + ['/user/foo', 2], + ['/user/foo/bar', 1], + ['/user/foo/flip/flop', 3], + ['/usr/foo', 4], + ); + + // delete a segment + map.set('/user/foo/bar', 1); + map.set('/user/foo', 2); + map.set('/user/foo/flip/flop', 3); + map.set('/usr/foo', 4); + map.deleteSuperstr('/user/foo'); + assertTstDfs(map, + ['/user/foo', 2], + ['/usr/foo', 4], + ); + }); + + test('TernarySearchTree (URI) - basics', function () { + const trie = new TernarySearchTree<URI, number>(new UriIterator(() => false, () => false)); + + trie.set(URI.file('/user/foo/bar'), 1); + trie.set(URI.file('/user/foo'), 2); + trie.set(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.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) - query parameters', function () { + const trie = new TernarySearchTree<URI, number>(new UriIterator(() => false, () => true)); + const root = URI.parse('memfs:/?param=1'); + trie.set(root, 1); + + assert.strictEqual(trie.get(URI.parse('memfs:/?param=1')), 1); + + assert.strictEqual(trie.findSubstr(URI.parse('memfs:/?param=1')), 1); + assert.strictEqual(trie.findSubstr(URI.parse('memfs:/aaa?param=1')), 1); + }); + + test('TernarySearchTree (URI) - lookup', function () { + + const map = new TernarySearchTree<URI, number>(new UriIterator(() => false, () => 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); + map.set(URI.parse('http://foo.bar/user/foo/flip/flop'), 3); + + 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<URI, number>(new UriIterator(uri => /^https?$/.test(uri.scheme), () => false)); + map.set(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.strictEqual(map.get(URI.parse('foo://foo.bar/USER/foo/bar')), undefined); + }); + + test('TernarySearchTree (URI) - superstr', function () { + + const map = new TernarySearchTree<URI, number>(new UriIterator(() => false, () => 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<[URI, number]>; + let iter = map.findSuperstr(URI.file('/user'))!; + + item = iter.next(); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + iter = map.findSuperstr(URI.file('/usr'))!; + item = iter.next(); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); + + item = iter.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + iter = map.findSuperstr(URI.file('/'))!; + item = iter.next(); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); + item = iter.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + 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 () { + const trie = new TernarySearchTree<string, number>(new ConfigKeysIterator()); + + trie.set('config.foo.bar', 1); + trie.set('config.foo', 2); + trie.set('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.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 () { + + const map = new TernarySearchTree<string, number>(new ConfigKeysIterator()); + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + + 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 () { + + const map = new TernarySearchTree<string, number>(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]>; + const iter = map.findSuperstr('config'); + + item = iter!.next(); + assert.strictEqual(item.value[1], 2); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value[1], 1); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value[1], 3); + assert.strictEqual(item.done, false); + item = iter!.next(); + assert.strictEqual(item.value, undefined); + assert.strictEqual(item.done, true); + + assert.strictEqual(map.findSuperstr('foo'), undefined); + assert.strictEqual(map.findSuperstr('config.foo.no'), undefined); + assert.strictEqual(map.findSuperstr('config.foop'), undefined); + }); + + + test('TernarySearchTree (ConfigKeySegments) - delete_superstr', function () { + + const map = new TernarySearchTree<string, number>(new ConfigKeysIterator()); + map.set('config.foo.bar', 1); + map.set('config.foo', 2); + map.set('config.foo.flip.flop', 3); + map.set('boo', 4); + + assertTstDfs(map, + ['boo', 4], + ['config.foo', 2], + ['config.foo.bar', 1], + ['config.foo.flip.flop', 3], + ); + + // not a segment + map.deleteSuperstr('config.fo'); + assertTstDfs(map, + ['boo', 4], + ['config.foo', 2], + ['config.foo.bar', 1], + ['config.foo.flip.flop', 3], + ); + + // 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'); + assertTstDfs(map, + ['boo', 4], + ['config.foo', 2], + ); + }); + + test('TST, fill', function () { + const tst = TernarySearchTree.forStrings(); + + const keys = ['foo', 'bar', 'bang', 'bazz']; + Object.freeze(keys); + tst.fill(true, keys); + + for (const key of keys) { + assert.ok(tst.get(key), key); + } + }); +}); + + +suite.skip('TST, perf', function () { + + function createRandomUris(n: number): URI[] { + const uris: URI[] = []; + function randomWord(): string { + let result = ''; + const length = 4 + Math.floor(Math.random() * 4); + for (let i = 0; i < length; i++) { + result += (Math.random() * 26 + 65).toString(36); + } + return result; + } + + // generate 10000 random words + const words: string[] = []; + for (let i = 0; i < 10000; i++) { + words.push(randomWord()); + } + + for (let i = 0; i < n; i++) { + + let len = 4 + Math.floor(Math.random() * 4); + + const segments: string[] = []; + for (; len >= 0; len--) { + segments.push(words[Math.floor(Math.random() * words.length)]); + } + + uris.push(URI.from({ scheme: 'file', path: segments.join('/') })); + } + + return uris; + } + + let tree: TernarySearchTree<URI, boolean>; + let sampleUris: URI[] = []; + let candidates: URI[] = []; + + suiteSetup(() => { + const len = 50_000; + sampleUris = createRandomUris(len); + candidates = [...sampleUris.slice(0, len / 2), ...createRandomUris(len / 2)]; + shuffle(candidates); + }); + + setup(() => { + tree = TernarySearchTree.forUris(); + for (const uri of sampleUris) { + tree.set(uri, true); + } + }); + + const _profile = false; + + function perfTest(name: string, callback: Function) { + test(name, function () { + if (_profile) { console.profile(name); } + const sw = new StopWatch(true); + callback(); + console.log(name, sw.elapsed()); + if (_profile) { console.profileEnd(); } + }); + } + + perfTest('TST, clear', function () { + tree.clear(); + }); + + perfTest('TST, insert', function () { + const insertTree = TernarySearchTree.forUris(); + for (const uri of sampleUris) { + insertTree.set(uri, true); + } + }); + + perfTest('TST, lookup', function () { + let match = 0; + for (const candidate of candidates) { + if (tree.has(candidate)) { + match += 1; + } + } + assert.strictEqual(match, sampleUris.length / 2); + }); + + perfTest('TST, substr', function () { + let match = 0; + for (const candidate of candidates) { + if (tree.findSubstr(candidate)) { + match += 1; + } + } + assert.strictEqual(match, sampleUris.length / 2); + }); + + perfTest('TST, superstr', function () { + for (const candidate of candidates) { + tree.findSuperstr(candidate); + } + }); +}); diff --git a/src/vs/base/test/common/types.test.ts b/src/vs/base/test/common/types.test.ts index 8eb0ae1a76..7c74511362 100644 --- a/src/vs/base/test/common/types.test.ts +++ b/src/vs/base/test/common/types.test.ts @@ -82,24 +82,6 @@ suite('Types', () => { assert(types.isEmptyObject({})); }); - test('isArray', () => { - assert(!types.isArray(undefined)); - assert(!types.isArray(null)); - assert(!types.isArray('foo')); - assert(!types.isArray(5)); - assert(!types.isArray(true)); - assert(!types.isArray({})); - assert(!types.isArray(/test/)); - assert(!types.isArray(new RegExp(''))); - assert(!types.isArray(new Date())); - assert(!types.isArray(assert)); - assert(!types.isArray(function foo() { /**/ })); - assert(!types.isArray({ foo: 'bar' })); - - assert(types.isArray([])); - assert(types.isArray([1, 2, '3'])); - }); - test('isString', () => { assert(!types.isString(undefined)); assert(!types.isString(null)); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 7c9b808f90..1f51e92dca 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { URI, UriComponents, isUriComponents } from 'vs/base/common/uri'; suite('URI', () => { @@ -469,7 +469,31 @@ suite('URI', () => { }), true); }); - test('Unable to open \'%A0.txt\': URI malformed #76506', function () { + test('isUriComponents', function () { + + assert.ok(isUriComponents(URI.file('a'))); + assert.ok(isUriComponents(URI.file('a').toJSON())); + assert.ok(isUriComponents(URI.file(''))); + assert.ok(isUriComponents(URI.file('').toJSON())); + + assert.strictEqual(isUriComponents(1), false); + assert.strictEqual(isUriComponents(true), false); + assert.strictEqual(isUriComponents("true"), false); + assert.strictEqual(isUriComponents({}), false); + assert.strictEqual(isUriComponents({ scheme: '' }), true); // valid components but INVALID uri + assert.strictEqual(isUriComponents({ scheme: 'fo' }), true); + assert.strictEqual(isUriComponents({ scheme: 'fo', path: '/p' }), true); + assert.strictEqual(isUriComponents({ path: '/p' }), false); + }); + + test('from, from(strict), revive', function () { + + assert.throws(() => URI.from({ scheme: '' }, true)); + assert.strictEqual(URI.from({ scheme: '' }).scheme, 'file'); + assert.strictEqual(URI.revive({ scheme: '' }).scheme, ''); + }); + + test('Unable to open \'%A0.txt\': URI malformed #76506, part 2', function () { 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'); }); @@ -595,4 +619,11 @@ suite('URI', () => { //https://github.com/microsoft/vscode/issues/93831 assertJoined('file:///c:/foo/bar', './other/foo.img', 'file:///c:/foo/bar/other/foo.img', false); }); + + test('vscode-uri: URI.toString() wrongly encode IPv6 literals #154048', function () { + assert.strictEqual(URI.parse('http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html').toString(), 'http://[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:80/index.html'); + + assert.strictEqual(URI.parse('http://user@[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html').toString(), 'http://user@[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:80/index.html'); + assert.strictEqual(URI.parse('http://us[er@[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html').toString(), 'http://us%5Ber@[fedc:ba98:7654:3210:fedc:ba98:7654:3210]:80/index.html'); + }); }); diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index 70a1510794..5f1100682b 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri'; export type ValueCallback<T = any> = (value: T | Promise<T>) => void; -export function toResource(this: any, path: string) { +export function toResource(this: any, path: string): URI { if (isWindows) { return URI.file(join('C:\\', btoa(this.test.fullTitle()), path)); } diff --git a/src/vs/base/test/node/decoder.test.ts b/src/vs/base/test/node/decoder.test.ts deleted file mode 100644 index e2554685f1..0000000000 --- a/src/vs/base/test/node/decoder.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { LineDecoder } from 'vs/base/node/decoder'; - -suite('Decoder', () => { - - test('decoding', () => { - const lineDecoder = new LineDecoder(); - let res = lineDecoder.write(Buffer.from('hello')); - assert.strictEqual(res.length, 0); - - res = lineDecoder.write(Buffer.from('\nworld')); - assert.strictEqual(res[0], 'hello'); - assert.strictEqual(res.length, 1); - - assert.strictEqual(lineDecoder.end(), 'world'); - }); -}); diff --git a/src/vs/base/test/node/id.test.ts b/src/vs/base/test/node/id.test.ts index 93434e21e4..9cfc7bc51f 100644 --- a/src/vs/base/test/node/id.test.ts +++ b/src/vs/base/test/node/id.test.ts @@ -11,8 +11,10 @@ import { flakySuite } from 'vs/base/test/node/testUtils'; flakySuite('ID', () => { test('getMachineId', async function () { - const id = await getMachineId(); + const errors = []; + const id = await getMachineId(err => errors.push(err)); assert.ok(id); + assert.strictEqual(errors.length, 0); }); test('getMac', async () => { diff --git a/src/vs/base/test/node/pfs/fixtures/index.html b/src/vs/base/test/node/pfs/fixtures/index.html index bccd24d927..165a4ecbcc 100644 --- a/src/vs/base/test/node/pfs/fixtures/index.html +++ b/src/vs/base/test/node/pfs/fixtures/index.html @@ -1,7 +1,6 @@ <!DOCTYPE html> <html> <head id='headID'> - <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>Strada diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 37d6e5bf85..974667aa5c 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -9,10 +9,11 @@ import { tmpdir } from 'os'; import { timeout } from 'vs/base/common/async'; import { VSBuffer } from 'vs/base/common/buffer'; import { randomPath } from 'vs/base/common/extpath'; -import { join, sep } from 'vs/base/common/path'; +import { FileAccess } from 'vs/base/common/network'; +import { basename, dirname, join, sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { configureFlushOnWrite, Promises, RimRafMode, rimrafSync, SymlinkSupport, writeFileSync } from 'vs/base/node/pfs'; -import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; configureFlushOnWrite(false); // speed up all unit tests by disabling flush on write @@ -90,6 +91,24 @@ flakySuite('PFS', function () { assert.ok(!fs.existsSync(testDir)); }); + test('rimraf - simple - move (with moveToPath)', async () => { + fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); + fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); + + await Promises.rm(testDir, RimRafMode.MOVE, join(dirname(testDir), `${basename(testDir)}.vsctmp`)); + assert.ok(!fs.existsSync(testDir)); + }); + + test('rimraf - path does not exist - move', async () => { + const nonExistingDir = join(testDir, 'unknown-move'); + await Promises.rm(nonExistingDir, RimRafMode.MOVE); + }); + + test('rimraf - path does not exist - unlink', async () => { + const nonExistingDir = join(testDir, 'unknown-unlink'); + await Promises.rm(nonExistingDir, RimRafMode.UNLINK); + }); + test('rimraf - recursive folder structure - unlink', async () => { fs.writeFileSync(join(testDir, 'somefile.txt'), 'Contents'); fs.writeFileSync(join(testDir, 'someOtherFile.txt'), 'Contents'); @@ -155,7 +174,7 @@ flakySuite('PFS', function () { }); test('copy, move and delete', async () => { - const sourceDir = getPathFromAmdModule(require, './fixtures'); + const sourceDir = FileAccess.asFileUri('vs/base/test/node/pfs/fixtures').fsPath; const parentDir = join(tmpdir(), 'vsctests', 'pfs'); const targetDir = randomPath(parentDir); const targetDir2 = randomPath(parentDir); diff --git a/src/vs/base/test/node/processes/processes.integrationTest.ts b/src/vs/base/test/node/processes/processes.integrationTest.ts index 2a22389395..5bd7c99782 100644 --- a/src/vs/base/test/node/processes/processes.integrationTest.ts +++ b/src/vs/base/test/node/processes/processes.integrationTest.ts @@ -5,10 +5,10 @@ import * as assert from 'assert'; import * as cp from 'child_process'; +import { FileAccess } from 'vs/base/common/network'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import * as processes from 'vs/base/node/processes'; -import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; function fork(id: string): cp.ChildProcess { const opts: any = { @@ -19,7 +19,7 @@ function fork(id: string): cp.ChildProcess { }) }; - return cp.fork(getPathFromAmdModule(require, 'bootstrap-fork'), ['--type=processTests'], opts); + return cp.fork(FileAccess.asFileUri('bootstrap-fork').fsPath, ['--type=processTests'], opts); } suite('Processes', () => { diff --git a/src/vs/base/test/node/testUtils.ts b/src/vs/base/test/node/testUtils.ts index d4b2efbb2d..5a3368484d 100644 --- a/src/vs/base/test/node/testUtils.ts +++ b/src/vs/base/test/node/testUtils.ts @@ -5,15 +5,10 @@ import { randomPath } from 'vs/base/common/extpath'; import { join } from 'vs/base/common/path'; -import { URI } from 'vs/base/common/uri'; import * as testUtils from 'vs/base/test/common/testUtils'; export function getRandomTestPath(tmpdir: string, ...segments: string[]): string { return randomPath(join(tmpdir, ...segments)); } -export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string { - return URI.parse(requirefn.toUrl(relativePath)).fsPath; -} - export import flakySuite = testUtils.flakySuite; diff --git a/src/vs/base/test/node/unc.test.ts b/src/vs/base/test/node/unc.test.ts new file mode 100644 index 0000000000..babe4bde5a --- /dev/null +++ b/src/vs/base/test/node/unc.test.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { strictEqual } from 'assert'; +import { getUNCHost } from 'vs/base/node/unc'; + +suite('UNC', () => { + + test('getUNCHost', () => { + + strictEqual(getUNCHost(undefined), undefined); + strictEqual(getUNCHost(null), undefined); + + strictEqual(getUNCHost('/'), undefined); + strictEqual(getUNCHost('/foo'), undefined); + + strictEqual(getUNCHost('c:'), undefined); + strictEqual(getUNCHost('c:\\'), undefined); + strictEqual(getUNCHost('c:\\foo'), undefined); + strictEqual(getUNCHost('c:\\foo\\\\server\\path'), undefined); + + strictEqual(getUNCHost('\\'), undefined); + strictEqual(getUNCHost('\\\\'), undefined); + strictEqual(getUNCHost('\\\\localhost'), undefined); + + strictEqual(getUNCHost('\\\\localhost\\'), 'localhost'); + strictEqual(getUNCHost('\\\\localhost\\a'), 'localhost'); + + strictEqual(getUNCHost('\\\\.'), undefined); + strictEqual(getUNCHost('\\\\?'), undefined); + + strictEqual(getUNCHost('\\\\.\\localhost'), '.'); + strictEqual(getUNCHost('\\\\?\\localhost'), '?'); + + strictEqual(getUNCHost('\\\\.\\UNC\\localhost'), '.'); + strictEqual(getUNCHost('\\\\?\\UNC\\localhost'), '?'); + + strictEqual(getUNCHost('\\\\.\\UNC\\localhost\\'), 'localhost'); + strictEqual(getUNCHost('\\\\?\\UNC\\localhost\\'), 'localhost'); + + strictEqual(getUNCHost('\\\\.\\UNC\\localhost\\a'), 'localhost'); + strictEqual(getUNCHost('\\\\?\\UNC\\localhost\\a'), 'localhost'); + }); +}); diff --git a/src/vs/base/test/node/uri.test.perf.ts b/src/vs/base/test/node/uri.test.perf.ts index fa5a63fa2e..6917e21c52 100644 --- a/src/vs/base/test/node/uri.test.perf.ts +++ b/src/vs/base/test/node/uri.test.perf.ts @@ -5,15 +5,15 @@ import * as assert from 'assert'; import { readFileSync } from 'fs'; +import { FileAccess } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; suite('URI - perf', function () { let manyFileUris: URI[]; setup(function () { manyFileUris = []; - const data = readFileSync(getPathFromAmdModule(require, './uri.test.data.txt')).toString(); + const data = readFileSync(FileAccess.asFileUri('vs/base/test/node/uri.test.data.txt').fsPath).toString(); const lines = data.split('\n'); for (const line of lines) { manyFileUris.push(URI.file(line)); diff --git a/src/vs/base/test/node/zip/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts index ef4a4d365f..cb4d5973e0 100644 --- a/src/vs/base/test/node/zip/zip.test.ts +++ b/src/vs/base/test/node/zip/zip.test.ts @@ -6,10 +6,11 @@ import * as assert from 'assert'; import { tmpdir } from 'os'; import { createCancelablePromise } from 'vs/base/common/async'; +import { FileAccess } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import { Promises } from 'vs/base/node/pfs'; import { extract } from 'vs/base/node/zip'; -import { getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; suite('Zip', () => { @@ -26,7 +27,7 @@ suite('Zip', () => { }); test('extract should handle directories', async () => { - const fixtures = getPathFromAmdModule(require, './fixtures'); + const fixtures = FileAccess.asFileUri('vs/base/test/node/zip/fixtures').fsPath; const fixture = path.join(fixtures, 'extract.zip'); await createCancelablePromise(token => extract(fixture, testDir, {}, token)); diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index f08e487085..d7525a3a31 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -5,7 +5,7 @@ (function () { - const MonacoEnvironment = (self).MonacoEnvironment; + const MonacoEnvironment = (globalThis).MonacoEnvironment; const monacoBaseUrl = MonacoEnvironment && MonacoEnvironment.baseUrl ? MonacoEnvironment.baseUrl : '../../../'; const trustedTypesPolicy = ( @@ -17,7 +17,8 @@ // see https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor const fnArgs = args.slice(0, -1).join(','); const fnBody = args.pop()!.toString(); - const body = `(function anonymous(${fnArgs}) {\n${fnBody}\n})`; + // Do not add a new line to fnBody, as this will confuse source maps. + const body = `(function anonymous(${fnArgs}) { ${fnBody}\n})`; return body; } }) @@ -28,10 +29,10 @@ try { const func = ( trustedTypesPolicy - ? self.eval(trustedTypesPolicy.createScript('', 'true')) + ? globalThis.eval(trustedTypesPolicy.createScript('', 'true')) : new Function('true') ); - func.call(self); + func.call(globalThis); return true; } catch (err) { return false; @@ -40,12 +41,12 @@ function loadAMDLoader() { return new Promise((resolve, reject) => { - if (typeof (self).define === 'function' && (self).define.amd) { + if (typeof (globalThis).define === 'function' && (globalThis).define.amd) { return resolve(); } const loaderSrc: string | TrustedScriptURL = monacoBaseUrl + 'vs/loader.js'; - const isCrossOrigin = (/^((http:)|(https:)|(file:))/.test(loaderSrc) && loaderSrc.substring(0, self.origin.length) !== self.origin); + const isCrossOrigin = (/^((http:)|(https:)|(file:))/.test(loaderSrc) && loaderSrc.substring(0, globalThis.origin.length) !== globalThis.origin); if (!isCrossOrigin && canUseEval()) { // use `fetch` if possible because `importScripts` // is synchronous and can lead to deadlocks on Safari @@ -58,10 +59,10 @@ text = `${text}\n//# sourceURL=${loaderSrc}`; const func = ( trustedTypesPolicy - ? self.eval(trustedTypesPolicy.createScript('', text) as unknown as string) + ? globalThis.eval(trustedTypesPolicy.createScript('', text) as unknown as string) : new Function(text) ); - func.call(self); + func.call(globalThis); resolve(); }).then(undefined, reject); return; @@ -91,12 +92,13 @@ require([moduleId], function (ws) { setTimeout(function () { const messageHandler = ws.create((msg: any, transfer?: Transferable[]) => { - (self).postMessage(msg, transfer); + (globalThis).postMessage(msg, transfer); }, null); - self.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data, e.ports); + globalThis.onmessage = (e: MessageEvent) => messageHandler.onmessage(e.data, e.ports); while (beforeReadyMessages.length > 0) { - self.onmessage(beforeReadyMessages.shift()!); + const e = beforeReadyMessages.shift()!; + messageHandler.onmessage(e.data, e.ports); } }, 0); }); @@ -106,13 +108,13 @@ // If the loader is already defined, configure it immediately // This helps in the bundled case, where we must load nls files // and they need a correct baseUrl to be loaded. - if (typeof (self).define === 'function' && (self).define.amd) { + if (typeof (globalThis).define === 'function' && (globalThis).define.amd) { configureAMDLoader(); } let isFirstMessage = true; const beforeReadyMessages: MessageEvent[] = []; - self.onmessage = (message: MessageEvent) => { + globalThis.onmessage = (message: MessageEvent) => { if (!isFirstMessage) { beforeReadyMessages.push(message); return; diff --git a/src/vs/code/browser/workbench/workbench-dev.html b/src/vs/code/browser/workbench/workbench-dev.html index 47c143b503..46519b2cb2 100644 --- a/src/vs/code/browser/workbench/workbench-dev.html +++ b/src/vs/code/browser/workbench/workbench-dev.html @@ -3,7 +3,7 @@ @@ -27,6 +27,7 @@ + diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 2d10cb99ba..28eaa2a95e 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -14,9 +14,13 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { request } from 'vs/base/parts/request/browser/request'; import product from 'vs/platform/product/common/product'; import { isFolderToOpen, isWorkspaceToOpen } from 'vs/platform/window/common/window'; -import { create, ICredentialsProvider, IURLCallbackProvider, IWorkbenchConstructionOptions, IWorkspace, IWorkspaceProvider } from 'vs/workbench/workbench.web.main'; +import { create } from 'vs/workbench/workbench.web.main'; import { posix } from 'vs/base/common/path'; import { ltrim } from 'vs/base/common/strings'; +import type { ICredentialsProvider } from 'vs/platform/credentials/common/credentials'; +import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService'; +import type { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api'; +import type { IWorkspace, IWorkspaceProvider } from 'vs/workbench/services/host/browser/browserHostService'; interface ICredential { service: string; @@ -400,34 +404,13 @@ class WorkspaceProvider implements IWorkspaceProvider { // Folder else if (isFolderToOpen(workspace)) { - let queryParamFolder: string; - if (this.config.remoteAuthority && workspace.folderUri.scheme === Schemas.vscodeRemote) { - // when connected to a remote and having a folder - // for that remote, only use the path as query - // value to form shorter, nicer URLs. - // ensure paths are absolute (begin with `/`) - // clipboard: ltrim(workspace.folderUri.path, posix.sep) - queryParamFolder = `${posix.sep}${ltrim(workspace.folderUri.path, posix.sep)}`; - } else { - queryParamFolder = encodeURIComponent(workspace.folderUri.toString(true)); - } - + const queryParamFolder = this.encodeWorkspacePath(workspace.folderUri); targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${queryParamFolder}`; } // Workspace else if (isWorkspaceToOpen(workspace)) { - let queryParamWorkspace: string; - if (this.config.remoteAuthority && workspace.workspaceUri.scheme === Schemas.vscodeRemote) { - // when connected to a remote and having a workspace - // for that remote, only use the path as query - // value to form shorter, nicer URLs. - // ensure paths are absolute (begin with `/`) - queryParamWorkspace = `${posix.sep}${ltrim(workspace.workspaceUri.path, posix.sep)}`; - } else { - queryParamWorkspace = encodeURIComponent(workspace.workspaceUri.toString(true)); - } - + const queryParamWorkspace = this.encodeWorkspacePath(workspace.workspaceUri); targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${queryParamWorkspace}`; } @@ -439,6 +422,22 @@ class WorkspaceProvider implements IWorkspaceProvider { return targetHref; } + private encodeWorkspacePath(uri: URI): string { + if (this.config.remoteAuthority && uri.scheme === Schemas.vscodeRemote) { + + // when connected to a remote and having a folder + // or workspace for that remote, only use the path + // as query value to form shorter, nicer URLs. + // however, we still need to `encodeURIComponent` + // to ensure to preserve special characters, such + // as `+` in the path. + + return encodeURIComponent(`${posix.sep}${ltrim(uri.path, posix.sep)}`).replaceAll('%2F', '/'); + } + + return encodeURIComponent(uri.toString(true)); + } + private isSame(workspaceA: IWorkspace, workspaceB: IWorkspace): boolean { if (!workspaceA || !workspaceB) { return workspaceA === workspaceB; // both empty @@ -504,11 +503,10 @@ function doCreateUri(path: string, queryValues: Map): URI { // Create workbench create(document.body, { ...config, - settingsSyncOptions: config.settingsSyncOptions ? { - enabled: config.settingsSyncOptions.enabled, - } : undefined, + windowIndicator: config.windowIndicator ?? { label: '$(remote)', tooltip: `${product.nameShort} Web` }, + settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined, workspaceProvider: WorkspaceProvider.create(config), urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute), - credentialsProvider: config.remoteAuthority ? undefined : new LocalStorageCredentialsProvider() // with a remote, we don't use a local credentials provider + credentialsProvider: config.remoteAuthority ? undefined /* with a remote, we don't use a local credentials provider */ : new LocalStorageCredentialsProvider() }); })(); diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts deleted file mode 100644 index a2c2a76f55..0000000000 --- a/src/vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner.ts +++ /dev/null @@ -1,188 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IExtensionGalleryService, IExtensionIdentifier, IGlobalExtensionEnablementService, ServerDidUninstallExtensionEvent, ServerInstallExtensionResult, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getIdAndVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; -import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; -import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration'; -import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { DidChangeProfilesEvent, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; - -const uninstalOptions: UninstallOptions = { versionOnly: true, donotIncludePack: true, donotCheckDependents: true }; - -export class ExtensionsCleaner extends Disposable { - - constructor( - @INativeServerExtensionManagementService extensionManagementService: INativeServerExtensionManagementService, - @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, - @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, - @IExtensionStorageService extensionStorageService: IExtensionStorageService, - @IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService, - @IInstantiationService instantiationService: IInstantiationService, - @IStorageService storageService: IStorageService, - @ILogService logService: ILogService, - ) { - super(); - - extensionManagementService.removeUninstalledExtensions(this.userDataProfilesService.profiles.length === 1); - migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService); - ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService); - this._register(instantiationService.createInstance(ProfileExtensionsCleaner)); - } - -} - -class ProfileExtensionsCleaner extends Disposable { - - private profileExtensionsLocations = new Map(); // {{SQL CARBON EDIT}} lewissanchez - Added parenthesis - - private readonly profileModeDisposables = this._register(new MutableDisposable()); - - constructor( - @INativeServerExtensionManagementService private readonly extensionManagementService: INativeServerExtensionManagementService, - @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, - @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - @ILogService private readonly logService: ILogService, - ) { - super(); - this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles }); - } - - private async onDidChangeProfiles({ added, removed, all }: Omit): Promise { - try { - await Promise.all(removed.map(profile => profile.extensionsResource ? this.removeExtensionsFromProfile(profile.extensionsResource) : Promise.resolve())); - } catch (error) { - this.logService.error(error); - } - - if (all.length === 1) { - // Exit profile mode - this.profileModeDisposables.clear(); - // Listen for entering into profile mode - const disposable = this._register(this.userDataProfilesService.onDidChangeProfiles(() => { - disposable.dispose(); - this.onDidChangeProfiles({ added: this.userDataProfilesService.profiles, removed: [], all: this.userDataProfilesService.profiles }); - })); - return; - } - - try { - if (added.length) { - await Promise.all(added.map(profile => profile.extensionsResource ? this.populateExtensionsFromProfile(profile.extensionsResource) : Promise.resolve())); - // Enter profile mode - if (!this.profileModeDisposables.value) { - this.profileModeDisposables.value = new DisposableStore(); - this.profileModeDisposables.value.add(toDisposable(() => this.profileExtensionsLocations.clear())); - this.profileModeDisposables.value.add(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); - this.profileModeDisposables.value.add(this.extensionManagementService.onDidInstallExtensions(e => this.onDidInstallExtensions(e))); - this.profileModeDisposables.value.add(this.extensionManagementService.onDidUninstallExtension(e => this.onDidUninstallExtension(e))); - await this.uninstallExtensionsNotInProfiles(); - } - } - } catch (error) { - this.logService.error(error); - } - } - - private async uninstallExtensionsNotInProfiles(): Promise { - const installed = await this.extensionManagementService.getAllUserInstalled(); - const toUninstall = installed.filter(installedExtension => !this.profileExtensionsLocations.has(this.getKey(installedExtension.identifier, installedExtension.manifest.version))); - if (toUninstall.length) { - await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions))); - } - } - - private async onDidInstallExtensions(installedExtensions: readonly ServerInstallExtensionResult[]): Promise { - for (const { local, profileLocation } of installedExtensions) { - if (!local || !profileLocation) { - continue; - } - this.addExtensionWithKey(this.getKey(local.identifier, local.manifest.version), profileLocation); - } - } - - private async onDidUninstallExtension(e: ServerDidUninstallExtensionEvent): Promise { - if (!e.profileLocation || !e.version) { - return; - } - if (this.removeExtensionWithKey(this.getKey(e.identifier, e.version), e.profileLocation)) { - await this.uninstallExtensions([{ identifier: e.identifier, version: e.version }]); - } - } - - private async populateExtensionsFromProfile(extensionsProfileLocation: URI): Promise { - const extensions = await this.extensionsProfileScannerService.scanProfileExtensions(extensionsProfileLocation); - for (const extension of extensions) { - this.addExtensionWithKey(this.getKey(extension.identifier, extension.version), extensionsProfileLocation); - } - } - - private async removeExtensionsFromProfile(removedProfile: URI): Promise { - const extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[] = []; - for (const key of [...this.profileExtensionsLocations.keys()]) { - if (!this.removeExtensionWithKey(key, removedProfile)) { - continue; - } - const extensionToRemove = this.fromKey(key); - if (extensionToRemove) { - extensionsToRemove.push(extensionToRemove); - } - } - if (extensionsToRemove.length) { - await this.uninstallExtensions(extensionsToRemove); - } - } - - private addExtensionWithKey(key: string, extensionsProfileLocation: URI): void { - let locations = this.profileExtensionsLocations.get(key); - if (!locations) { - locations = []; - this.profileExtensionsLocations.set(key, locations); - } - locations.push(extensionsProfileLocation); - } - - private removeExtensionWithKey(key: string, profileLocation: URI): boolean { - const profiles = this.profileExtensionsLocations.get(key); - if (profiles) { - const index = profiles.findIndex(profile => this.uriIdentityService.extUri.isEqual(profile, profileLocation)); - if (index > -1) { - profiles.splice(index, 1); - } - } - if (!profiles?.length) { - this.profileExtensionsLocations.delete(key); - return true; - } - return false; - } - - private async uninstallExtensions(extensionsToRemove: { identifier: IExtensionIdentifier; version: string }[]): Promise { - const installed = await this.extensionManagementService.getAllUserInstalled(); - const toUninstall = installed.filter(installedExtension => extensionsToRemove.some(e => this.getKey(installedExtension.identifier, installedExtension.manifest.version) === this.getKey(e.identifier, e.version))); - if (toUninstall.length) { - await Promise.all(toUninstall.map(extension => this.extensionManagementService.uninstall(extension, uninstalOptions))); - } - } - - private getKey(identifier: IExtensionIdentifier, version: string): string { - return `${ExtensionIdentifier.toKey(identifier.id)}@${version}`; - } - - private fromKey(key: string): { identifier: IExtensionIdentifier; version: string } | undefined { - const [id, version] = getIdAndVersion(key); - return version ? { identifier: { id }, version } : undefined; - } - -} diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts deleted file mode 100644 index 08f991352a..0000000000 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { RunOnceScheduler } from 'vs/base/common/async'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { join } from 'vs/base/common/path'; -import { Promises } from 'vs/base/node/pfs'; -import { IBackupWorkspacesFormat } from 'vs/platform/backup/node/backup'; -import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ILogService } from 'vs/platform/log/common/log'; - -export class StorageDataCleaner extends Disposable { - - // Workspace/Folder storage names are MD5 hashes (128bits / 4 due to hex presentation) - private static readonly NON_EMPTY_WORKSPACE_ID_LENGTH = 128 / 4; - - // Reserved empty window workspace storage name when in extension development - private static readonly EXTENSION_DEV_EMPTY_WINDOW_ID = 'ext-dev'; - - constructor( - private readonly backupWorkspacesPath: string, - @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, - @ILogService private readonly logService: ILogService - ) { - super(); - - const scheduler = this._register(new RunOnceScheduler(() => { - this.cleanUpStorage(); - }, 30 * 1000 /* after 30s */)); - scheduler.schedule(); - } - - private async cleanUpStorage(): Promise { - this.logService.trace('[storage cleanup]: Starting to clean up storage folders.'); - - 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 Promises.readFile(this.backupWorkspacesPath, 'utf8'); - - const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat; - const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(emptyWorkspace => emptyWorkspace.backupFolder); - - // Read all workspace storage folders that exist & cleanup unused - const workspaceStorageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath); - await Promise.all(workspaceStorageFolders.map(async workspaceStorageFolder => { - if ( - workspaceStorageFolder.length === StorageDataCleaner.NON_EMPTY_WORKSPACE_ID_LENGTH || // keep non-empty workspaces - workspaceStorageFolder === StorageDataCleaner.EXTENSION_DEV_EMPTY_WINDOW_ID || // keep empty extension dev workspaces - emptyWorkspaces.indexOf(workspaceStorageFolder) >= 0 // keep empty workspaces that are in use - ) { - return; - } - - this.logService.trace(`[storage cleanup]: Deleting workspace storage folder ${workspaceStorageFolder}.`); - - await Promises.rm(join(this.environmentService.workspaceStorageHome.fsPath, workspaceStorageFolder)); - })); - } catch (error) { - onUnexpectedError(error); - } - } -} diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.html b/src/vs/code/electron-browser/sharedProcess/sharedProcess.html deleted file mode 100644 index 07fd9bd047..0000000000 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - Shared Process - - - - - - - - - - - diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js deleted file mode 100644 index 66e5a0bdd2..0000000000 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcess.js +++ /dev/null @@ -1,58 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check -(function () { - 'use strict'; - - const bootstrap = bootstrapLib(); - const bootstrapWindow = bootstrapWindowLib(); - - // Avoid Monkey Patches from Application Insights - bootstrap.avoidMonkeyPatchFromAppInsights(); - - // Load shared process into window - bootstrapWindow.load(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess, configuration) { - return sharedProcess.main(configuration); - }, - { - configureDeveloperSettings: function () { - return { - disallowReloadKeybinding: true - }; - } - } - ); - - /** - * @returns {{ avoidMonkeyPatchFromAppInsights: () => void; }} - */ - function bootstrapLib() { - // @ts-ignore (defined in bootstrap.js) - return window.MonacoBootstrap; - } - - /** - * @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration - * - * @returns {{ - * load: ( - * modules: string[], - * resultCallback: (result, configuration: ISandboxConfiguration) => unknown, - * options?: { - * configureDeveloperSettings?: (config: ISandboxConfiguration) => { - * forceEnableDeveloperKeybindings?: boolean, - * disallowReloadKeybinding?: boolean, - * removeDeveloperKeybindingsAfterLoad?: boolean - * } - * } - * ) => Promise - * }} - */ - function bootstrapWindowLib() { - // @ts-ignore (defined in bootstrap-window.js) - return window.MonacoBootstrapWindow; - } -}()); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index cae819f54d..bb92c8dd53 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,25 +3,24 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, BrowserWindow, contentTracing, dialog, protocol, session, Session, systemPreferences, WebFrameMain } from 'electron'; +import { app, BrowserWindow, dialog, protocol, session, Session, systemPreferences, WebFrameMain } from 'electron'; +import { addUNCHostToAllowlist, disableUNCAccessRestrictions } from 'vs/base/node/unc'; import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain'; -import { statSync } from 'fs'; import { hostname, release } from 'os'; import { VSBuffer } from 'vs/base/common/buffer'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; -import { isEqualOrParent, randomPath } from 'vs/base/common/extpath'; +import { isEqualOrParent } from 'vs/base/common/extpath'; import { once } from 'vs/base/common/functional'; import { stripComments } from 'vs/base/common/json'; -import { getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels'; +import { getPathLabel } from 'vs/base/common/labels'; import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isAbsolute, join, posix } from 'vs/base/common/path'; import { IProcessEnvironment, isLinux, isLinuxSnap, isMacintosh, isWindows, OS } from 'vs/base/common/platform'; -import { assertType, withNullAsUndefined } from 'vs/base/common/types'; +import { assertType } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { getMachineId } from 'vs/base/node/id'; import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu'; import { getDelayedChannel, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron'; @@ -56,23 +55,23 @@ import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemPro import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IIssueMainService, IssueMainService } from 'vs/platform/issue/electron-main/issueMainService'; +import { IIssueMainService } from 'vs/platform/issue/common/issue'; +import { IssueMainService } from 'vs/platform/issue/electron-main/issueMainService'; import { IKeyboardLayoutMainService, KeyboardLayoutMainService } from 'vs/platform/keyboardLayout/electron-main/keyboardLayoutMainService'; import { ILaunchMainService, LaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { ILoggerService, ILogService } from 'vs/platform/log/common/log'; -import { LoggerChannel, LogLevelChannel } from 'vs/platform/log/common/logIpc'; import { IMenubarMainService, MenubarMainService } from 'vs/platform/menubar/electron-main/menubarMainService'; import { INativeHostMainService, NativeHostMainService } from 'vs/platform/native/electron-main/nativeHostMainService'; import { IProductService } from 'vs/platform/product/common/productService'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { SharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedProcess'; import { ISignService } from 'vs/platform/sign/common/sign'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; +import { IStateService } from 'vs/platform/state/node/state'; import { StorageDatabaseChannel } from 'vs/platform/storage/electron-main/storageIpc'; import { ApplicationStorageMainService, IApplicationStorageMainService, IStorageMainService, StorageMainService } from 'vs/platform/storage/electron-main/storageMainService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; -import { ITelemetryService, machineIdKey, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService, TelemetryLevel } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { getPiiPathsFromEnvironment, getTelemetryLevel, isInternalTelemetry, NullTelemetryService, supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; @@ -90,7 +89,7 @@ import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManage import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService'; import { IWindowOpenable } from 'vs/platform/window/common/window'; import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; -import { ICodeWindow, WindowError } from 'vs/platform/window/electron-main/window'; +import { ICodeWindow } from 'vs/platform/window/electron-main/window'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker'; import { hasWorkspaceFileExtension } from 'vs/platform/workspace/common/workspace'; @@ -102,10 +101,26 @@ import { CredentialsNativeMainService } from 'vs/platform/credentials/electron-m import { IPolicyService } from 'vs/platform/policy/common/policy'; import { PolicyChannel } from 'vs/platform/policy/common/policyIpc'; import { IUserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; -import { IDefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { DefaultExtensionsProfileInitHandler } from 'vs/platform/extensionManagement/electron-main/defaultExtensionsProfileInit'; import { RequestChannel } from 'vs/platform/request/common/requestIpc'; import { IRequestService } from 'vs/platform/request/common/request'; +import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; +import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; +import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; +import { UserDataProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataProfilesHandler'; +import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc'; +import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { resolveMachineId } from 'vs/platform/telemetry/electron-main/telemetryUtils'; +import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; +import { LoggerChannel } from 'vs/platform/log/electron-main/logIpc'; +import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService'; +import { IInitialProtocolUrls, IProtocolUrl } from 'vs/platform/url/electron-main/url'; +import { massageMessageBoxOptions } from 'vs/platform/dialogs/common/dialogs'; +import { IUtilityProcessWorkerMainService, UtilityProcessWorkerMainService } from 'vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService'; +import { ipcUtilityProcessWorkerChannelName } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService'; +import { firstOrDefault } from 'vs/base/common/arrays'; +import { ILocalPtyService, LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ElectronPtyHostStarter } from 'vs/platform/terminal/electron-main/electronPtyHostStarter'; +import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; /** * The main VS Code application. There will only ever be one instance, @@ -121,12 +136,14 @@ export class CodeApplication extends Disposable { private readonly userEnv: IProcessEnvironment, @IInstantiationService private readonly mainInstantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, + @ILoggerService private readonly loggerService: ILoggerService, @IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService, @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStateMainService private readonly stateMainService: IStateMainService, + @IStateService private readonly stateService: IStateService, @IFileService private readonly fileService: IFileService, - @IProductService private readonly productService: IProductService + @IProductService private readonly productService: IProductService, + @IUserDataProfilesMainService private readonly userDataProfilesMainService: IUserDataProfilesMainService, ) { super(); @@ -203,9 +220,8 @@ export class CodeApplication extends Disposable { }; const isAllowedWebviewRequest = (uri: URI, details: Electron.OnBeforeRequestListenerDetails): boolean => { - // Only restrict top level page of webviews: index.html if (uri.path !== '/index.html') { - return true; + return true; // Only restrict top level page of webviews: index.html } const frame = details.frame; @@ -300,6 +316,18 @@ export class CodeApplication extends Disposable { } //#endregion + + //#region UNC Host Allowlist (Windows) + + if (isWindows) { + if (this.configurationService.getValue('security.restrictUNCAccess') === false) { + disableUNCAccessRestrictions(); + } else { + addUNCHostToAllowlist(this.configurationService.getValue('security.allowedUNCHosts')); + } + } + + //#endregion } private registerListeners(): void { @@ -321,12 +349,12 @@ export class CodeApplication extends Disposable { }); // macOS dock activate - app.on('activate', (event, hasVisibleWindows) => { + app.on('activate', async (event, hasVisibleWindows) => { this.logService.trace('app#activate'); // Mac only event: open new window when we get activated if (!hasVisibleWindows) { - this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK }); + await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK }); } }); @@ -358,7 +386,7 @@ export class CodeApplication extends Disposable { event.preventDefault(); // Keep in array because more might come! - macOpenFileURIs.push(this.getWindowOpenableFromPathSync(path)); + macOpenFileURIs.push(hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) }); // Clear previous handler if any if (runningTimeout !== undefined) { @@ -367,8 +395,8 @@ export class CodeApplication extends Disposable { } // Handle paths delayed in case more are coming! - runningTimeout = setTimeout(() => { - this.windowsMainService?.open({ + runningTimeout = setTimeout(async () => { + await this.windowsMainService?.open({ context: OpenContext.DOCK /* can also be opening from finder while app is running */, cli: this.environmentMainService.args, urisToOpen: macOpenFileURIs, @@ -381,8 +409,8 @@ export class CodeApplication extends Disposable { }, 100); }); - app.on('new-window-for-tab', () => { - this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button + app.on('new-window-for-tab', async () => { + await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button }); //#region Bootstrap IPC Handlers @@ -520,17 +548,20 @@ export class CodeApplication extends Disposable { // Resolve unique machine ID this.logService.trace('Resolving machine identifier...'); - const machineId = await this.resolveMachineId(); + const machineId = await resolveMachineId(this.stateService, this.logService); this.logService.trace(`Resolved machine identifier: ${machineId}`); // Shared process - const { sharedProcess, sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId); + const { sharedProcessReady, sharedProcessClient } = this.setupSharedProcess(machineId); // Services - const appInstantiationService = await this.initServices(machineId, sharedProcess, sharedProcessReady); + const appInstantiationService = await this.initServices(machineId, sharedProcessReady); - // Setup Handlers - this.setUpHandlers(appInstantiationService); + // Auth Handler + this._register(appInstantiationService.createInstance(ProxyAuthHandler)); + + // Transient profiles handler + this._register(appInstantiationService.createInstance(UserDataProfilesHandler)); // Setup Auth Handler this._register(appInstantiationService.createInstance(ProxyAuthHandler)); // {{SQL CARBON EDIT}} Cast here to avoid compilation error (not finding constructor?) @@ -538,42 +569,283 @@ export class CodeApplication extends Disposable { // Init Channels appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient)); + // Setup Protocol URL Handlers + const initialProtocolUrls = appInstantiationService.invokeFunction(accessor => this.setupProtocolUrlHandlers(accessor, mainProcessElectronServer)); + + // Signal phase: ready - before opening first window + this.lifecycleMainService.phase = LifecycleMainPhase.Ready; + // Open Windows - const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer)); + await appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, initialProtocolUrls)); + + // Signal phase: after window open + this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; // Post Open Windows Tasks - appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess)); + this.afterWindowOpen(); - // Tracing: Stop tracing after windows are ready if enabled - if (this.environmentMainService.args.trace) { - appInstantiationService.invokeFunction(accessor => this.stopTracingEventually(accessor, windows)); - } + // Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec) + const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => { + this._register(runWhenIdle(() => this.lifecycleMainService.phase = LifecycleMainPhase.Eventually, 2500)); + }, 2500)); + eventuallyPhaseScheduler.schedule(); } - private setUpHandlers(instantiationService: IInstantiationService): void { - // Auth Handler - this._register(instantiationService.createInstance(ProxyAuthHandler)); + private setupProtocolUrlHandlers(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): IInitialProtocolUrls | undefined { + const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); + const urlService = accessor.get(IURLService); + const nativeHostMainService = this.nativeHostMainService = accessor.get(INativeHostMainService); - // Default Extensions Profile Init Handler - this._register(instantiationService.createInstance(DefaultExtensionsProfileInitHandler)); + // Install URL handlers that deal with protocl URLs either + // from this process by opening windows and/or by forwarding + // the URLs into a window process to be handled there. + + const app = this; + urlService.registerHandler({ + async handleURL(uri: URI, options?: IOpenURLOptions): Promise { + return app.handleProtocolUrl(windowsMainService, urlService, uri, options); + } + }); + + 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, this.logService); + const urlHandlerChannel = mainProcessElectronServer.getChannel('urlHandler', urlHandlerRouter); + urlService.registerHandler(new URLHandlerChannelClient(urlHandlerChannel)); + + const initialProtocolUrls = this.resolveInitialProtocolUrls(); + this._register(new ElectronURLListener(initialProtocolUrls?.urls, urlService, windowsMainService, this.environmentMainService, this.productService, this.logService)); + + return initialProtocolUrls; } - private async resolveMachineId(): Promise { + private resolveInitialProtocolUrls(): IInitialProtocolUrls | undefined { - // We cache the machineId for faster lookups on startup - // and resolve it only once initially if not cached or we need to replace the macOS iBridge device - let machineId = this.stateMainService.getItem(machineIdKey); - if (!machineId || (isMacintosh && machineId === '6c9d2bc8f91b89624add29c0abeae7fb42bf539fa1cdb2e3e57cd668fa9bcead')) { - machineId = await getMachineId(); + /** + * Protocol URL handling on startup is complex, refer to + * {@link IInitialProtocolUrls} for an explainer. + */ - this.stateMainService.setItem(machineIdKey, machineId); + // Windows/Linux: protocol handler invokes CLI with --open-url + const protocolUrlsFromCommandLine = this.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : []; + if (protocolUrlsFromCommandLine.length > 0) { + this.logService.trace('app#resolveInitialProtocolUrls() protocol urls from command line:', protocolUrlsFromCommandLine); } - return machineId; + // macOS: open-url events that were received before the app is ready + const protocolUrlsFromEvent = ((global).getOpenUrls() || []) as string[]; + if (protocolUrlsFromEvent.length > 0) { + this.logService.trace(`app#resolveInitialProtocolUrls() protocol urls from macOS 'open-url' event:`, protocolUrlsFromEvent); + } + + if (protocolUrlsFromCommandLine.length + protocolUrlsFromEvent.length === 0) { + return undefined; + } + + const openables: IWindowOpenable[] = []; + const urls = [ + ...protocolUrlsFromCommandLine, + ...protocolUrlsFromEvent + ].map(url => { + try { + return { uri: URI.parse(url), originalUrl: url }; + } catch { + this.logService.trace('app#resolveInitialProtocolUrls() protocol url failed to parse:', url); + + return undefined; + } + }).filter((obj): obj is IProtocolUrl => { + if (!obj) { + return false; // invalid + } + + if (this.shouldBlockURI(obj.uri)) { + this.logService.trace('app#resolveInitialProtocolUrls() protocol url was blocked:', obj.uri.toString(true)); + + return false; // blocked + } + + const windowOpenable = this.getWindowOpenableFromProtocolUrl(obj.uri); + if (windowOpenable) { + this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be handled as window to open:', obj.uri.toString(true), windowOpenable); + + openables.push(windowOpenable); + + return false; // handled as window to open + } + + this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be passed to active window for handling:', obj.uri.toString(true)); + + return true; // handled within active window + }); + + return { urls, openables }; } - private setupSharedProcess(machineId: string): { sharedProcess: SharedProcess; sharedProcessReady: Promise; sharedProcessClient: Promise } { - const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId, this.userEnv)); + private shouldBlockURI(uri: URI): boolean { + if (uri.authority === Schemas.file && isWindows) { + const { options, buttonIndeces } = massageMessageBoxOptions({ + type: 'warning', + buttons: [ + localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes"), + localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No") + ], + message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", getPathLabel(uri, { os: OS, tildify: this.environmentMainService }), this.productService.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'"), + }, this.productService); + + const res = buttonIndeces[dialog.showMessageBoxSync(options)]; + if (res === 1) { + return true; + } + } + + return false; + } + + private getWindowOpenableFromProtocolUrl(uri: URI): IWindowOpenable | undefined { + if (!uri.path) { + return undefined; + } + + // File path + if (uri.authority === Schemas.file) { + const fileUri = URI.file(uri.fsPath); + + if (hasWorkspaceFileExtension(fileUri)) { + return { workspaceUri: fileUri }; + } + + return { fileUri }; + } + + // Remote path + else if (uri.authority === Schemas.vscodeRemote) { + + // Example conversion: + // From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco + // To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco + + const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */); + if (secondSlash !== -1) { + const authority = uri.path.substring(1, secondSlash); + const path = uri.path.substring(secondSlash); + const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment }); + + if (hasWorkspaceFileExtension(path)) { + return { workspaceUri: remoteUri }; + } + + if (/:[\d]+$/.test(path)) { + // path with :line:column syntax + return { fileUri: remoteUri }; + } + + return { folderUri: remoteUri }; + } + } + + return undefined; + } + + private async handleProtocolUrl(windowsMainService: IWindowsMainService, urlService: IURLService, uri: URI, options?: IOpenURLOptions): Promise { + this.logService.trace('app#handleProtocolUrl():', uri.toString(true), options); + + // Support 'workspace' URLs (https://github.com/microsoft/vscode/issues/124263) + if (uri.scheme === this.productService.urlProtocol && uri.path === 'workspace') { + uri = uri.with({ + authority: 'file', + path: URI.parse(uri.query).path, + query: '' + }); + } + + // If URI should be blocked, behave as if it's handled + if (this.shouldBlockURI(uri)) { + this.logService.trace('app#handleProtocolUrl() protocol url was blocked:', uri.toString(true)); + + return true; + } + + let shouldOpenInNewWindow = false; + + // We should handle the URI in a new window if the URL contains `windowId=_blank` + const params = new URLSearchParams(uri.query); + if (params.get('windowId') === '_blank') { + this.logService.trace(`app#handleProtocolUrl() found 'windowId=_blank' as parameter, setting shouldOpenInNewWindow=true:`, uri.toString(true)); + + params.delete('windowId'); + uri = uri.with({ query: params.toString() }); + + shouldOpenInNewWindow = true; + } + + // or if no window is open (macOS only) + else if (isMacintosh && windowsMainService.getWindowCount() === 0) { + this.logService.trace(`app#handleProtocolUrl() running on macOS with no window open, setting shouldOpenInNewWindow=true:`, uri.toString(true)); + + shouldOpenInNewWindow = true; + } + + // Pass along whether the application is being opened via a Continue On flow + const continueOn = params.get('continueOn'); + if (continueOn !== null) { + this.logService.trace(`app#handleProtocolUrl() found 'continueOn' as parameter:`, uri.toString(true)); + + params.delete('continueOn'); + uri = uri.with({ query: params.toString() }); + + this.environmentMainService.continueOn = continueOn ?? undefined; + } + + // Check if the protocol URL is a window openable to open... + const windowOpenableFromProtocolUrl = this.getWindowOpenableFromProtocolUrl(uri); + if (windowOpenableFromProtocolUrl) { + this.logService.trace('app#handleProtocolUrl() opening protocol url as window:', windowOpenableFromProtocolUrl, uri.toString(true)); + + const window = firstOrDefault(await windowsMainService.open({ + context: OpenContext.API, + cli: { ...this.environmentMainService.args }, + urisToOpen: [windowOpenableFromProtocolUrl], + forceNewWindow: shouldOpenInNewWindow, + gotoLineMode: true + // remoteAuthority: will be determined based on windowOpenableFromProtocolUrl + })); + + window?.focus(); // this should help ensuring that the right window gets focus when multiple are opened + + return true; + } + + // ...or if we should open in a new window and then handle it within that window + if (shouldOpenInNewWindow) { + this.logService.trace('app#handleProtocolUrl() opening empty window and passing in protocol url:', uri.toString(true)); + + const window = firstOrDefault(await windowsMainService.open({ + context: OpenContext.API, + cli: { ...this.environmentMainService.args }, + forceNewWindow: true, + forceEmpty: true, + gotoLineMode: true, + remoteAuthority: getRemoteAuthority(uri) + })); + + await window?.ready(); + + return urlService.open(uri, options); + } + + this.logService.trace('app#handleProtocolUrl(): not handled', uri.toString(true), options); + + return false; + } + + private setupSharedProcess(machineId: string): { sharedProcessReady: Promise; sharedProcessClient: Promise } { + const sharedProcess = this._register(this.mainInstantiationService.createInstance(SharedProcess, machineId)); const sharedProcessClient = (async () => { this.logService.trace('Main->SharedProcess#connect'); @@ -591,10 +863,10 @@ export class CodeApplication extends Disposable { return sharedProcessClient; })(); - return { sharedProcess, sharedProcessReady, sharedProcessClient }; + return { sharedProcessReady, sharedProcessClient }; } - private async initServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise): Promise { + private async initServices(machineId: string, sharedProcessReady: Promise): Promise { const services = new ServiceCollection(); // Update @@ -617,16 +889,17 @@ export class CodeApplication extends Disposable { } // Windows - services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv])); + services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv], false)); // Dialogs - services.set(IDialogMainService, new SyncDescriptor(DialogMainService)); + const dialogMainService = new DialogMainService(this.logService, this.productService); + services.set(IDialogMainService, dialogMainService); // Launch - services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService)); + services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService, undefined, false /* proxied to other processes */)); // Diagnostics - services.set(IDiagnosticsMainService, new SyncDescriptor(DiagnosticsMainService)); + services.set(IDiagnosticsMainService, new SyncDescriptor(DiagnosticsMainService, undefined, false /* proxied to other processes */)); services.set(IDiagnosticsService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('diagnostics'))))); // Issues @@ -639,7 +912,7 @@ export class CodeApplication extends Disposable { services.set(IKeyboardLayoutMainService, new SyncDescriptor(KeyboardLayoutMainService)); // Native Host - services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, [sharedProcess])); + services.set(INativeHostMainService, new SyncDescriptor(NativeHostMainService, undefined, false /* proxied to other processes */)); // Credentials services.set(ICredentialsMainService, new SyncDescriptor(CredentialsNativeMainService)); @@ -647,11 +920,6 @@ export class CodeApplication extends Disposable { // 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)); @@ -665,6 +933,20 @@ export class CodeApplication extends Disposable { services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); services.set(IApplicationStorageMainService, new SyncDescriptor(ApplicationStorageMainService)); + // Terminal + const ptyHostStarter = new ElectronPtyHostStarter({ + graceTime: LocalReconnectConstants.GraceTime, + shortGraceTime: LocalReconnectConstants.ShortGraceTime, + scrollback: this.configurationService.getValue(TerminalSettingId.PersistentSessionScrollback) ?? 100 + }, this.environmentMainService, this.lifecycleMainService, this.logService); + const ptyHostService = new PtyHostService( + ptyHostStarter, + this.configurationService, + this.logService, + this.loggerService + ); + services.set(ILocalPtyService, ptyHostService); + // External terminal if (isWindows) { services.set(IExternalTerminalMainService, new SyncDescriptor(WindowsExternalTerminalService)); @@ -675,31 +957,44 @@ export class CodeApplication extends Disposable { } // Backups - const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService); + const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService, this.stateService); services.set(IBackupMainService, backupMainService); + // Workspaces + const workspacesManagementMainService = new WorkspacesManagementMainService(this.environmentMainService, this.logService, this.userDataProfilesMainService, backupMainService, dialogMainService); + services.set(IWorkspacesManagementMainService, workspacesManagementMainService); + services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService, undefined, false /* proxied to other processes */)); + services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService, undefined, false)); + // URL handling - services.set(IURLService, new SyncDescriptor(NativeURLService)); + services.set(IURLService, new SyncDescriptor(NativeURLService, undefined, false /* proxied to other processes */)); // Telemetry if (supportsTelemetry(this.productService, this.environmentMainService)) { const isInternal = isInternalTelemetry(this.productService, this.configurationService); const channel = getDelayedChannel(sharedProcessReady.then(client => client.getChannel('telemetryAppender'))); const appender = new TelemetryAppenderClient(channel); - const commonProperties = resolveCommonProperties(this.fileService, release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, isInternal, this.environmentMainService.installSourcePath); + const commonProperties = resolveCommonProperties(release(), hostname(), process.arch, this.productService.commit, this.productService.version, machineId, isInternal); const piiPaths = getPiiPathsFromEnvironment(this.environmentMainService); const config: ITelemetryServiceConfig = { appenders: [appender], commonProperties, piiPaths, sendErrorTelemetry: true }; - services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); + services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config], false)); } else { services.set(ITelemetryService, NullTelemetryService); } // Default Extensions Profile Init - services.set(IDefaultExtensionsProfileInitService, ProxyChannel.toService(getDelayedChannel(sharedProcessReady.then(client => client.getChannel('IDefaultExtensionsProfileInitService'))))); + services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true)); + services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true)); + + // Utility Process Worker + services.set(IUtilityProcessWorkerMainService, new SyncDescriptor(UtilityProcessWorkerMainService, undefined, true)); // Init services that require it - await backupMainService.initialize(); + await Promises.settled([ + backupMainService.initialize(), + workspacesManagementMainService.initialize() + ]); return this.mainInstantiationService.createChild(services); } @@ -793,17 +1088,20 @@ export class CodeApplication extends Disposable { mainProcessElectronServer.registerChannel('storage', storageChannel); sharedProcessClient.then(client => client.registerChannel('storage', storageChannel)); + // Profile Storage Changes Listener (shared process) + const profileStorageListener = this._register(new ProfileStorageChangesListenerChannel(accessor.get(IStorageMainService), accessor.get(IUserDataProfilesMainService), this.logService)); + sharedProcessClient.then(client => client.registerChannel('profileStorageListener', profileStorageListener)); + + // Terminal + const ptyHostChannel = ProxyChannel.fromService(accessor.get(ILocalPtyService)); + mainProcessElectronServer.registerChannel(TerminalIpcChannels.LocalPty, ptyHostChannel); + // External Terminal const externalTerminalChannel = ProxyChannel.fromService(accessor.get(IExternalTerminalMainService)); mainProcessElectronServer.registerChannel('externalTerminal', externalTerminalChannel); - // 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),); + const loggerChannel = new LoggerChannel(accessor.get(ILoggerMainService),); mainProcessElectronServer.registerChannel('logger', loggerChannel); sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); @@ -814,200 +1112,108 @@ export class CodeApplication extends Disposable { // Extension Host Starter const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter)); mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel); + + // Utility Process Worker + const utilityProcessWorkerChannel = ProxyChannel.fromService(accessor.get(IUtilityProcessWorkerMainService)); + mainProcessElectronServer.registerChannel(ipcUtilityProcessWorkerChannelName, utilityProcessWorkerChannel); } - private openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] { + private async openFirstWindow(accessor: ServicesAccessor, initialProtocolUrls: IInitialProtocolUrls | undefined): Promise { const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); - const urlService = accessor.get(IURLService); - const nativeHostMainService = accessor.get(INativeHostMainService); - // Signal phase: ready (services set) - this.lifecycleMainService.phase = LifecycleMainPhase.Ready; - - // 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.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : [], - - // macOS: open-url events - ...((global).getOpenUrls() || []) as string[] - - ].map(url => { - try { - return { uri: URI.parse(url), url }; - } catch { - return undefined; - } - }).filter((obj): obj is { uri: URI; url: string } => { - if (!obj) { - return false; - } - - // 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(obj.uri); - if (windowOpenable) { - pendingWindowOpenablesFromProtocolLinks.push(windowOpenable); - - return false; - } - - return true; - }); - - // 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.environmentMainService; - const productService = this.productService; - const logService = this.logService; - urlService.registerHandler({ - async handleURL(uri: URI, options?: IOpenURLOptions): Promise { - logService.trace('app#handleURL: ', uri.toString(true), options); - - if (uri.scheme === productService.urlProtocol && uri.path === 'workspace') { - uri = uri.with({ - authority: 'file', - path: URI.parse(uri.query).path, - query: '' - }); - } - - // If URI should be blocked, behave as if it's handled - if (app.shouldBlockURI(uri)) { - return true; - } - - let shouldOpenInNewWindow = false; - - // We should handle the URI in a new window if the URL contains `windowId=_blank` - const params = new URLSearchParams(uri.query); - if (params.get('windowId') === '_blank') { - params.delete('windowId'); - uri = uri.with({ query: params.toString() }); - shouldOpenInNewWindow = true; - } - - // or if no window is open (macOS only) - shouldOpenInNewWindow ||= isMacintosh && windowsMainService.getWindowCount() === 0; - - // Pass along edit session id - if (params.get('editSessionId') !== null) { - environmentService.editSessionId = params.get('editSessionId') ?? undefined; - params.delete('editSessionId'); - uri = uri.with({ query: params.toString() }); - } - - // Check for URIs to open in window - const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri); - logService.trace('app#handleURL: windowOpenableFromProtocolLink = ', windowOpenableFromProtocolLink); - if (windowOpenableFromProtocolLink) { - const [window] = windowsMainService.open({ - context: OpenContext.API, - cli: { ...environmentService.args }, - urisToOpen: [windowOpenableFromProtocolLink], - forceNewWindow: shouldOpenInNewWindow, - gotoLineMode: true - // remoteAuthority: will be determined based on windowOpenableFromProtocolLink - }); - - window.focus(); // this should help ensuring that the right window gets focus when multiple are opened - - return true; - } - - if (shouldOpenInNewWindow) { - const [window] = windowsMainService.open({ - context: OpenContext.API, - cli: { ...environmentService.args }, - forceNewWindow: true, - forceEmpty: true, - gotoLineMode: true, - remoteAuthority: getRemoteAuthority(uri) - }); - - await window.ready(); - - return urlService.open(uri, options); - } - - return false; - } - }); - - // Create a URL handler which forwards to the last active window - 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 = 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.environmentMainService, this.productService)); - - // Open our first window - const args = this.environmentMainService.args; - const macOpenFiles: string[] = (global).macOpenFiles; const context = isLaunchedFromCli(process.env) ? OpenContext.CLI : OpenContext.DESKTOP; + const args = this.environmentMainService.args; + + // First check for windows from protocol links to open + if (initialProtocolUrls) { + + // Openables can open as windows directly + if (initialProtocolUrls.openables.length > 0) { + return windowsMainService.open({ + context, + cli: args, + urisToOpen: initialProtocolUrls.openables, + gotoLineMode: true, + initialStartup: true + // remoteAuthority: will be determined based on openables + }); + } + + // Protocol links with `windowId=_blank` on startup + // should be handled in a special way: + // We take the first one of these and open an empty + // window for it. This ensures we are not restoring + // all windows of the previous session. + // If there are any more URLs like these, they will + // be handled from the URL listeners installed later. + + if (initialProtocolUrls.urls.length > 0) { + for (const protocolUrl of initialProtocolUrls.urls) { + const params = new URLSearchParams(protocolUrl.uri.query); + if (params.get('windowId') === '_blank') { + + // It is important here that we remove `windowId=_blank` from + // this URL because here we open an empty window for it. + + params.delete('windowId'); + protocolUrl.originalUrl = protocolUrl.uri.toString(true); + protocolUrl.uri = protocolUrl.uri.with({ query: params.toString() }); + + return windowsMainService.open({ + context, + cli: args, + forceNewWindow: true, + forceEmpty: true, + gotoLineMode: true, + initialStartup: true + // remoteAuthority: will be determined based on openables + }); + } + } + } + } + + const macOpenFiles: string[] = (global).macOpenFiles; const hasCliArgs = args._.length; const hasFolderURIs = !!args['folder-uri']; const hasFileURIs = !!args['file-uri']; const noRecentEntry = args['skip-add-to-recently-opened'] === true; const waitMarkerFileURI = args.wait && args.waitMarkerFilePath ? URI.file(args.waitMarkerFilePath) : undefined; const remoteAuthority = args.remote || undefined; + const forceProfile = args.profile; + const forceTempProfile = args['profile-temp']; - // check for a pending window to open from URI - // e.g. when running code with --open-uri from - // a protocol handler - if (pendingWindowOpenablesFromProtocolLinks.length > 0) { - return windowsMainService.open({ - context, - cli: args, - urisToOpen: pendingWindowOpenablesFromProtocolLinks, - gotoLineMode: true, - initialStartup: true - // remoteAuthority: will be determined based on pendingWindowOpenablesFromProtocolLinks - }); - } + // Started without file/folder arguments + if (!hasCliArgs && !hasFolderURIs && !hasFileURIs) { - // new window if "-n" - if (args['new-window'] && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { - return windowsMainService.open({ - context, - cli: args, - forceNewWindow: true, - forceEmpty: true, - noRecentEntry, - waitMarkerFileURI, - initialStartup: true, - remoteAuthority - }); - } + // Force new window + if (args['new-window'] || forceProfile || forceTempProfile) { + return windowsMainService.open({ + context, + cli: args, + forceNewWindow: true, + forceEmpty: true, + noRecentEntry, + waitMarkerFileURI, + initialStartup: true, + remoteAuthority, + forceProfile, + forceTempProfile + }); + } - // mac: open-file event received on startup - if (macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { - return windowsMainService.open({ - context: OpenContext.DOCK, - cli: args, - urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)), - noRecentEntry, - waitMarkerFileURI, - initialStartup: true, - // remoteAuthority: will be determined based on macOpenFiles - }); + // mac: open-file event received on startup + if (macOpenFiles.length) { + return windowsMainService.open({ + context: OpenContext.DOCK, + cli: args, + urisToOpen: macOpenFiles.map(path => (hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) })), + noRecentEntry, + waitMarkerFileURI, + initialStartup: true, + // remoteAuthority: will be determined based on macOpenFiles + }); + } } // default: read paths from cli @@ -1021,158 +1227,16 @@ export class CodeApplication extends Disposable { waitMarkerFileURI, gotoLineMode: args.goto, initialStartup: true, - remoteAuthority + remoteAuthority, + forceProfile, + forceTempProfile }); } - private shouldBlockURI(uri: URI): boolean { - if (uri.authority === Schemas.file && isWindows) { - const res = dialog.showMessageBoxSync({ - title: this.productService.nameLong, - type: 'question', - buttons: [ - mnemonicButtonLabel(localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes")), - mnemonicButtonLabel(localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No")), - ], - defaultId: 0, - 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, { os: OS, tildify: this.environmentMainService }), this.productService.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 - }); + private afterWindowOpen(): void { - if (res === 1) { - return true; - } - } - - return false; - } - - private getWindowOpenableFromProtocolLink(uri: URI): IWindowOpenable | undefined { - if (!uri.path) { - return undefined; - } - - // File path - if (uri.authority === Schemas.file) { - const fileUri = URI.file(uri.fsPath); - - if (hasWorkspaceFileExtension(fileUri)) { - return { workspaceUri: fileUri }; - } - - return { fileUri }; - } - - // Remote path - else if (uri.authority === Schemas.vscodeRemote) { - // Example conversion: - // From: vscode://vscode-remote/wsl+ubuntu/mnt/c/GitDevelopment/monaco - // To: vscode-remote://wsl+ubuntu/mnt/c/GitDevelopment/monaco - const secondSlash = uri.path.indexOf(posix.sep, 1 /* skip over the leading slash */); - if (secondSlash !== -1) { - const authority = uri.path.substring(1, secondSlash); - const path = uri.path.substring(secondSlash); - const remoteUri = URI.from({ scheme: Schemas.vscodeRemote, authority, path, query: uri.query, fragment: uri.fragment }); - - if (hasWorkspaceFileExtension(path)) { - return { workspaceUri: remoteUri }; - } - - if (/:[\d]+$/.test(path)) { - // path with :line:column syntax - return { fileUri: remoteUri }; - } - - return { folderUri: remoteUri }; - } - } - - return undefined; - } - - private getWindowOpenableFromPathSync(path: string): IWindowOpenable { - try { - const fileStat = statSync(path); - if (fileStat.isDirectory()) { - return { folderUri: URI.file(path) }; - } - - if (hasWorkspaceFileExtension(path)) { - return { workspaceUri: URI.file(path) }; - } - } catch (error) { - // ignore errors - } - - return { fileUri: URI.file(path) }; - } - - private async afterWindowOpen(accessor: ServicesAccessor, sharedProcess: SharedProcess): Promise { - - // Signal phase: after window open - this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; - - // Observe shared process for errors - let willShutdown = false; - once(this.lifecycleMainService.onWillShutdown)(() => willShutdown = true); - const telemetryService = accessor.get(ITelemetryService); - this._register(sharedProcess.onDidError(({ type, details }) => { - - // Logging - let message: string; - switch (type) { - case WindowError.UNRESPONSIVE: - message = 'SharedProcess: detected unresponsive window'; - break; - case WindowError.CRASHED: - message = `SharedProcess: crashed (detail: ${details?.reason ?? ''}, code: ${details?.exitCode ?? ''})`; - break; - case WindowError.LOAD: - message = `SharedProcess: failed to load (detail: ${details?.reason ?? ''}, code: ${details?.exitCode ?? ''})`; - break; - } - onUnexpectedError(new Error(message)); - - // Telemetry - type SharedProcessErrorClassification = { - type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The type of shared process crash to understand the nature of the crash better.' }; - reason: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The type of shared process crash to understand the nature of the crash better.' }; - code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The type of shared process crash to understand the nature of the crash better.' }; - visible: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether shared process window was visible or not.' }; - shuttingdown: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Whether the application is shutting down when the crash happens.' }; - owner: 'bpasero'; - comment: 'Event which fires whenever an error occurs in the shared process'; - - }; - type SharedProcessErrorEvent = { - type: WindowError; - reason: string | undefined; - code: number | undefined; - visible: boolean; - shuttingdown: boolean; - }; - telemetryService.publicLog2('sharedprocesserror', { - type, - reason: details?.reason, - code: details?.exitCode, - visible: sharedProcess.isVisible(), - shuttingdown: willShutdown - }); - })); - - // Windows: install mutex - const win32MutexName = this.productService.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); - } - } + // Windows: mutex + this.installMutex(); // Remote Authorities protocol.registerHttpProtocol(Schemas.vscodeRemoteResource, (request, callback) => { @@ -1182,28 +1246,59 @@ export class CodeApplication extends Disposable { }); }); - // Initialize update service - const updateService = accessor.get(IUpdateService); - if (updateService instanceof Win32UpdateService || updateService instanceof LinuxUpdateService || updateService instanceof DarwinUpdateService) { - await updateService.initialize(); - } - // Start to fetch shell environment (if needed) after window has opened // Since this operation can take a long time, we want to warm it up while // the window is opening. // We also show an error to the user in case this fails. this.resolveShellEnvironment(this.environmentMainService.args, process.env, true); + // Crash reporter + this.updateCrashReporterEnablement(); + } + + private async installMutex(): Promise { + const win32MutexName = this.productService.win32MutexName; + if (isWindows && win32MutexName) { + try { + const WindowsMutex = await import('@vscode/windows-mutex'); + const mutex = new WindowsMutex.Mutex(win32MutexName); + once(this.lifecycleMainService.onWillShutdown)(() => mutex.release()); + } catch (error) { + this.logService.error(error); + } + } + } + + private async resolveShellEnvironment(args: NativeParsedArgs, env: IProcessEnvironment, notifyOnError: boolean): Promise { + try { + return await getResolvedShellEnv(this.configurationService, this.logService, args, env); + } catch (error) { + const errorMessage = toErrorMessage(error); + if (notifyOnError) { + this.windowsMainService?.sendToFocused('vscode:showResolveShellEnvError', errorMessage); + } else { + this.logService.error(errorMessage); + } + } + + return {}; + } + + private async updateCrashReporterEnablement(): Promise { + // If enable-crash-reporter argv is undefined then this is a fresh start, - // based on telemetry.enableCrashreporter settings, generate a UUID which + // 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.environmentMainService.argvResource); const argvString = argvContent.value.toString(); const argvJSON = JSON.parse(stripComments(argvString)); + const telemetryLevel = getTelemetryLevel(this.configurationService); + const enableCrashReporter = telemetryLevel >= TelemetryLevel.CRASH; + + // Initial startup if (argvJSON['enable-crash-reporter'] === undefined) { - const telemetryLevel = getTelemetryLevel(this.configurationService); - const enableCrashReporter = telemetryLevel >= TelemetryLevel.CRASH; const additionalArgvContent = [ '', ' // Allows to disable crash reporting.', @@ -1219,64 +1314,16 @@ export class CodeApplication extends Disposable { await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString)); } + + // Subsequent startup: update crash reporter value if changed + else { + const newArgvString = argvString.replace(/"enable-crash-reporter": .*,/, `"enable-crash-reporter": ${enableCrashReporter},`); + if (newArgvString !== argvString) { + await this.fileService.writeFile(this.environmentMainService.argvResource, VSBuffer.fromString(newArgvString)); + } + } } catch (error) { this.logService.error(error); } } - - private async resolveShellEnvironment(args: NativeParsedArgs, env: IProcessEnvironment, notifyOnError: boolean): Promise { - try { - return await getResolvedShellEnv(this.logService, args, env); - } catch (error) { - const errorMessage = toErrorMessage(error); - if (notifyOnError) { - this.windowsMainService?.sendToFocused('vscode:showResolveShellEnvError', errorMessage); - } else { - this.logService.error(errorMessage); - } - } - - return {}; - } - - 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(`${randomPath(this.environmentMainService.userHome.fsPath, this.productService.applicationName)}.trace.txt`); - - if (!timeout) { - dialogMainService.showMessageBox({ - title: this.productService.nameLong, - 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: [mnemonicButtonLabel(localize({ key: 'trace.ok', comment: ['&& denotes a mnemonic'] }, "&&OK"))], - defaultId: 0, - noLink: true - }, 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/main.ts b/src/vs/code/electron-main/main.ts index 54af9e0983..747abd44dd 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -14,9 +14,9 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ExpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; import { once } from 'vs/base/common/functional'; -import { getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels'; +import { getPathLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; -import { basename, join, resolve } from 'vs/base/common/path'; +import { basename, resolve } from 'vs/base/common/path'; import { mark } from 'vs/base/common/performance'; import { IProcessEnvironment, isMacintosh, isWindows, OS } from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; @@ -34,7 +34,7 @@ import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsServ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { addArg, parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper'; -import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; +import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; @@ -44,10 +44,8 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { ILifecycleMainService, LifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { BufferLogService } from 'vs/platform/log/common/bufferLog'; -import { ConsoleMainLogger, getLogLevel, ILoggerService, ILogService, MultiplexLogService } from 'vs/platform/log/common/log'; -import { LoggerService } from 'vs/platform/log/node/loggerService'; -import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import { BufferLogger } from 'vs/platform/log/common/bufferLog'; +import { ConsoleMainLogger, getLogLevel, ILoggerService, ILogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IProtocolMainService } from 'vs/platform/protocol/electron-main/protocol'; @@ -58,8 +56,7 @@ import { IRequestService } from 'vs/platform/request/common/request'; import { RequestMainService } from 'vs/platform/request/electron-main/requestMainService'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; -import { IStateMainService } from 'vs/platform/state/electron-main/state'; -import { StateMainService } from 'vs/platform/state/electron-main/stateMainService'; +import { IStateReadService, IStateService } from 'vs/platform/state/node/state'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { IUserDataProfilesMainService, UserDataProfilesMainService } from 'vs/platform/userDataProfile/electron-main/userDataProfile'; @@ -69,7 +66,10 @@ import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ILoggerMainService, LoggerMainService } from 'vs/platform/log/electron-main/loggerService'; +import { LogService } from 'vs/platform/log/common/logService'; +import { massageMessageBoxOptions } from 'vs/platform/dialogs/common/dialogs'; +import { SaveStrategy, StateService } from 'vs/platform/state/node/stateService'; /** * The main VS Code entry point. @@ -103,11 +103,11 @@ class CodeMain { // Init services try { - await this.initServices(environmentMainService, userDataProfilesMainService, configurationService, stateMainService); + await this.initServices(environmentMainService, userDataProfilesMainService, configurationService, stateMainService, productService); } catch (error) { // Show a dialog for errors that can be resolved by the user - this.handleStartupDataDirError(environmentMainService, productService.nameLong, error); + this.handleStartupDataDirError(environmentMainService, productService, error); throw error; } @@ -117,25 +117,27 @@ class CodeMain { const logService = accessor.get(ILogService); const lifecycleMainService = accessor.get(ILifecycleMainService); const fileService = accessor.get(IFileService); + const loggerService = accessor.get(ILoggerService); // 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.claimInstance(logService, environmentMainService, lifecycleMainService, instantiationService, productService, true); - // Write a lockfile to indicate an instance is running (https://github.com/microsoft/vscode/issues/127861#issuecomment-877417451) + // Write a lockfile to indicate an instance is running + // (https://github.com/microsoft/vscode/issues/127861#issuecomment-877417451) FSPromises.writeFile(environmentMainService.mainLockfile, String(process.pid)).catch(err => { - logService.warn(`Error writing main lockfile: ${err.stack}`); + logService.warn(`app#startup(): Error writing main lockfile: ${err.stack}`); }); // Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906) - bufferLogService.logger = new SpdLogLogger('main', join(environmentMainService.logsPath, 'main.log'), true, false, bufferLogService.getLevel()); + bufferLogService.logger = loggerService.createLogger('main', { name: localize('mainLog', "Main") }); // Lifecycle once(lifecycleMainService.onWillShutdown)(evt => { fileService.dispose(); configurationService.dispose(); - evt.join(FSPromises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ })); + evt.join('instanceLockfile', FSPromises.unlink(environmentMainService.mainLockfile).catch(() => { /* ignored */ })); }); return instantiationService.createInstance(CodeApplication, mainProcessNodeIpcServer, instanceEnvironment).startup(); @@ -145,7 +147,7 @@ class CodeMain { } } - private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateMainService, BufferLogService, IProductService, UserDataProfilesMainService] { + private createServices(): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogger, IProductService, UserDataProfilesMainService] { const services = new ServiceCollection(); const disposables = new DisposableStore(); process.once('exit', () => disposables.dispose()); @@ -159,11 +161,15 @@ class CodeMain { const instanceEnvironment = this.patchEnvironment(environmentMainService); // Patch `process.env` with the instance's environment services.set(IEnvironmentMainService, environmentMainService); + // Logger + const loggerService = new LoggerMainService(getLogLevel(environmentMainService), environmentMainService.logsHome); + services.set(ILoggerMainService, loggerService); + // 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 = disposables.add(new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentMainService)), bufferLogService])); + const bufferLogger = new BufferLogger(loggerService.getLogLevel()); + const logService = disposables.add(new LogService(bufferLogger, [new ConsoleMainLogger(loggerService.getLogLevel())])); services.set(ILogService, logService); // Files @@ -176,19 +182,17 @@ class CodeMain { const uriIdentityService = new UriIdentityService(fileService); services.set(IUriIdentityService, uriIdentityService); - // Logger - services.set(ILoggerService, new LoggerService(logService, fileService)); - // State - const stateMainService = new StateMainService(environmentMainService, logService, fileService); - services.set(IStateMainService, stateMainService); + const stateService = new StateService(SaveStrategy.DELAYED, environmentMainService, logService, fileService); + services.set(IStateReadService, stateService); + services.set(IStateService, stateService); // User Data Profiles - const userDataProfilesMainService = new UserDataProfilesMainService(stateMainService, uriIdentityService, environmentMainService, fileService, logService); + const userDataProfilesMainService = new UserDataProfilesMainService(stateService, uriIdentityService, environmentMainService, fileService, logService); services.set(IUserDataProfilesMainService, userDataProfilesMainService); // Policy - const policyService = isWindows && productService.win32RegValueName ? disposables.add(new NativePolicyService(productService.win32RegValueName)) + const policyService = isWindows && productService.win32RegValueName ? disposables.add(new NativePolicyService(logService, productService.win32RegValueName)) : environmentMainService.policyFile ? disposables.add(new FilePolicyService(environmentMainService.policyFile, fileService, logService)) : new NullPolicyService(); services.set(IPolicyService, policyService); @@ -198,24 +202,24 @@ class CodeMain { services.set(IConfigurationService, configurationService); // Lifecycle - services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService)); + services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService, undefined, false)); // Request - services.set(IRequestService, new SyncDescriptor(RequestMainService)); + services.set(IRequestService, new SyncDescriptor(RequestMainService, undefined, true)); // Themes services.set(IThemeMainService, new SyncDescriptor(ThemeMainService)); // Signing - services.set(ISignService, new SyncDescriptor(SignService)); + services.set(ISignService, new SyncDescriptor(SignService, undefined, false /* proxied to other processes */)); // Tunnel services.set(ITunnelService, new SyncDescriptor(TunnelService)); - // Protocol - services.set(IProtocolMainService, new SyncDescriptor(ProtocolMainService)); + // Protocol (instantiated early and not using sync descriptor for security reasons) + services.set(IProtocolMainService, new ProtocolMainService(environmentMainService, userDataProfilesMainService, logService)); - return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateMainService, bufferLogService, productService, userDataProfilesMainService]; + return [new InstantiationService(services, true), instanceEnvironment, environmentMainService, configurationService, stateService, bufferLogger, productService, userDataProfilesMainService]; } private patchEnvironment(environmentMainService: IEnvironmentMainService): IProcessEnvironment { @@ -235,14 +239,14 @@ class CodeMain { return instanceEnvironment; } - private async initServices(environmentMainService: IEnvironmentMainService, userDataProfilesMainService: UserDataProfilesMainService, configurationService: ConfigurationService, stateMainService: StateMainService): Promise { + private async initServices(environmentMainService: IEnvironmentMainService, userDataProfilesMainService: UserDataProfilesMainService, configurationService: ConfigurationService, stateService: StateService, productService: IProductService): Promise { await Promises.settled([ // Environment service (paths) Promise.all([ environmentMainService.extensionsPath, environmentMainService.codeCachePath, - environmentMainService.logsPath, + environmentMainService.logsHome.fsPath, userDataProfilesMainService.defaultProfile.globalStorageHome.fsPath, environmentMainService.workspaceStorageHome.fsPath, environmentMainService.localHistoryHome.fsPath, @@ -250,13 +254,14 @@ class CodeMain { ].map(path => path ? FSPromises.mkdir(path, { recursive: true }) : undefined)), // State service - stateMainService.init(), + stateService.init(), // Configuration service configurationService.initialize() ]); - userDataProfilesMainService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); + // Initialize user data profiles after initializing the state + userDataProfilesMainService.init(); } private async claimInstance(logService: ILogService, environmentMainService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, productService: IProductService, retry: boolean): Promise { @@ -273,11 +278,11 @@ class CodeMain { } catch (error) { // Handle unexpected errors (the only expected error is EADDRINUSE that - // indicates a second instance of Code is running) + // indicates another instance of VS Code is running) if (error.code !== 'EADDRINUSE') { // Show a dialog for errors that can be resolved by the user - this.handleStartupDataDirError(environmentMainService, productService.nameLong, error); + this.handleStartupDataDirError(environmentMainService, productService, error); // Any other runtime error is just printed to the console throw error; @@ -293,9 +298,9 @@ class CodeMain { if (!retry || isWindows || error.code !== 'ECONNREFUSED') { if (error.code === 'EPERM') { this.showStartupWarningDialog( - localize('secondInstanceAdmin', "A second instance of {0} is already running as administrator.", productService.nameShort), + localize('secondInstanceAdmin', "Another instance of {0} is already running as administrator.", productService.nameShort), localize('secondInstanceAdminDetail', "Please close the other instance and try again."), - productService.nameLong + productService ); } @@ -318,7 +323,7 @@ class CodeMain { // Tests from CLI require to be the only instance currently 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.'; + const msg = `Running extension tests from the command line is currently only supported if no other instance of ${productService.nameShort} is running.`; logService.error(msg); client.dispose(); @@ -334,7 +339,7 @@ class CodeMain { this.showStartupWarningDialog( localize('secondInstanceNoResponse', "Another instance of {0} is running but not responding", productService.nameShort), localize('secondInstanceNoResponseDetail', "Please close all other instances and try again."), - productService.nameLong + productService ); }, 10000); } @@ -346,9 +351,9 @@ class CodeMain { if (environmentMainService.args.status) { return instantiationService.invokeFunction(async () => { const diagnosticsService = new DiagnosticsService(NullTelemetryService, productService); - const mainProcessInfo = await otherInstanceLaunchMainService.getMainProcessInfo(); + const mainDiagnostics = await otherInstanceDiagnosticsMainService.getMainDiagnostics(); const remoteDiagnostics = await otherInstanceDiagnosticsMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true }); - const diagnostics = await diagnosticsService.getDiagnostics(mainProcessInfo, remoteDiagnostics); + const diagnostics = await diagnosticsService.getDiagnostics(mainDiagnostics, remoteDiagnostics); console.log(diagnostics); throw new ExpectedError(); @@ -377,7 +382,7 @@ class CodeMain { // Print --status usage info if (environmentMainService.args.status) { - logService.warn('Warning: The --status argument can only be used if Code is already running. Please run it again after Code has started.'); + logService.warn(localize('statusWarning', "Warning: The --status argument can only be used if {0} is already running. Please run it again after {0} has started.", productService.nameShort)); throw new ExpectedError('Terminating...'); } @@ -389,31 +394,30 @@ class CodeMain { return mainProcessNodeIpcServer; } - private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, title: string, error: NodeJS.ErrnoException): void { + private handleStartupDataDirError(environmentMainService: IEnvironmentMainService, productService: IProductService, error: NodeJS.ErrnoException): void { if (error.code === 'EACCES' || error.code === 'EPERM') { const directories = coalesce([environmentMainService.userDataPath, environmentMainService.extensionsPath, XDG_RUNTIME_DIR]).map(folder => getPathLabel(URI.file(folder), { os: OS, tildify: environmentMainService })); this.showStartupWarningDialog( localize('startupDataDirError', "Unable to write program user data."), localize('startupUserDataAndExtensionsDirErrorDetail', "{0}\n\nPlease make sure the following directories are writeable:\n\n{1}", toErrorMessage(error), directories.join('\n')), - title + productService ); } } - private showStartupWarningDialog(message: string, detail: string, title: string): void { + private showStartupWarningDialog(message: string, detail: string, productService: IProductService): void { + // use sync variant here because we likely exit after this method // due to startup issues and otherwise the dialog seems to disappear // https://github.com/microsoft/vscode/issues/104493 - dialog.showMessageBoxSync({ - title, + + dialog.showMessageBoxSync(massageMessageBoxOptions({ type: 'warning', - buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], + buttons: [localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close")], message, - detail, - defaultId: 0, - noLink: true - }); + detail + }, productService).options); } private async windowsAllowSetForegroundWindow(launchMainService: ILaunchMainService, logService: ILogService): Promise { @@ -468,9 +472,10 @@ class CodeMain { // 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. + // added as argument. This can happen if VS Code was started from CLI. + if (args.wait && !args.waitMarkerFilePath) { - const waitMarkerFilePath = createWaitMarkerFile(args.verbose); + const waitMarkerFilePath = createWaitMarkerFileSync(args.verbose); if (waitMarkerFilePath) { addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath); args.waitMarkerFilePath = waitMarkerFilePath; diff --git a/src/vs/code/electron-sandbox/issue/issueReporter-dev.html b/src/vs/code/electron-sandbox/issue/issueReporter-dev.html new file mode 100644 index 0000000000..a303a97d47 --- /dev/null +++ b/src/vs/code/electron-sandbox/issue/issueReporter-dev.html @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/vs/code/electron-sandbox/issue/issueReporter.html b/src/vs/code/electron-sandbox/issue/issueReporter.html index 3a0cb4be74..c6290004d2 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporter.html +++ b/src/vs/code/electron-sandbox/issue/issueReporter.html @@ -3,21 +3,41 @@ - + + - - - - - - + diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 6bd8da6322..355193a25c 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -5,42 +5,20 @@ import 'vs/css!./media/issueReporter'; import 'vs/base/browser/ui/codicons/codiconStyles'; // make sure codicon css is loaded -import { localize } from 'vs/nls'; -import { $, reset, safeInnerHtml, windowOpenNoOpener } from 'vs/base/browser/dom'; -import { Button } from 'vs/base/browser/ui/button/button'; -import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; -import { Delayer } from 'vs/base/common/async'; -import { Codicon } from 'vs/base/common/codicons'; -import { groupBy } from 'vs/base/common/collections'; -import { debounce } from 'vs/base/common/decorators'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { isLinux, isLinuxSnap, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { escape } from 'vs/base/common/strings'; -import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; -import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel'; +import { safeInnerHtml } from 'vs/base/browser/dom'; +import { isLinux, isWindows } from 'vs/base/common/platform'; import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage'; -import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IssueReporterWindowConfiguration, IssueType } from 'vs/platform/issue/common/issue'; -import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { IIssueMainService, IssueReporterWindowConfiguration } from 'vs/platform/issue/common/issue'; +import { INativeHostService } from 'vs/platform/native/common/native'; import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; -import { applyZoom, zoomIn, zoomOut } from 'vs/platform/window/electron-sandbox/window'; -import * as locConstants from 'sql/base/common/locConstants'; // {{SQL CARBON EDIT}} - -const MAX_URL_LENGTH = 2045; - -interface SearchResult { - html_url: string; - title: string; - state?: string; -} - -enum IssueSource { - VSCode = 'vscode', - Extension = 'extension', - Marketplace = 'marketplace' -} +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; +import { IssueReporter } from './issueReporterService'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common/extensions'; +import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; export function startup(configuration: IssueReporterWindowConfiguration) { const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac'; @@ -48,1176 +26,26 @@ export function startup(configuration: IssueReporterWindowConfiguration) { safeInnerHtml(document.body, BaseHtml()); - const issueReporter = new IssueReporter(configuration); + const instantiationService = initServices(configuration.windowId); + + const issueReporter = instantiationService.createInstance(IssueReporter, configuration); issueReporter.render(); document.body.style.display = 'block'; issueReporter.setInitialFocus(); } -export class IssueReporter extends Disposable { - private nativeHostService!: INativeHostService; - private readonly issueReporterModel: IssueReporterModel; - private numberOfSearchResultsDisplayed = 0; - private receivedSystemInfo = false; - private receivedPerformanceInfo = false; - private shouldQueueSearch = false; - private hasBeenSubmitted = false; - private delayedSubmit = new Delayer(300); +function initServices(windowId: number) { + const services = new ServiceCollection(); - private readonly previewButton!: Button; - - constructor(private readonly configuration: IssueReporterWindowConfiguration) { - super(); - - const mainProcessService = new ElectronIPCMainProcessService(configuration.windowId); - this.nativeHostService = new NativeHostService(configuration.windowId, mainProcessService) as INativeHostService; - - 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.darwinUniversalAssetId ? `${configuration.product.version} (Universal)` : configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, - os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isLinuxSnap ? ' snap' : ''}` - }, - extensionsDisabled: !!configuration.disableExtensions, - fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, - selectedExtension: targetExtension, - }); - - const issueReporterElement = this.getElementById('issue-reporter'); - if (issueReporterElement) { - this.previewButton = new Button(issueReporterElement); - this.updatePreviewButtonState(); - } - - const issueTitle = configuration.data.issueTitle; - if (issueTitle) { - const issueTitleElement = this.getElementById('issue-title'); - if (issueTitleElement) { - issueTitleElement.value = issueTitle; - } - } - - const issueBody = configuration.data.issueBody; - if (issueBody) { - const description = this.getElementById('description'); - if (description) { - description.value = issueBody; - this.issueReporterModel.update({ issueDescription: issueBody }); - } - } - - ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_: unknown, info: Partial) => { - this.issueReporterModel.update(info); - this.receivedPerformanceInfo = true; - - const state = this.issueReporterModel.getData(); - this.updateProcessInfo(state); - this.updateWorkspaceInfo(state); - this.updatePreviewButtonState(); - }); - - ipcRenderer.on('vscode:issueSystemInfoResponse', (_: unknown, info: SystemInfo) => { - this.issueReporterModel.update({ systemInfo: info }); - this.receivedSystemInfo = true; - - this.updateSystemInfo(this.issueReporterModel.getData()); - this.updatePreviewButtonState(); - }); - - ipcRenderer.send('vscode:issueSystemInfoRequest'); - if (configuration.data.issueType === IssueType.PerformanceIssue) { - ipcRenderer.send('vscode:issuePerformanceInfoRequest'); - } - - if (window.document.documentElement.lang !== 'en') { - show(this.getElementById('english')); - } - - this.setUpTypes(); - this.setEventHandlers(); - applyZoom(configuration.data.zoomLevel); - this.applyStyles(configuration.data.styles); - this.handleExtensionData(configuration.data.enabledExtensions); - this.updateExperimentsInfo(configuration.data.experiments); - this.updateRestrictedMode(configuration.data.restrictedMode); - this.updatePreviewFeaturesEnabled(configuration.data.previewFeaturesEnabled); // {{SQL CARBON EDIT}} Add preview features flag - this.updateUnsupportedMode(configuration.data.isUnsupported); + const contributedServices = getSingletonServiceDescriptors(); + for (const [id, descriptor] of contributedServices) { + services.set(id, descriptor); } - render(): void { - this.renderBlocks(); - } + services.set(IMainProcessService, new SyncDescriptor(ElectronIPCMainProcessService, [windowId])); + services.set(INativeHostService, new SyncDescriptor(NativeHostService, [windowId])); - setInitialFocus() { - const { fileOnExtension } = this.issueReporterModel.getData(); - if (fileOnExtension) { - const issueTitle = document.getElementById('issue-title'); - if (issueTitle) { - issueTitle.focus(); - } - } else { - const issueType = document.getElementById('issue-type'); - if (issueType) { - issueType.focus(); - } - } - } - - private applyStyles(styles: IssueReporterStyles) { - const styleTag = document.createElement('style'); - const content: string[] = []; - - if (styles.inputBackground) { - content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { background-color: ${styles.inputBackground}; }`); - } - - if (styles.inputBorder) { - content.push(`input[type="text"], textarea, select { border: 1px solid ${styles.inputBorder}; }`); - } else { - content.push(`input[type="text"], textarea, select { border: 1px solid transparent; }`); - } - - if (styles.inputForeground) { - content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { color: ${styles.inputForeground}; }`); - } - - if (styles.inputErrorBorder) { - content.push(`.invalid-input, .invalid-input:focus, .validation-error { border: 1px solid ${styles.inputErrorBorder} !important; }`); - content.push(`.required-input { color: ${styles.inputErrorBorder}; }`); - } - - if (styles.inputErrorBackground) { - content.push(`.validation-error { background: ${styles.inputErrorBackground}; }`); - } - - if (styles.inputErrorForeground) { - content.push(`.validation-error { color: ${styles.inputErrorForeground}; }`); - } - - if (styles.inputActiveBorder) { - content.push(`input[type='text']:focus, textarea:focus, select:focus, summary:focus, button:focus, a:focus, .workbenchCommand:focus { border: 1px solid ${styles.inputActiveBorder}; outline-style: none; }`); - } - - if (styles.textLinkColor) { - content.push(`a, .workbenchCommand { color: ${styles.textLinkColor}; }`); - } - - if (styles.textLinkColor) { - content.push(`a { color: ${styles.textLinkColor}; }`); - } - - if (styles.textLinkActiveForeground) { - content.push(`a:hover, .workbenchCommand:hover { color: ${styles.textLinkActiveForeground}; }`); - } - - if (styles.sliderBackgroundColor) { - content.push(`::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`); - } - - if (styles.sliderActiveColor) { - content.push(`::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`); - } - - if (styles.sliderHoverColor) { - content.push(`::--webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`); - } - - if (styles.buttonBackground) { - content.push(`.monaco-text-button { background-color: ${styles.buttonBackground} !important; }`); - } - - if (styles.buttonForeground) { - content.push(`.monaco-text-button { color: ${styles.buttonForeground} !important; }`); - } - - if (styles.buttonHoverBackground) { - content.push(`.monaco-text-button:not(.disabled):hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`); - } - - styleTag.textContent = content.join('\n'); - document.head.appendChild(styleTag); - document.body.style.color = styles.color || ''; - } - - private handleExtensionData(extensions: IssueReporterExtensionData[]) { - const installedExtensions = extensions.filter(x => !x.isBuiltin); - const { nonThemes, themes } = groupBy(installedExtensions, ext => { - return ext.isTheme ? 'themes' : 'nonThemes'; - }); - - const numberOfThemeExtesions = themes && themes.length; - this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); - this.updateExtensionTable(nonThemes, numberOfThemeExtesions); - - if (this.configuration.disableExtensions || installedExtensions.length === 0) { - (this.getElementById('disableExtensions')).disabled = true; - } - - this.updateExtensionSelector(installedExtensions); - } - - private setEventHandlers(): void { - this.addEventListener('issue-type', 'change', (event: Event) => { - const issueType = parseInt((event.target).value); - this.issueReporterModel.update({ issueType: issueType }); - if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) { - ipcRenderer.send('vscode:issuePerformanceInfoRequest'); - } - this.updatePreviewButtonState(); - this.setSourceOptions(); - this.render(); - }); - - (['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] }); - }); - }); - - const showInfoElements = document.getElementsByClassName('showInfo'); - for (let i = 0; i < showInfoElements.length; i++) { - const showInfo = showInfoElements.item(i)!; - (showInfo as HTMLAnchorElement).addEventListener('click', (e: MouseEvent) => { - e.preventDefault(); - const label = (e.target); - if (label) { - const containingElement = label.parentElement && label.parentElement.parentElement; - const info = containingElement && containingElement.lastElementChild; - if (info && info.classList.contains('hidden')) { - show(info); - label.textContent = localize('hide', "hide"); - } else { - hide(info); - label.textContent = localize('show', "show"); - } - } - }); - } - - this.addEventListener('issue-source', 'change', (e: Event) => { - const value = (e.target).value; - const problemSourceHelpText = this.getElementById('problem-source-help-text')!; - if (value === '') { - this.issueReporterModel.update({ fileOnExtension: undefined }); - show(problemSourceHelpText); - this.clearSearchResults(); - this.render(); - return; - } else { - hide(problemSourceHelpText); - } - - let fileOnExtension, fileOnMarketplace = false; - if (value === IssueSource.Extension) { - fileOnExtension = true; - } else if (value === IssueSource.Marketplace) { - fileOnMarketplace = true; - } - - this.issueReporterModel.update({ fileOnExtension, fileOnMarketplace }); - this.render(); - - const title = (this.getElementById('issue-title')).value; - this.searchIssues(title, fileOnExtension, fileOnMarketplace); - }); - - this.addEventListener('description', 'input', (e: Event) => { - const issueDescription = (e.target).value; - this.issueReporterModel.update({ issueDescription }); - - // Only search for extension issues on title change - if (this.issueReporterModel.fileOnExtension() === false) { - const title = (this.getElementById('issue-title')).value; - this.searchVSCodeIssues(title, issueDescription); - } - }); - - this.addEventListener('issue-title', 'input', (e: Event) => { - const title = (e.target).value; - const lengthValidationMessage = this.getElementById('issue-title-length-validation-error'); - const issueUrl = this.getIssueUrl(); - if (title && this.getIssueUrlWithTitle(title, issueUrl).length > MAX_URL_LENGTH) { - show(lengthValidationMessage); - } else { - hide(lengthValidationMessage); - } - const issueSource = this.getElementById('issue-source'); - if (!issueSource || issueSource.value === '') { - return; - } - - const { fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData(); - this.searchIssues(title, fileOnExtension, fileOnMarketplace); - }); - - this.previewButton.onDidClick(async () => { - this.delayedSubmit.trigger(async () => { - this.createIssue(); - }); - }); - - function sendWorkbenchCommand(commandId: string) { - ipcRenderer.send('vscode:workbenchCommand', { id: commandId, from: 'issueReporter' }); - } - - this.addEventListener('disableExtensions', 'click', () => { - sendWorkbenchCommand('workbench.action.reloadWindowWithExtensionsDisabled'); - }); - - this.addEventListener('extensionBugsLink', 'click', (e: Event) => { - const url = (e.target).innerText; - windowOpenNoOpener(url); - }); - - this.addEventListener('disableExtensions', 'keydown', (e: Event) => { - e.stopPropagation(); - if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) { - sendWorkbenchCommand('workbench.extensions.action.disableAll'); - sendWorkbenchCommand('workbench.action.reloadWindow'); - } - }); - - document.onkeydown = async (e: KeyboardEvent) => { - const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; - // Cmd/Ctrl+Enter previews issue and closes window - if (cmdOrCtrlKey && e.keyCode === 13) { - this.delayedSubmit.trigger(async () => { - if (await this.createIssue()) { - ipcRenderer.send('vscode:closeIssueReporter'); - } - }); - } - - // Cmd/Ctrl + w closes issue window - if (cmdOrCtrlKey && e.keyCode === 87) { - e.stopPropagation(); - e.preventDefault(); - - const issueTitle = (this.getElementById('issue-title'))!.value; - const { issueDescription } = this.issueReporterModel.getData(); - if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) { - ipcRenderer.send('vscode:issueReporterConfirmClose'); - } else { - ipcRenderer.send('vscode:closeIssueReporter'); - } - } - - // Cmd/Ctrl + zooms in - if (cmdOrCtrlKey && e.keyCode === 187) { - zoomIn(); - } - - // Cmd/Ctrl - zooms out - if (cmdOrCtrlKey && e.keyCode === 189) { - zoomOut(); - } - - // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac - // Manually perform the selection - if (isMacintosh) { - if (cmdOrCtrlKey && e.keyCode === 65 && e.target) { - if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { - (e.target).select(); - } - } - } - }; - } - - private updatePreviewButtonState() { - if (this.isPreviewEnabled()) { - 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; - this.previewButton.label = localize('loadingData', "Loading data..."); - } - } - - private isPreviewEnabled() { - const issueType = this.issueReporterModel.getData().issueType; - if (issueType === IssueType.Bug && this.receivedSystemInfo) { - return true; - } - - if (issueType === IssueType.PerformanceIssue && this.receivedSystemInfo && this.receivedPerformanceInfo) { - return true; - } - - if (issueType === IssueType.FeatureRequest) { - return true; - } - - return false; - } - - private getExtensionRepositoryUrl(): string | undefined { - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - return selectedExtension && selectedExtension.repositoryUrl; - } - - private getExtensionBugsUrl(): string | undefined { - const selectedExtension = this.issueReporterModel.getData().selectedExtension; - return selectedExtension && selectedExtension.bugsUrl; - } - - private searchVSCodeIssues(title: string, issueDescription?: string): void { - if (title) { - this.searchDuplicates(title, issueDescription); - } else { - this.clearSearchResults(); - } - } - - private searchIssues(title: string, fileOnExtension: boolean | undefined, fileOnMarketplace: boolean | undefined): void { - if (fileOnExtension) { - return this.searchExtensionIssues(title); - } - - if (fileOnMarketplace) { - return this.searchMarketplaceIssues(title); - } - - const description = this.issueReporterModel.getData().issueDescription; - this.searchVSCodeIssues(title, description); - } - - private searchExtensionIssues(title: string): void { - const url = this.getExtensionGitHubUrl(); - if (title) { - const matches = /^https?:\/\/github\.com\/(.*)/.exec(url); - if (matches && matches.length) { - const repo = matches[1]; - return this.searchGitHub(repo, title); - } - - // If the extension has no repository, display empty search results - if (this.issueReporterModel.getData().selectedExtension) { - this.clearSearchResults(); - return this.displaySearchResults([]); - - } - } - - this.clearSearchResults(); - } - - private searchMarketplaceIssues(title: string): void { - if (title) { - const gitHubInfo = this.parseGitHubUrl(this.configuration.product.reportMarketplaceIssueUrl!); - if (gitHubInfo) { - return this.searchGitHub(`${gitHubInfo.owner}/${gitHubInfo.repositoryName}`, title); - } - } - } - - private clearSearchResults(): void { - const similarIssues = this.getElementById('similar-issues')!; - similarIssues.innerText = ''; - this.numberOfSearchResultsDisplayed = 0; - } - - @debounce(300) - private searchGitHub(repo: string, title: string): void { - const query = `is:issue+repo:${repo}+${title}`; - const similarIssues = this.getElementById('similar-issues')!; - - window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => { - response.json().then(result => { - similarIssues.innerText = ''; - if (result && result.items) { - this.displaySearchResults(result.items); - } else { - // If the items property isn't present, the rate limit has been hit - const message = $('div.list-title'); - message.textContent = localize('rateLimited', "GitHub query limit exceeded. Please wait."); - similarIssues.appendChild(message); - - const resetTime = response.headers.get('X-RateLimit-Reset'); - const timeToWait = resetTime ? parseInt(resetTime) - Math.floor(Date.now() / 1000) : 1; - if (this.shouldQueueSearch) { - this.shouldQueueSearch = false; - setTimeout(() => { - this.searchGitHub(repo, title); - this.shouldQueueSearch = true; - }, timeToWait * 1000); - } - } - }).catch(_ => { - // Ignore - }); - }).catch(_ => { - // Ignore - }); - } - - @debounce(300) - private searchDuplicates(title: string, body?: string): void { - const url = 'https://ads-probot.westus.cloudapp.azure.com:7890/duplicate_candidates'; // {{SQL CARBON EDIT}} use our server - const init = { - method: 'POST', - body: JSON.stringify({ - title, - body - }), - headers: new Headers({ - 'Content-Type': 'application/json' - }) - }; - - window.fetch(url, init).then((response) => { - response.json().then(result => { - this.clearSearchResults(); - - if (result && result.candidates) { - this.displaySearchResults(result.candidates); - } else { - throw new Error('Unexpected response, no candidates property'); - } - }).catch(_ => { - // Ignore - }); - }).catch(_ => { - // Ignore - }); - } - - private displaySearchResults(results: SearchResult[]) { - const similarIssues = this.getElementById('similar-issues')!; - if (results.length) { - const issues = $('div.issues-container'); - const issuesText = $('div.list-title'); - issuesText.textContent = localize('similarIssues', "Similar issues"); - - this.numberOfSearchResultsDisplayed = results.length < 5 ? results.length : 5; - for (let i = 0; i < this.numberOfSearchResultsDisplayed; i++) { - const issue = results[i]; - const link = $('a.issue-link', { href: issue.html_url }); - link.textContent = issue.title; - link.title = issue.title; - link.addEventListener('click', (e) => this.openLink(e)); - link.addEventListener('auxclick', (e) => this.openLink(e)); - - let issueState: HTMLElement; - let item: HTMLElement; - if (issue.state) { - issueState = $('span.issue-state'); - - const issueIcon = $('span.issue-icon'); - 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"); - - issueState.title = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed"); - issueState.appendChild(issueIcon); - issueState.appendChild(issueStateLabel); - - item = $('div.issue', undefined, issueState, link); - } else { - item = $('div.issue', undefined, link); - } - - issues.appendChild(item); - } - - similarIssues.appendChild(issuesText); - similarIssues.appendChild(issues); - } else { - const message = $('div.list-title'); - message.textContent = localize('noSimilarIssues', "No similar issues found"); - similarIssues.appendChild(message); - } - } - - private setUpTypes(): void { - const makeOption = (issueType: IssueType, description: string) => $('option', { 'value': issueType.valueOf() }, escape(description)); - - const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement; - const { issueType } = this.issueReporterModel.getData(); - reset(typeSelect, - makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), - makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), - makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")), - ); - - typeSelect.value = issueType.toString(); - - this.setSourceOptions(); - } - - private makeOption(value: string, description: string, disabled: boolean): HTMLOptionElement { - const option: HTMLOptionElement = document.createElement('option'); - option.disabled = disabled; - option.value = value; - option.textContent = description; - - return option; - } - - private setSourceOptions(): void { - const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement; - const { issueType, fileOnExtension, selectedExtension } = this.issueReporterModel.getData(); - let selected = sourceSelect.selectedIndex; - if (selected === -1) { - if (fileOnExtension !== undefined) { - selected = fileOnExtension ? 2 : 1; - } else if (selectedExtension?.isBuiltin) { - selected = 1; - } - } - - sourceSelect.innerText = ''; - sourceSelect.append(this.makeOption('', localize('selectSource', "Select source"), true)); - sourceSelect.append(this.makeOption('azuredatastudio', locConstants.issueReporterMainAzuredatastudio, false)); // {{SQL CARBON EDIT}} Update name - sourceSelect.append(this.makeOption('extension', localize('extension', "An extension"), false)); - if (this.configuration.product.reportMarketplaceIssueUrl) { - sourceSelect.append(this.makeOption('marketplace', localize('marketplace', "Extensions marketplace"), false)); - } - - if (issueType !== IssueType.FeatureRequest) { - sourceSelect.append(this.makeOption('', localize('unknown', "Don't know"), false)); - } - - if (selected !== -1 && selected < sourceSelect.options.length) { - sourceSelect.selectedIndex = selected; - } else { - sourceSelect.selectedIndex = 0; - hide(this.getElementById('problem-source-help-text')); - } - } - - private renderBlocks(): void { - // Depending on Issue Type, we render different blocks and text - const { issueType, fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData(); - const blockContainer = this.getElementById('block-container'); - const systemBlock = document.querySelector('.block-system'); - const processBlock = document.querySelector('.block-process'); - const workspaceBlock = document.querySelector('.block-workspace'); - const extensionsBlock = document.querySelector('.block-extensions'); - const experimentsBlock = document.querySelector('.block-experiments'); - - const problemSource = this.getElementById('problem-source')!; - const descriptionTitle = this.getElementById('issue-description-label')!; - const descriptionSubtitle = this.getElementById('issue-description-subtitle')!; - const extensionSelector = this.getElementById('extension-selection')!; - - // Hide all by default - hide(blockContainer); - hide(systemBlock); - hide(processBlock); - hide(workspaceBlock); - hide(extensionsBlock); - hide(experimentsBlock); - hide(problemSource); - hide(extensionSelector); - - if (issueType === IssueType.Bug) { - show(problemSource); - - if (!fileOnMarketplace) { - show(blockContainer); - show(systemBlock); - show(experimentsBlock); - } - - if (fileOnExtension) { - show(extensionSelector); - } else if (!fileOnMarketplace) { - show(extensionsBlock); - } - reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*')); - reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); - } else if (issueType === IssueType.PerformanceIssue) { - show(problemSource); - - if (!fileOnMarketplace) { - show(blockContainer); - show(systemBlock); - show(processBlock); - show(workspaceBlock); - show(experimentsBlock); - } - - if (fileOnExtension) { - show(extensionSelector); - } else if (!fileOnMarketplace) { - show(extensionsBlock); - } - - reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*')); - reset(descriptionSubtitle, localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); - } else if (issueType === IssueType.FeatureRequest) { - reset(descriptionTitle, localize('description', "Description"), $('span.required-input', undefined, '*')); - reset(descriptionSubtitle, localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); - show(problemSource); - - if (fileOnExtension) { - show(extensionSelector); - } - } - } - - private validateInput(inputId: string): boolean { - const inputElement = (this.getElementById(inputId)); - const inputValidationMessage = this.getElementById(`${inputId}-empty-error`); - if (!inputElement.value) { - inputElement.classList.add('invalid-input'); - inputValidationMessage?.classList.remove('hidden'); - return false; - } else { - inputElement.classList.remove('invalid-input'); - inputValidationMessage?.classList.add('hidden'); - return true; - } - } - - private validateInputs(): boolean { - let isValid = true; - ['issue-title', 'description', 'issue-source'].forEach(elementId => { - isValid = this.validateInput(elementId) && isValid; - }); - - if (this.issueReporterModel.fileOnExtension()) { - isValid = this.validateInput('extension-selector') && isValid; - } - - 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 - // to detect further changes - const invalidInput = document.getElementsByClassName('invalid-input'); - if (invalidInput.length) { - (invalidInput[0]).focus(); - } - - this.addEventListener('issue-title', 'input', _ => { - this.validateInput('issue-title'); - }); - - this.addEventListener('description', 'input', _ => { - this.validateInput('description'); - }); - - this.addEventListener('issue-source', 'change', _ => { - this.validateInput('issue-source'); - }); - - if (this.issueReporterModel.fileOnExtension()) { - this.addEventListener('extension-selector', 'change', _ => { - this.validateInput('extension-selector'); - }); - } - - return false; - } - - this.hasBeenSubmitted = true; - - const issueTitle = (this.getElementById('issue-title')).value; - const issueBody = this.issueReporterModel.serialize(); - - const issueUrl = this.getIssueUrl(); - const gitHubDetails = this.parseGitHubUrl(issueUrl); - if (this.configuration.data.githubAccessToken && gitHubDetails) { - return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); - } - - const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value, issueUrl); - let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; - - if (url.length > MAX_URL_LENGTH) { - try { - url = await this.writeToClipboard(baseUrl, issueBody); - } catch (_) { - return false; - } - } - - ipcRenderer.send('vscode:openExternal', url); - return true; - } - - private async writeToClipboard(baseUrl: string, issueBody: string): Promise { - return new Promise((resolve, reject) => { - ipcRenderer.once('vscode:issueReporterClipboardResponse', async (event: unknown, shouldWrite: boolean) => { - if (shouldWrite) { - await this.nativeHostService.writeClipboardText(issueBody); - resolve(baseUrl + `&body=${encodeURIComponent(localize('pasteData', "We have written the needed data into your clipboard because it was too large to send. Please paste."))}`); - } else { - reject(); - } - }); - - ipcRenderer.send('vscode:issueReporterClipboard'); - }); - } - - private getIssueUrl(): string { - return this.issueReporterModel.fileOnExtension() - ? this.getExtensionGitHubUrl() - : this.issueReporterModel.getData().fileOnMarketplace - ? this.configuration.product.reportMarketplaceIssueUrl! - : this.configuration.product.reportIssueUrl!; - } - - 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(); - const extensionUrl = this.getExtensionRepositoryUrl(); - // If given, try to match the extension's bug url - if (bugsUrl && bugsUrl.match(/^https?:\/\/github\.com\/(.*)/)) { - repositoryUrl = normalizeGitHubUrl(bugsUrl); - } else if (extensionUrl && extensionUrl.match(/^https?:\/\/github\.com\/(.*)/)) { - repositoryUrl = normalizeGitHubUrl(extensionUrl); - } - - return repositoryUrl; - } - - private getIssueUrlWithTitle(issueTitle: string, repositoryUrl: string): string { - if (this.issueReporterModel.fileOnExtension()) { - repositoryUrl = repositoryUrl + '/issues/new'; - } - - const queryStringPrefix = repositoryUrl.indexOf('?') === -1 ? '?' : '&'; - return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`; - } - - private updateSystemInfo(state: IssueReporterModelData) { - const target = document.querySelector('.block-system .block-info'); - - if (target) { - const systemInfo = state.systemInfo!; - const renderedDataTable = $('table', undefined, - $('tr', undefined, - $('td', undefined, 'CPUs'), - $('td', undefined, systemInfo.cpus || ''), - ), - $('tr', undefined, - $('td', undefined, 'GPU Status' as string), - $('td', undefined, Object.keys(systemInfo.gpuStatus).map(key => `${key}: ${systemInfo.gpuStatus[key]}`).join('\n')), - ), - $('tr', undefined, - $('td', undefined, 'Load (avg)' as string), - $('td', undefined, systemInfo.load || ''), - ), - $('tr', undefined, - $('td', undefined, 'Memory (System)' as string), - $('td', undefined, systemInfo.memory), - ), - $('tr', undefined, - $('td', undefined, 'Process Argv' as string), - $('td', undefined, systemInfo.processArgs), - ), - $('tr', undefined, - $('td', undefined, 'Screen Reader' as string), - $('td', undefined, systemInfo.screenReader), - ), - $('tr', undefined, - $('td', undefined, 'VM'), - $('td', undefined, systemInfo.vmHint), - ), - ); - reset(target, renderedDataTable); - - systemInfo.remoteData.forEach(remote => { - target.appendChild($('hr')); - if (isRemoteDiagnosticError(remote)) { - const remoteDataTable = $('table', undefined, - $('tr', undefined, - $('td', undefined, 'Remote'), - $('td', undefined, remote.hostName) - ), - $('tr', undefined, - $('td', undefined, ''), - $('td', undefined, remote.errorMessage) - ) - ); - target.appendChild(remoteDataTable); - } else { - const remoteDataTable = $('table', undefined, - $('tr', undefined, - $('td', undefined, 'Remote'), - $('td', undefined, remote.hostName) - ), - $('tr', undefined, - $('td', undefined, 'OS'), - $('td', undefined, remote.machineInfo.os) - ), - $('tr', undefined, - $('td', undefined, 'CPUs'), - $('td', undefined, remote.machineInfo.cpus || '') - ), - $('tr', undefined, - $('td', undefined, 'Memory (System)' as string), - $('td', undefined, remote.machineInfo.memory) - ), - $('tr', undefined, - $('td', undefined, 'VM'), - $('td', undefined, remote.machineInfo.vmHint) - ), - ); - target.appendChild(remoteDataTable); - } - }); - } - } - - private updateExtensionSelector(extensions: IssueReporterExtensionData[]): void { - interface IOption { - name: string; - id: string; - } - - const extensionOptions: IOption[] = extensions.map(extension => { - return { - name: extension.displayName || extension.name || '', - id: extension.id - }; - }); - - // Sort extensions by name - extensionOptions.sort((a, b) => { - const aName = a.name.toLowerCase(); - const bName = b.name.toLowerCase(); - if (aName > bName) { - return 1; - } - - if (aName < bName) { - return -1; - } - - return 0; - }); - - const makeOption = (extension: IOption, selectedExtension?: IssueReporterExtensionData): HTMLOptionElement => { - const selected = selectedExtension && extension.id === selectedExtension.id; - return $('option', { - 'value': extension.id, - 'selected': selected || '' - }, extension.name); - }; - - const extensionsSelector = this.getElementById('extension-selector'); - if (extensionsSelector) { - const { selectedExtension } = this.issueReporterModel.getData(); - reset(extensionsSelector, $('option'), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); - - this.addEventListener('extension-selector', 'change', (e: Event) => { - const selectedExtensionId = (e.target).value; - const extensions = this.issueReporterModel.getData().allExtensions; - const matches = extensions.filter(extension => extension.id === selectedExtensionId); - if (matches.length) { - this.issueReporterModel.update({ selectedExtension: matches[0] }); - this.validateSelectedExtension(); - - const title = (this.getElementById('issue-title')).value; - this.searchExtensionIssues(title); - } else { - this.issueReporterModel.update({ selectedExtension: undefined }); - this.clearSearchResults(); - this.validateSelectedExtension(); - } - }); - } - - this.addEventListener('problem-source', 'change', (_) => { - this.validateSelectedExtension(); - }); - } - - private validateSelectedExtension(): void { - const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!; - const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!; - hide(extensionValidationMessage); - hide(extensionValidationNoUrlsMessage); - - if (!this.issueReporterModel.getData().selectedExtension) { - this.previewButton.enabled = true; - return; - } - - const hasValidGitHubUrl = this.getExtensionGitHubUrl(); - if (hasValidGitHubUrl) { - this.previewButton.enabled = true; - } else { - this.setExtensionValidationMessage(); - this.previewButton.enabled = false; - } - } - - private setExtensionValidationMessage(): void { - const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!; - const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!; - const bugsUrl = this.getExtensionBugsUrl(); - if (bugsUrl) { - show(extensionValidationMessage); - const link = this.getElementById('extensionBugsLink')!; - link.textContent = bugsUrl; - return; - } - - const extensionUrl = this.getExtensionRepositoryUrl(); - if (extensionUrl) { - show(extensionValidationMessage); - const link = this.getElementById('extensionBugsLink'); - link!.textContent = extensionUrl; - return; - } - - show(extensionValidationNoUrlsMessage); - } - - private updateProcessInfo(state: IssueReporterModelData) { - const target = document.querySelector('.block-process .block-info') as HTMLElement; - if (target) { - reset(target, $('code', undefined, state.processInfo)); - } - } - - private updateWorkspaceInfo(state: IssueReporterModelData) { - document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo; - } - - private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void { - const target = document.querySelector('.block-extensions .block-info'); - if (target) { - if (this.configuration.disableExtensions) { - reset(target, localize('disabledExtensions', "Extensions are disabled")); - return; - } - - const themeExclusionStr = numThemeExtensions ? `\n(${numThemeExtensions} theme extensions excluded)` : ''; - extensions = extensions || []; - - if (!extensions.length) { - target.innerText = 'Extensions: none' + themeExclusionStr; - return; - } - - reset(target, this.getExtensionTableHtml(extensions), document.createTextNode(themeExclusionStr)); - } - } - - private updateRestrictedMode(restrictedMode: boolean) { - this.issueReporterModel.update({ restrictedMode }); - } - - // {{SQL CARBON EDIT}} Add preview features flag - private updatePreviewFeaturesEnabled(previewFeaturesEnabled: boolean) { - this.issueReporterModel.update({ previewFeaturesEnabled }); - } - - private updateUnsupportedMode(isUnsupported: boolean) { - this.issueReporterModel.update({ isUnsupported }); - } - - 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, - $('th', undefined, 'Extension'), - $('th', undefined, 'Author (truncated)' as string), - $('th', undefined, 'Version'), - ), - ...extensions.map(extension => $('tr', undefined, - $('td', undefined, extension.name), - $('td', undefined, extension.publisher?.substr(0, 3) ?? 'N/A'), - $('td', undefined, extension.version), - )) - ); - } - - private openLink(event: MouseEvent): void { - event.preventDefault(); - event.stopPropagation(); - // Exclude right click - if (event.which < 3) { - windowOpenNoOpener((event.target).href); - } - } - - private getElementById(elementId: string): T | undefined { - const element = document.getElementById(elementId) as T | undefined; - if (element) { - return element; - } else { - return undefined; - } - } - - private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void { - const element = this.getElementById(elementId); - element?.addEventListener(eventType, handler); - } + return new InstantiationService(services, true); } -// helper functions - -function hide(el: Element | undefined | null) { - el?.classList.add('hidden'); -} -function show(el: Element | undefined | null) { - el?.classList.remove('hidden'); -} +registerMainProcessRemoteService(IIssueMainService, 'issue'); diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 1c7a2434cc..3fa5d04b35 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -12,8 +12,8 @@ export interface IssueReporterData { versionInfo?: any; systemInfo?: SystemInfo; - processInfo?: any; - workspaceInfo?: any; + processInfo?: string; + workspaceInfo?: string; includeSystemInfo: boolean; includeWorkspaceInfo: boolean; @@ -189,7 +189,7 @@ ${this.getInfos()} |Item|Value| |---|---| -|Remote|${remote.hostName}| +|Remote|${remote.latency ? `${remote.hostName} (latency: ${remote.latency.current.toFixed(2)}ms last, ${remote.latency.average.toFixed(2)}ms average)` : remote.hostName}| |OS|${remote.machineInfo.os}| |CPUs|${remote.machineInfo.cpus}| |Memory (System)|${remote.machineInfo.memory}| diff --git a/src/vs/code/electron-sandbox/issue/issueReporterPage.ts b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts index ab95ca3cf4..2fe955e698 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterPage.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterPage.ts @@ -11,11 +11,23 @@ const sendProcessInfoLabel = escape(localize('sendProcessInfo', "Include my curr const sendWorkspaceInfoLabel = escape(localize('sendWorkspaceInfo', "Include my workspace metadata")); const sendExtensionsLabel = escape(localize('sendExtensions', "Include my enabled extensions")); const sendExperimentsLabel = escape(localize('sendExperiments', "Include A/B experiment info")); +const reviewGuidanceLabel = localize( // intentionally not escaped because of its embedded tags + { + key: 'reviewGuidanceLabel', + comment: [ + '{Locked=""}', + '{Locked=""}' + ] + }, + 'Before you report an issue here please review the guidance we provide.' +); export default (): string => `
+
${reviewGuidanceLabel}
+
@@ -25,13 +37,13 @@ export default (): string => `
- +
@@ -40,14 +52,14 @@ export default (): string => ` + .replace('{0}', () => ``)}
-
+
diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts new file mode 100644 index 0000000000..683dbe311d --- /dev/null +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -0,0 +1,1224 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; +import { $, reset, windowOpenNoOpener } from 'vs/base/browser/dom'; +import { Button, unthemedButtonStyles } from 'vs/base/browser/ui/button/button'; +import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { Delayer } from 'vs/base/common/async'; +import { Codicon } from 'vs/base/common/codicons'; +import { groupBy } from 'vs/base/common/collections'; +import { debounce } from 'vs/base/common/decorators'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { isLinuxSnap, isMacintosh } from 'vs/base/common/platform'; +import { escape } from 'vs/base/common/strings'; +import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel'; +import { isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; +import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IssueReporterWindowConfiguration, IssueType } from 'vs/platform/issue/common/issue'; +import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil'; +import { INativeHostService } from 'vs/platform/native/common/native'; +import { applyZoom, zoomIn, zoomOut } from 'vs/platform/window/electron-sandbox/window'; +import { CancellationError } from 'vs/base/common/errors'; + +// GitHub has let us know that we could up our limit here to 8k. We chose 7500 to play it safe. +// ref https://github.com/microsoft/vscode/issues/159191 +const MAX_URL_LENGTH = 7500; + +interface SearchResult { + html_url: string; + title: string; + state?: string; +} + +enum IssueSource { + VSCode = 'vscode', + Extension = 'extension', + Marketplace = 'marketplace' +} + +export class IssueReporter extends Disposable { + private readonly issueReporterModel: IssueReporterModel; + private numberOfSearchResultsDisplayed = 0; + private receivedSystemInfo = false; + private receivedPerformanceInfo = false; + private shouldQueueSearch = false; + private hasBeenSubmitted = false; + private delayedSubmit = new Delayer(300); + + private readonly previewButton!: Button; + + constructor( + private readonly configuration: IssueReporterWindowConfiguration, + @INativeHostService private readonly nativeHostService: INativeHostService, + @IIssueMainService private readonly issueMainService: IIssueMainService + ) { + super(); + const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id.toLocaleLowerCase() === configuration.data.extensionId?.toLocaleLowerCase()) : undefined; + this.issueReporterModel = new IssueReporterModel({ + ...configuration.data, + issueType: configuration.data.issueType || IssueType.Bug, + versionInfo: { + vscodeVersion: `${configuration.product.nameShort} ${!!configuration.product.darwinUniversalAssetId ? `${configuration.product.version} (Universal)` : configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`, + os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isLinuxSnap ? ' snap' : ''}` + }, + extensionsDisabled: !!configuration.disableExtensions, + fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined, + selectedExtension: targetExtension + }); + + const issueReporterElement = this.getElementById('issue-reporter'); + if (issueReporterElement) { + this.previewButton = new Button(issueReporterElement, unthemedButtonStyles); + this.updatePreviewButtonState(); + } + + const issueTitle = configuration.data.issueTitle; + if (issueTitle) { + const issueTitleElement = this.getElementById('issue-title'); + if (issueTitleElement) { + issueTitleElement.value = issueTitle; + } + } + + const issueBody = configuration.data.issueBody; + if (issueBody) { + const description = this.getElementById('description'); + if (description) { + description.value = issueBody; + this.issueReporterModel.update({ issueDescription: issueBody }); + } + } + + this.issueMainService.$getSystemInfo().then(info => { + this.issueReporterModel.update({ systemInfo: info }); + this.receivedSystemInfo = true; + + this.updateSystemInfo(this.issueReporterModel.getData()); + this.updatePreviewButtonState(); + }); + if (configuration.data.issueType === IssueType.PerformanceIssue) { + this.issueMainService.$getPerformanceInfo().then(info => { + this.updatePerformanceInfo(info as Partial); + }); + } + + if (window.document.documentElement.lang !== 'en') { + show(this.getElementById('english')); + } + + this.setUpTypes(); + this.setEventHandlers(); + applyZoom(configuration.data.zoomLevel); + this.applyStyles(configuration.data.styles); + this.handleExtensionData(configuration.data.enabledExtensions); + this.updateExperimentsInfo(configuration.data.experiments); + this.updateRestrictedMode(configuration.data.restrictedMode); + this.updateUnsupportedMode(configuration.data.isUnsupported); + } + + render(): void { + this.renderBlocks(); + } + + setInitialFocus() { + const { fileOnExtension } = this.issueReporterModel.getData(); + if (fileOnExtension) { + const issueTitle = document.getElementById('issue-title'); + issueTitle?.focus(); + } else { + const issueType = document.getElementById('issue-type'); + issueType?.focus(); + } + } + + private applyStyles(styles: IssueReporterStyles) { + const styleTag = document.createElement('style'); + const content: string[] = []; + + if (styles.inputBackground) { + content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { background-color: ${styles.inputBackground}; }`); + } + + if (styles.inputBorder) { + content.push(`input[type="text"], textarea, select { border: 1px solid ${styles.inputBorder}; }`); + } else { + content.push(`input[type="text"], textarea, select { border: 1px solid transparent; }`); + } + + if (styles.inputForeground) { + content.push(`input[type="text"], textarea, select, .issues-container > .issue > .issue-state, .block-info { color: ${styles.inputForeground}; }`); + } + + if (styles.inputErrorBorder) { + content.push(`.invalid-input, .invalid-input:focus, .validation-error { border: 1px solid ${styles.inputErrorBorder} !important; }`); + content.push(`.required-input { color: ${styles.inputErrorBorder}; }`); + } + + if (styles.inputErrorBackground) { + content.push(`.validation-error { background: ${styles.inputErrorBackground}; }`); + } + + if (styles.inputErrorForeground) { + content.push(`.validation-error { color: ${styles.inputErrorForeground}; }`); + } + + if (styles.inputActiveBorder) { + content.push(`input[type='text']:focus, textarea:focus, select:focus, summary:focus, button:focus, a:focus, .workbenchCommand:focus { border: 1px solid ${styles.inputActiveBorder}; outline-style: none; }`); + } + + if (styles.textLinkColor) { + content.push(`a, .workbenchCommand { color: ${styles.textLinkColor}; }`); + } + + if (styles.textLinkColor) { + content.push(`a { color: ${styles.textLinkColor}; }`); + } + + if (styles.textLinkActiveForeground) { + content.push(`a:hover, .workbenchCommand:hover { color: ${styles.textLinkActiveForeground}; }`); + } + + if (styles.sliderBackgroundColor) { + content.push(`::-webkit-scrollbar-thumb { background-color: ${styles.sliderBackgroundColor}; }`); + } + + if (styles.sliderActiveColor) { + content.push(`::-webkit-scrollbar-thumb:active { background-color: ${styles.sliderActiveColor}; }`); + } + + if (styles.sliderHoverColor) { + content.push(`::--webkit-scrollbar-thumb:hover { background-color: ${styles.sliderHoverColor}; }`); + } + + if (styles.buttonBackground) { + content.push(`.monaco-text-button { background-color: ${styles.buttonBackground} !important; }`); + } + + if (styles.buttonForeground) { + content.push(`.monaco-text-button { color: ${styles.buttonForeground} !important; }`); + } + + if (styles.buttonHoverBackground) { + content.push(`.monaco-text-button:not(.disabled):hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`); + } + + styleTag.textContent = content.join('\n'); + document.head.appendChild(styleTag); + document.body.style.color = styles.color || ''; + } + + private handleExtensionData(extensions: IssueReporterExtensionData[]) { + const installedExtensions = extensions.filter(x => !x.isBuiltin); + const { nonThemes, themes } = groupBy(installedExtensions, ext => { + return ext.isTheme ? 'themes' : 'nonThemes'; + }); + + const numberOfThemeExtesions = themes && themes.length; + this.issueReporterModel.update({ numberOfThemeExtesions, enabledNonThemeExtesions: nonThemes, allExtensions: installedExtensions }); + this.updateExtensionTable(nonThemes, numberOfThemeExtesions); + if (this.configuration.disableExtensions || installedExtensions.length === 0) { + (this.getElementById('disableExtensions')).disabled = true; + } + + this.updateExtensionSelector(installedExtensions); + } + + private async updateIssueReporterUri(extension: IssueReporterExtensionData): Promise { + try { + const uri = await this.issueMainService.$getIssueReporterUri(extension.id); + extension.bugsUrl = uri.toString(true); + } catch (e) { + extension.hasIssueUriRequestHandler = false; + // The issue handler failed so fall back to old issue reporter experience. + this.renderBlocks(); + } + } + + private setEventHandlers(): void { + this.addEventListener('issue-type', 'change', (event: Event) => { + const issueType = parseInt((event.target).value); + this.issueReporterModel.update({ issueType: issueType }); + if (issueType === IssueType.PerformanceIssue && !this.receivedPerformanceInfo) { + this.issueMainService.$getPerformanceInfo().then(info => { + this.updatePerformanceInfo(info as Partial); + }); + } + this.updatePreviewButtonState(); + this.setSourceOptions(); + this.render(); + }); + + (['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] }); + }); + }); + + const showInfoElements = document.getElementsByClassName('showInfo'); + for (let i = 0; i < showInfoElements.length; i++) { + const showInfo = showInfoElements.item(i)!; + (showInfo as HTMLAnchorElement).addEventListener('click', (e: MouseEvent) => { + e.preventDefault(); + const label = (e.target); + if (label) { + const containingElement = label.parentElement && label.parentElement.parentElement; + const info = containingElement && containingElement.lastElementChild; + if (info && info.classList.contains('hidden')) { + show(info); + label.textContent = localize('hide', "hide"); + } else { + hide(info); + label.textContent = localize('show', "show"); + } + } + }); + } + + this.addEventListener('issue-source', 'change', (e: Event) => { + const value = (e.target).value; + const problemSourceHelpText = this.getElementById('problem-source-help-text')!; + if (value === '') { + this.issueReporterModel.update({ fileOnExtension: undefined }); + show(problemSourceHelpText); + this.clearSearchResults(); + this.render(); + return; + } else { + hide(problemSourceHelpText); + } + + let fileOnExtension, fileOnMarketplace = false; + if (value === IssueSource.Extension) { + fileOnExtension = true; + } else if (value === IssueSource.Marketplace) { + fileOnMarketplace = true; + } + + this.issueReporterModel.update({ fileOnExtension, fileOnMarketplace }); + this.render(); + + const title = (this.getElementById('issue-title')).value; + this.searchIssues(title, fileOnExtension, fileOnMarketplace); + }); + + this.addEventListener('description', 'input', (e: Event) => { + const issueDescription = (e.target).value; + this.issueReporterModel.update({ issueDescription }); + + // Only search for extension issues on title change + if (this.issueReporterModel.fileOnExtension() === false) { + const title = (this.getElementById('issue-title')).value; + this.searchVSCodeIssues(title, issueDescription); + } + }); + + this.addEventListener('issue-title', 'input', (e: Event) => { + const title = (e.target).value; + const lengthValidationMessage = this.getElementById('issue-title-length-validation-error'); + const issueUrl = this.getIssueUrl(); + if (title && this.getIssueUrlWithTitle(title, issueUrl).length > MAX_URL_LENGTH) { + show(lengthValidationMessage); + } else { + hide(lengthValidationMessage); + } + const issueSource = this.getElementById('issue-source'); + if (!issueSource || issueSource.value === '') { + return; + } + + const { fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData(); + this.searchIssues(title, fileOnExtension, fileOnMarketplace); + }); + + this.previewButton.onDidClick(async () => { + this.delayedSubmit.trigger(async () => { + this.createIssue(); + }); + }); + + this.addEventListener('disableExtensions', 'click', () => { + this.issueMainService.$reloadWithExtensionsDisabled(); + }); + + this.addEventListener('extensionBugsLink', 'click', (e: Event) => { + const url = (e.target).innerText; + windowOpenNoOpener(url); + }); + + this.addEventListener('disableExtensions', 'keydown', (e: Event) => { + e.stopPropagation(); + if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) { + this.issueMainService.$reloadWithExtensionsDisabled(); + } + }); + + document.onkeydown = async (e: KeyboardEvent) => { + const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; + // Cmd/Ctrl+Enter previews issue and closes window + if (cmdOrCtrlKey && e.keyCode === 13) { + this.delayedSubmit.trigger(async () => { + if (await this.createIssue()) { + this.close(); + } + }); + } + + // Cmd/Ctrl + w closes issue window + if (cmdOrCtrlKey && e.keyCode === 87) { + e.stopPropagation(); + e.preventDefault(); + + const issueTitle = (this.getElementById('issue-title'))!.value; + const { issueDescription } = this.issueReporterModel.getData(); + if (!this.hasBeenSubmitted && (issueTitle || issueDescription)) { + // fire and forget + this.issueMainService.$showConfirmCloseDialog(); + } else { + this.close(); + } + } + + // Cmd/Ctrl + zooms in + if (cmdOrCtrlKey && e.keyCode === 187) { + zoomIn(); + } + + // Cmd/Ctrl - zooms out + if (cmdOrCtrlKey && e.keyCode === 189) { + zoomOut(); + } + + // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac + // Manually perform the selection + if (isMacintosh) { + if (cmdOrCtrlKey && e.keyCode === 65 && e.target) { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + (e.target).select(); + } + } + } + }; + } + + private updatePerformanceInfo(info: Partial) { + this.issueReporterModel.update(info); + this.receivedPerformanceInfo = true; + + const state = this.issueReporterModel.getData(); + this.updateProcessInfo(state); + this.updateWorkspaceInfo(state); + this.updatePreviewButtonState(); + } + + private updatePreviewButtonState() { + if (this.isPreviewEnabled()) { + 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; + this.previewButton.label = localize('loadingData', "Loading data..."); + } + } + + private isPreviewEnabled() { + const issueType = this.issueReporterModel.getData().issueType; + if (issueType === IssueType.Bug && this.receivedSystemInfo) { + return true; + } + + if (issueType === IssueType.PerformanceIssue && this.receivedSystemInfo && this.receivedPerformanceInfo) { + return true; + } + + if (issueType === IssueType.FeatureRequest) { + return true; + } + + return false; + } + + private getExtensionRepositoryUrl(): string | undefined { + const selectedExtension = this.issueReporterModel.getData().selectedExtension; + return selectedExtension && selectedExtension.repositoryUrl; + } + + private getExtensionBugsUrl(): string | undefined { + const selectedExtension = this.issueReporterModel.getData().selectedExtension; + return selectedExtension && selectedExtension.bugsUrl; + } + + private searchVSCodeIssues(title: string, issueDescription?: string): void { + if (title) { + this.searchDuplicates(title, issueDescription); + } else { + this.clearSearchResults(); + } + } + + private searchIssues(title: string, fileOnExtension: boolean | undefined, fileOnMarketplace: boolean | undefined): void { + if (fileOnExtension) { + return this.searchExtensionIssues(title); + } + + if (fileOnMarketplace) { + return this.searchMarketplaceIssues(title); + } + + const description = this.issueReporterModel.getData().issueDescription; + this.searchVSCodeIssues(title, description); + } + + private searchExtensionIssues(title: string): void { + const url = this.getExtensionGitHubUrl(); + if (title) { + const matches = /^https?:\/\/github\.com\/(.*)/.exec(url); + if (matches && matches.length) { + const repo = matches[1]; + return this.searchGitHub(repo, title); + } + + // If the extension has no repository, display empty search results + if (this.issueReporterModel.getData().selectedExtension) { + this.clearSearchResults(); + return this.displaySearchResults([]); + + } + } + + this.clearSearchResults(); + } + + private searchMarketplaceIssues(title: string): void { + if (title) { + const gitHubInfo = this.parseGitHubUrl(this.configuration.product.reportMarketplaceIssueUrl!); + if (gitHubInfo) { + return this.searchGitHub(`${gitHubInfo.owner}/${gitHubInfo.repositoryName}`, title); + } + } + } + + private async close(): Promise { + await this.issueMainService.$closeReporter(); + } + + private clearSearchResults(): void { + const similarIssues = this.getElementById('similar-issues')!; + similarIssues.innerText = ''; + this.numberOfSearchResultsDisplayed = 0; + } + + @debounce(300) + private searchGitHub(repo: string, title: string): void { + const query = `is:issue+repo:${repo}+${title}`; + const similarIssues = this.getElementById('similar-issues')!; + + window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => { + response.json().then(result => { + similarIssues.innerText = ''; + if (result && result.items) { + this.displaySearchResults(result.items); + } else { + // If the items property isn't present, the rate limit has been hit + const message = $('div.list-title'); + message.textContent = localize('rateLimited', "GitHub query limit exceeded. Please wait."); + similarIssues.appendChild(message); + + const resetTime = response.headers.get('X-RateLimit-Reset'); + const timeToWait = resetTime ? parseInt(resetTime) - Math.floor(Date.now() / 1000) : 1; + if (this.shouldQueueSearch) { + this.shouldQueueSearch = false; + setTimeout(() => { + this.searchGitHub(repo, title); + this.shouldQueueSearch = true; + }, timeToWait * 1000); + } + } + }).catch(_ => { + // Ignore + }); + }).catch(_ => { + // Ignore + }); + } + + @debounce(300) + private searchDuplicates(title: string, body?: string): void { + const url = 'https://vscode-probot.westus.cloudapp.azure.com:7890/duplicate_candidates'; + const init = { + method: 'POST', + body: JSON.stringify({ + title, + body + }), + headers: new Headers({ + 'Content-Type': 'application/json' + }) + }; + + window.fetch(url, init).then((response) => { + response.json().then(result => { + this.clearSearchResults(); + + if (result && result.candidates) { + this.displaySearchResults(result.candidates); + } else { + throw new Error('Unexpected response, no candidates property'); + } + }).catch(_ => { + // Ignore + }); + }).catch(_ => { + // Ignore + }); + } + + private displaySearchResults(results: SearchResult[]) { + const similarIssues = this.getElementById('similar-issues')!; + if (results.length) { + const issues = $('div.issues-container'); + const issuesText = $('div.list-title'); + issuesText.textContent = localize('similarIssues', "Similar issues"); + + this.numberOfSearchResultsDisplayed = results.length < 5 ? results.length : 5; + for (let i = 0; i < this.numberOfSearchResultsDisplayed; i++) { + const issue = results[i]; + const link = $('a.issue-link', { href: issue.html_url }); + link.textContent = issue.title; + link.title = issue.title; + link.addEventListener('click', (e) => this.openLink(e)); + link.addEventListener('auxclick', (e) => this.openLink(e)); + + let issueState: HTMLElement; + let item: HTMLElement; + if (issue.state) { + issueState = $('span.issue-state'); + + const issueIcon = $('span.issue-icon'); + 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"); + + issueState.title = issue.state === 'open' ? localize('open', "Open") : localize('closed', "Closed"); + issueState.appendChild(issueIcon); + issueState.appendChild(issueStateLabel); + + item = $('div.issue', undefined, issueState, link); + } else { + item = $('div.issue', undefined, link); + } + + issues.appendChild(item); + } + + similarIssues.appendChild(issuesText); + similarIssues.appendChild(issues); + } else { + const message = $('div.list-title'); + message.textContent = localize('noSimilarIssues', "No similar issues found"); + similarIssues.appendChild(message); + } + } + + private setUpTypes(): void { + const makeOption = (issueType: IssueType, description: string) => $('option', { 'value': issueType.valueOf() }, escape(description)); + + const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement; + const { issueType } = this.issueReporterModel.getData(); + reset(typeSelect, + makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), + makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), + makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")) + ); + + typeSelect.value = issueType.toString(); + + this.setSourceOptions(); + } + + private makeOption(value: string, description: string, disabled: boolean): HTMLOptionElement { + const option: HTMLOptionElement = document.createElement('option'); + option.disabled = disabled; + option.value = value; + option.textContent = description; + + return option; + } + + private setSourceOptions(): void { + const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement; + const { issueType, fileOnExtension, selectedExtension } = this.issueReporterModel.getData(); + let selected = sourceSelect.selectedIndex; + if (selected === -1) { + if (fileOnExtension !== undefined) { + selected = fileOnExtension ? 2 : 1; + } else if (selectedExtension?.isBuiltin) { + selected = 1; + } + } + + sourceSelect.innerText = ''; + sourceSelect.append(this.makeOption('', localize('selectSource', "Select source"), true)); + sourceSelect.append(this.makeOption('vscode', localize('vscode', "Visual Studio Code"), false)); + sourceSelect.append(this.makeOption('extension', localize('extension', "An extension"), false)); + if (this.configuration.product.reportMarketplaceIssueUrl) { + sourceSelect.append(this.makeOption('marketplace', localize('marketplace', "Extensions marketplace"), false)); + } + + if (issueType !== IssueType.FeatureRequest) { + sourceSelect.append(this.makeOption('', localize('unknown', "Don't know"), false)); + } + + if (selected !== -1 && selected < sourceSelect.options.length) { + sourceSelect.selectedIndex = selected; + } else { + sourceSelect.selectedIndex = 0; + hide(this.getElementById('problem-source-help-text')); + } + } + + private renderBlocks(): void { + // Depending on Issue Type, we render different blocks and text + const { issueType, fileOnExtension, fileOnMarketplace, selectedExtension } = this.issueReporterModel.getData(); + const blockContainer = this.getElementById('block-container'); + const systemBlock = document.querySelector('.block-system'); + const processBlock = document.querySelector('.block-process'); + const workspaceBlock = document.querySelector('.block-workspace'); + const extensionsBlock = document.querySelector('.block-extensions'); + const experimentsBlock = document.querySelector('.block-experiments'); + + const problemSource = this.getElementById('problem-source')!; + const descriptionTitle = this.getElementById('issue-description-label')!; + const descriptionSubtitle = this.getElementById('issue-description-subtitle')!; + const extensionSelector = this.getElementById('extension-selection')!; + + const titleTextArea = this.getElementById('issue-title-container')!; + const descriptionTextArea = this.getElementById('description')!; + + // Hide all by default + hide(blockContainer); + hide(systemBlock); + hide(processBlock); + hide(workspaceBlock); + hide(extensionsBlock); + hide(experimentsBlock); + hide(problemSource); + hide(extensionSelector); + + show(problemSource); + show(titleTextArea); + show(descriptionTextArea); + + if (fileOnExtension) { + show(extensionSelector); + } + + if (fileOnExtension && selectedExtension?.hasIssueUriRequestHandler) { + hide(titleTextArea); + hide(descriptionTextArea); + reset(descriptionTitle, localize('handlesIssuesElsewhere', "This extension handles issues outside of VS Code")); + reset(descriptionSubtitle, localize('elsewhereDescription', "The '{0}' extension prefers to use an external issue reporter. To be taken to that issue reporting experience, click the button below.", selectedExtension.displayName)); + this.previewButton.label = localize('openIssueReporter', "Open External Issue Reporter"); + return; + } + + if (issueType === IssueType.Bug) { + if (!fileOnMarketplace) { + show(blockContainer); + show(systemBlock); + show(experimentsBlock); + if (!fileOnExtension) { + show(extensionsBlock); + } + } + + reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce") + ' ', $('span.required-input', undefined, '*')); + reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); + } else if (issueType === IssueType.PerformanceIssue) { + if (!fileOnMarketplace) { + show(blockContainer); + show(systemBlock); + show(processBlock); + show(workspaceBlock); + show(experimentsBlock); + } + + if (fileOnExtension) { + show(extensionSelector); + } else if (!fileOnMarketplace) { + show(extensionsBlock); + } + + reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce") + ' ', $('span.required-input', undefined, '*')); + reset(descriptionSubtitle, localize('performanceIssueDesciption', "When did this performance issue happen? Does it occur on startup or after a specific series of actions? We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); + } else if (issueType === IssueType.FeatureRequest) { + reset(descriptionTitle, localize('description', "Description") + ' ', $('span.required-input', undefined, '*')); + reset(descriptionSubtitle, localize('featureRequestDescription', "Please describe the feature you would like to see. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); + } + } + + private validateInput(inputId: string): boolean { + const inputElement = (this.getElementById(inputId)); + const inputValidationMessage = this.getElementById(`${inputId}-empty-error`); + if (!inputElement.value) { + inputElement.classList.add('invalid-input'); + inputValidationMessage?.classList.remove('hidden'); + return false; + } else { + inputElement.classList.remove('invalid-input'); + inputValidationMessage?.classList.add('hidden'); + return true; + } + } + + private validateInputs(): boolean { + let isValid = true; + ['issue-title', 'description', 'issue-source'].forEach(elementId => { + isValid = this.validateInput(elementId) && isValid; + }); + + if (this.issueReporterModel.fileOnExtension()) { + isValid = this.validateInput('extension-selector') && isValid; + } + + 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}` + }) + }; + + const response = await window.fetch(url, init); + if (!response.ok) { + return false; + } + const result = await response.json(); + await this.nativeHostService.openExternal(result.html_url); + this.close(); + return true; + } + + private async createIssue(): Promise { + // Short circuit if the extension provides a custom issue handler + if (this.issueReporterModel.getData().selectedExtension?.hasIssueUriRequestHandler) { + const url = this.getExtensionBugsUrl(); + if (url) { + this.hasBeenSubmitted = true; + await this.nativeHostService.openExternal(url); + return true; + } + } + + if (!this.validateInputs()) { + // If inputs are invalid, set focus to the first one and add listeners on them + // to detect further changes + const invalidInput = document.getElementsByClassName('invalid-input'); + if (invalidInput.length) { + (invalidInput[0]).focus(); + } + + this.addEventListener('issue-title', 'input', _ => { + this.validateInput('issue-title'); + }); + + this.addEventListener('description', 'input', _ => { + this.validateInput('description'); + }); + + this.addEventListener('issue-source', 'change', _ => { + this.validateInput('issue-source'); + }); + + if (this.issueReporterModel.fileOnExtension()) { + this.addEventListener('extension-selector', 'change', _ => { + this.validateInput('extension-selector'); + }); + } + + return false; + } + + this.hasBeenSubmitted = true; + + const issueTitle = (this.getElementById('issue-title')).value; + const issueBody = this.issueReporterModel.serialize(); + + const issueUrl = this.getIssueUrl(); + const gitHubDetails = this.parseGitHubUrl(issueUrl); + if (this.configuration.data.githubAccessToken && gitHubDetails) { + return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); + } + + const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value, issueUrl); + let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; + + if (url.length > MAX_URL_LENGTH) { + try { + url = await this.writeToClipboard(baseUrl, issueBody); + } catch (_) { + return false; + } + } + + await this.nativeHostService.openExternal(url); + return true; + } + + private async writeToClipboard(baseUrl: string, issueBody: string): Promise { + const shouldWrite = await this.issueMainService.$showClipboardDialog(); + if (!shouldWrite) { + throw new CancellationError(); + } + + await this.nativeHostService.writeClipboardText(issueBody); + + return baseUrl + `&body=${encodeURIComponent(localize('pasteData', "We have written the needed data into your clipboard because it was too large to send. Please paste."))}`; + } + + private getIssueUrl(): string { + return this.issueReporterModel.fileOnExtension() + ? this.getExtensionGitHubUrl() + : this.issueReporterModel.getData().fileOnMarketplace + ? this.configuration.product.reportMarketplaceIssueUrl! + : this.configuration.product.reportIssueUrl!; + } + + 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(); + const extensionUrl = this.getExtensionRepositoryUrl(); + // If given, try to match the extension's bug url + if (bugsUrl && bugsUrl.match(/^https?:\/\/github\.com\/(.*)/)) { + repositoryUrl = normalizeGitHubUrl(bugsUrl); + } else if (extensionUrl && extensionUrl.match(/^https?:\/\/github\.com\/(.*)/)) { + repositoryUrl = normalizeGitHubUrl(extensionUrl); + } + + return repositoryUrl; + } + + private getIssueUrlWithTitle(issueTitle: string, repositoryUrl: string): string { + if (this.issueReporterModel.fileOnExtension()) { + repositoryUrl = repositoryUrl + '/issues/new'; + } + + const queryStringPrefix = repositoryUrl.indexOf('?') === -1 ? '?' : '&'; + return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`; + } + + private updateSystemInfo(state: IssueReporterModelData) { + const target = document.querySelector('.block-system .block-info'); + + if (target) { + const systemInfo = state.systemInfo!; + const renderedDataTable = $('table', undefined, + $('tr', undefined, + $('td', undefined, 'CPUs'), + $('td', undefined, systemInfo.cpus || '') + ), + $('tr', undefined, + $('td', undefined, 'GPU Status' as string), + $('td', undefined, Object.keys(systemInfo.gpuStatus).map(key => `${key}: ${systemInfo.gpuStatus[key]}`).join('\n')) + ), + $('tr', undefined, + $('td', undefined, 'Load (avg)' as string), + $('td', undefined, systemInfo.load || '') + ), + $('tr', undefined, + $('td', undefined, 'Memory (System)' as string), + $('td', undefined, systemInfo.memory) + ), + $('tr', undefined, + $('td', undefined, 'Process Argv' as string), + $('td', undefined, systemInfo.processArgs) + ), + $('tr', undefined, + $('td', undefined, 'Screen Reader' as string), + $('td', undefined, systemInfo.screenReader) + ), + $('tr', undefined, + $('td', undefined, 'VM'), + $('td', undefined, systemInfo.vmHint) + ) + ); + reset(target, renderedDataTable); + + systemInfo.remoteData.forEach(remote => { + target.appendChild($('hr')); + if (isRemoteDiagnosticError(remote)) { + const remoteDataTable = $('table', undefined, + $('tr', undefined, + $('td', undefined, 'Remote'), + $('td', undefined, remote.hostName) + ), + $('tr', undefined, + $('td', undefined, ''), + $('td', undefined, remote.errorMessage) + ) + ); + target.appendChild(remoteDataTable); + } else { + const remoteDataTable = $('table', undefined, + $('tr', undefined, + $('td', undefined, 'Remote'), + $('td', undefined, remote.latency ? `${remote.hostName} (latency: ${remote.latency.current.toFixed(2)}ms last, ${remote.latency.average.toFixed(2)}ms average)` : remote.hostName) + ), + $('tr', undefined, + $('td', undefined, 'OS'), + $('td', undefined, remote.machineInfo.os) + ), + $('tr', undefined, + $('td', undefined, 'CPUs'), + $('td', undefined, remote.machineInfo.cpus || '') + ), + $('tr', undefined, + $('td', undefined, 'Memory (System)' as string), + $('td', undefined, remote.machineInfo.memory) + ), + $('tr', undefined, + $('td', undefined, 'VM'), + $('td', undefined, remote.machineInfo.vmHint) + ) + ); + target.appendChild(remoteDataTable); + } + }); + } + } + + private updateExtensionSelector(extensions: IssueReporterExtensionData[]): void { + interface IOption { + name: string; + id: string; + } + + const extensionOptions: IOption[] = extensions.map(extension => { + return { + name: extension.displayName || extension.name || '', + id: extension.id + }; + }); + + // Sort extensions by name + extensionOptions.sort((a, b) => { + const aName = a.name.toLowerCase(); + const bName = b.name.toLowerCase(); + if (aName > bName) { + return 1; + } + + if (aName < bName) { + return -1; + } + + return 0; + }); + + const makeOption = (extension: IOption, selectedExtension?: IssueReporterExtensionData): HTMLOptionElement => { + const selected = selectedExtension && extension.id === selectedExtension.id; + return $('option', { + 'value': extension.id, + 'selected': selected || '' + }, extension.name); + }; + + const extensionsSelector = this.getElementById('extension-selector'); + if (extensionsSelector) { + const { selectedExtension } = this.issueReporterModel.getData(); + reset(extensionsSelector, $('option'), ...extensionOptions.map(extension => makeOption(extension, selectedExtension))); + + this.addEventListener('extension-selector', 'change', (e: Event) => { + const selectedExtensionId = (e.target).value; + const extensions = this.issueReporterModel.getData().allExtensions; + const matches = extensions.filter(extension => extension.id === selectedExtensionId); + if (matches.length) { + this.issueReporterModel.update({ selectedExtension: matches[0] }); + if (matches[0].hasIssueUriRequestHandler) { + this.updateIssueReporterUri(matches[0]); + } else { + this.validateSelectedExtension(); + const title = (this.getElementById('issue-title')).value; + this.searchExtensionIssues(title); + } + } else { + this.issueReporterModel.update({ selectedExtension: undefined }); + this.clearSearchResults(); + this.validateSelectedExtension(); + } + this.updatePreviewButtonState(); + this.renderBlocks(); + }); + } + + this.addEventListener('problem-source', 'change', (_) => { + this.validateSelectedExtension(); + }); + } + + private validateSelectedExtension(): void { + const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!; + const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!; + hide(extensionValidationMessage); + hide(extensionValidationNoUrlsMessage); + + const extension = this.issueReporterModel.getData().selectedExtension; + if (!extension) { + this.previewButton.enabled = true; + return; + } + + const hasValidGitHubUrl = this.getExtensionGitHubUrl(); + if (hasValidGitHubUrl || extension.hasIssueUriRequestHandler) { + this.previewButton.enabled = true; + } else { + this.setExtensionValidationMessage(); + this.previewButton.enabled = false; + } + } + + private setExtensionValidationMessage(): void { + const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!; + const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!; + const bugsUrl = this.getExtensionBugsUrl(); + if (bugsUrl) { + show(extensionValidationMessage); + const link = this.getElementById('extensionBugsLink')!; + link.textContent = bugsUrl; + return; + } + + const extensionUrl = this.getExtensionRepositoryUrl(); + if (extensionUrl) { + show(extensionValidationMessage); + const link = this.getElementById('extensionBugsLink'); + link!.textContent = extensionUrl; + return; + } + + show(extensionValidationNoUrlsMessage); + } + + private updateProcessInfo(state: IssueReporterModelData) { + const target = document.querySelector('.block-process .block-info') as HTMLElement; + if (target) { + reset(target, $('code', undefined, state.processInfo ?? '')); + } + } + + private updateWorkspaceInfo(state: IssueReporterModelData) { + document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo; + } + + private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void { + const target = document.querySelector('.block-extensions .block-info'); + if (target) { + if (this.configuration.disableExtensions) { + reset(target, localize('disabledExtensions', "Extensions are disabled")); + return; + } + + const themeExclusionStr = numThemeExtensions ? `\n(${numThemeExtensions} theme extensions excluded)` : ''; + extensions = extensions || []; + + if (!extensions.length) { + target.innerText = 'Extensions: none' + themeExclusionStr; + return; + } + + reset(target, this.getExtensionTableHtml(extensions), document.createTextNode(themeExclusionStr)); + } + } + + private updateRestrictedMode(restrictedMode: boolean) { + this.issueReporterModel.update({ restrictedMode }); + } + + private updateUnsupportedMode(isUnsupported: boolean) { + this.issueReporterModel.update({ isUnsupported }); + } + + 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, + $('th', undefined, 'Extension'), + $('th', undefined, 'Author (truncated)' as string), + $('th', undefined, 'Version') + ), + ...extensions.map(extension => $('tr', undefined, + $('td', undefined, extension.name), + $('td', undefined, extension.publisher?.substr(0, 3) ?? 'N/A'), + $('td', undefined, extension.version) + )) + ); + } + + private openLink(event: MouseEvent): void { + event.preventDefault(); + event.stopPropagation(); + // Exclude right click + if (event.which < 3) { + windowOpenNoOpener((event.target).href); + } + } + + private getElementById(elementId: string): T | undefined { + const element = document.getElementById(elementId) as T | undefined; + if (element) { + return element; + } else { + return undefined; + } + } + + private addEventListener(elementId: string, eventType: string, handler: (event: Event) => void): void { + const element = this.getElementById(elementId); + element?.addEventListener(eventType, handler); + } +} + +// helper functions + +export function hide(el: Element | undefined | null) { + el?.classList.add('hidden'); +} +export function show(el: Element | undefined | null) { + el?.classList.remove('hidden'); +} diff --git a/src/vs/code/electron-sandbox/issue/media/issueReporter.css b/src/vs/code/electron-sandbox/issue/media/issueReporter.css index 9d044b982e..728d8bd384 100644 --- a/src/vs/code/electron-sandbox/issue/media/issueReporter.css +++ b/src/vs/code/electron-sandbox/issue/media/issueReporter.css @@ -73,6 +73,7 @@ textarea { padding: 4px 10px; align-self: flex-end; margin-bottom: 10px; + font-size: 13px; } select { diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.html b/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.html new file mode 100644 index 0000000000..55f2c0fe81 --- /dev/null +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer-dev.html @@ -0,0 +1,42 @@ + + + + + + + + + +
+ + + + + + + + diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorer.html b/src/vs/code/electron-sandbox/processExplorer/processExplorer.html index 73d89eac31..845d024e62 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorer.html +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorer.html @@ -3,17 +3,37 @@ - + +
- - - - - - + diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 40e2d8e8c6..8af27e327c 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -19,13 +19,15 @@ import { IRemoteDiagnosticError, isRemoteDiagnosticError } from 'vs/platform/dia import { ByteSize } from 'vs/platform/files/common/files'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { ProcessExplorerData, ProcessExplorerStyles, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { INativeHostService } from 'vs/platform/native/common/native'; import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/window/electron-sandbox/window'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; -const DEBUG_FLAGS_PATTERN = /\s--(inspect|debug)(-brk|port)?=(\d+)?/; -const DEBUG_PORT_PATTERN = /\s--(inspect|debug)-port=(\d+)/; +const DEBUG_FLAGS_PATTERN = /\s--inspect(?:-brk|port)?=(?\d+)?/; +const DEBUG_PORT_PATTERN = /\s--inspect-port=(?\d+)/; class ProcessListDelegate implements IListVirtualDelegate { getHeight(element: MachineProcessInformation | ProcessItem | IRemoteDiagnosticError) { @@ -54,13 +56,13 @@ class ProcessListDelegate implements IListVirtualDelegate { @@ -104,15 +106,16 @@ class ProcessTreeDataSource implements IDataSource { templateId: string = 'header'; + renderTemplate(container: HTMLElement): IProcessItemTemplateData { - const data = Object.create(null); const row = append(container, $('.row')); - data.name = append(row, $('.nameLabel')); - data.CPU = append(row, $('.cpu')); - data.memory = append(row, $('.memory')); - data.PID = append(row, $('.pid')); - return data; + const name = append(row, $('.nameLabel')); + const CPU = append(row, $('.cpu')); + const memory = append(row, $('.memory')); + const PID = append(row, $('.pid')); + return { name, CPU, memory, PID }; } + renderElement(node: ITreeNode, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void { templateData.name.textContent = localize('name', "Process Name"); templateData.CPU.textContent = localize('cpu', "CPU (%)"); @@ -120,6 +123,7 @@ class ProcessHeaderTreeRenderer implements ITreeRenderer { - constructor(private platform: string, private totalMem: number, private mapPidToWindowTitle: Map) { } + constructor(private platform: string, private totalMem: number, private mapPidToName: Map) { } templateId: string = 'process'; renderTemplate(container: HTMLElement): IProcessItemTemplateData { - const data = Object.create(null); const row = append(container, $('.row')); - data.name = append(row, $('.nameLabel')); - data.CPU = append(row, $('.cpu')); - data.memory = append(row, $('.memory')); - data.PID = append(row, $('.pid')); + const name = append(row, $('.nameLabel')); + const CPU = append(row, $('.cpu')); + const memory = append(row, $('.memory')); + const PID = append(row, $('.pid')); - return data; + return { name, CPU, PID, memory }; } 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; - } const pid = element.pid.toFixed(0); + let name = element.name; + if (this.mapPidToName.has(element.pid)) { + name = this.mapPidToName.get(element.pid)!; + } + templateData.name.textContent = name; templateData.name.title = element.cmd; @@ -227,7 +230,7 @@ function isProcessItem(item: any): item is ProcessItem { class ProcessExplorer { private lastRequestTime: number; - private mapPidToWindowTitle = new Map(); + private mapPidToName = new Map(); private nativeHostService: INativeHostService; @@ -240,10 +243,12 @@ class ProcessExplorer { this.applyStyles(data.styles); this.setEventHandlers(data); - // Map window process pids to titles, annotate process names with this when rendering to distinguish between them - ipcRenderer.on('vscode:windowsInfoResponse', (event: unknown, windows: any[]) => { - this.mapPidToWindowTitle = new Map(); - windows.forEach(window => this.mapPidToWindowTitle.set(window.pid, window.title)); + ipcRenderer.on('vscode:pidToNameResponse', (event: unknown, pidToNames: [number, string][]) => { + this.mapPidToName = new Map(); + + for (const [pid, name] of pidToNames) { + this.mapPidToName.set(pid, name); + } }); ipcRenderer.on('vscode:listProcessesResponse', async (event: unknown, processRoots: MachineProcessInformation[]) => { @@ -257,13 +262,14 @@ class ProcessExplorer { await this.createProcessTree(processRoots); } else { this.tree.setInput({ processes: { processRoots } }); + this.tree.layout(window.innerHeight, window.innerWidth); } this.requestProcessList(0); }); this.lastRequestTime = Date.now(); - ipcRenderer.send('vscode:windowsInfoRequest'); + ipcRenderer.send('vscode:pidToNameRequest'); ipcRenderer.send('vscode:listProcesses'); } @@ -300,7 +306,7 @@ class ProcessExplorer { const { totalmem } = await this.nativeHostService.getOSStatistics(); const renderers = [ - new ProcessRenderer(this.data.platform, totalmem, this.mapPidToWindowTitle), + new ProcessRenderer(this.data.platform, totalmem, this.mapPidToName), new ProcessHeaderTreeRenderer(), new MachineRenderer(), new ErrorRenderer() @@ -332,21 +338,35 @@ class ProcessExplorer { return 'header'; } - }, + } }); this.tree.setInput({ processes: { processRoots } }); this.tree.layout(window.innerHeight, window.innerWidth); + this.tree.onKeyDown(e => { + const event = new StandardKeyboardEvent(e); + if (event.keyCode === KeyCode.KeyE && event.altKey) { + const selectionPids = this.getSelectedPids(); + void Promise.all(selectionPids.map((pid) => this.nativeHostService.killProcess(pid, 'SIGTERM'))).then(() => this.tree?.refresh()); + } + }); this.tree.onContextMenu(e => { if (isProcessItem(e.element)) { this.showContextMenu(e.element, true); } }); + + container.style.height = `${window.innerHeight}px`; + + window.addEventListener('resize', () => { + container.style.height = `${window.innerHeight}px`; + this.tree?.layout(window.innerHeight, window.innerWidth); + }); } private isDebuggable(cmd: string): boolean { const matches = DEBUG_FLAGS_PATTERN.exec(cmd); - return (matches && matches.length >= 2) || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0; + return (matches && matches.groups!.port !== '0') || cmd.indexOf('node ') >= 0 || cmd.indexOf('node.exe') >= 0; } private attachTo(item: ProcessItem) { @@ -357,12 +377,8 @@ class ProcessExplorer { }; let matches = DEBUG_FLAGS_PATTERN.exec(item.cmd); - if (matches && matches.length >= 2) { - // attach via port - if (matches.length === 4 && matches[3]) { - config.port = parseInt(matches[3]); - } - config.protocol = matches[1] === 'debug' ? 'legacy' : 'inspector'; + if (matches) { + config.port = Number(matches.groups!.port); } else { // no port -> try to attach via pid (send SIGUSR1) config.processId = String(item.pid); @@ -370,9 +386,9 @@ class ProcessExplorer { // a debug-port=n or inspect-port=n overrides the port matches = DEBUG_PORT_PATTERN.exec(item.cmd); - if (matches && matches.length === 3) { + if (matches) { // override port - config.port = parseInt(matches[2]); + config.port = Number(matches.groups!.port); } ipcRenderer.send('vscode:workbenchCommand', { id: 'debug.startFromConfig', from: 'processExplorer', args: [config] }); @@ -416,6 +432,47 @@ class ProcessExplorer { content.push(`.monaco-list-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`); } + // Scrollbars + if (styles.scrollbarShadowColor) { + content.push(` + .monaco-scrollable-element > .shadow.top { + box-shadow: ${styles.scrollbarShadowColor} 0 6px 6px -6px inset; + } + + .monaco-scrollable-element > .shadow.left { + box-shadow: ${styles.scrollbarShadowColor} 6px 0 6px -6px inset; + } + + .monaco-scrollable-element > .shadow.top.left { + box-shadow: ${styles.scrollbarShadowColor} 6px 6px 6px -6px inset; + } + `); + } + + if (styles.scrollbarSliderBackgroundColor) { + content.push(` + .monaco-scrollable-element > .scrollbar > .slider { + background: ${styles.scrollbarSliderBackgroundColor}; + } + `); + } + + if (styles.scrollbarSliderHoverBackgroundColor) { + content.push(` + .monaco-scrollable-element > .scrollbar > .slider:hover { + background: ${styles.scrollbarSliderHoverBackgroundColor}; + } + `); + } + + if (styles.scrollbarSliderActiveBackgroundColor) { + content.push(` + .monaco-scrollable-element > .scrollbar > .slider.active { + background: ${styles.scrollbarSliderActiveBackgroundColor}; + } + `); + } + styleElement.textContent = content.join('\n'); if (styles.color) { @@ -429,6 +486,7 @@ class ProcessExplorer { if (isLocal) { items.push({ + accelerator: 'Alt+E', label: localize('killProcess', "Kill Process"), click: () => { this.nativeHostService.killProcess(pid, 'SIGTERM'); @@ -451,12 +509,7 @@ class ProcessExplorer { label: localize('copy', "Copy"), click: () => { // Collect the selected pids - const selectionPids = this.tree?.getSelection()?.map(e => { - if (!e || !('pid' in e)) { - return undefined; - } - return e.pid; - }).filter(e => !!e) as number[]; + const selectionPids = this.getSelectedPids(); // If the selection does not contain the right clicked item, copy the right clicked // item only. if (!selectionPids?.includes(pid)) { @@ -505,13 +558,22 @@ class ProcessExplorer { // Wait at least a second between requests. if (waited > 1000) { - ipcRenderer.send('vscode:windowsInfoRequest'); + ipcRenderer.send('vscode:pidToNameRequest'); ipcRenderer.send('vscode:listProcesses'); } else { this.requestProcessList(waited); } }, 200); } + + private getSelectedPids() { + return this.tree?.getSelection()?.map(e => { + if (!e || !('pid' in e)) { + return undefined; + } + return e.pid; + }).filter(e => !!e) as number[]; + } } function createCodiconStyleSheet() { diff --git a/src/vs/code/electron-sandbox/workbench/workbench-dev.html b/src/vs/code/electron-sandbox/workbench/workbench-dev.html new file mode 100644 index 0000000000..4fde7a1111 --- /dev/null +++ b/src/vs/code/electron-sandbox/workbench/workbench-dev.html @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index f34f6f6cd2..27c3e3c8ff 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -9,11 +9,6 @@ - - - - - diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index ca0af36024..105d399627 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -24,20 +24,21 @@ 'vs/nls!vs/workbench/workbench.desktop.main', 'vs/css!vs/workbench/workbench.desktop.main' ], - function (_, configuration) { + function (desktopMain, configuration) { // Mark start of workbench performance.mark('code/didLoadWorkbenchMain'); - // @ts-ignore + // @ts-ignore // {{SQL CARBON EDIT}} - load desktop main directly return require('vs/workbench/electron-sandbox/desktop.main').main(configuration); + //return desktopMain.main(configuration); }, { configureDeveloperSettings: function (windowConfig) { return { // disable automated devtools opening on error when running extension tests // as this can lead to nondeterministic test execution (devtools steals focus) - forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string', + forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string' || windowConfig['enable-smoke-test-driver'] === true, // enable devtools keybindings in extension development window forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0, removeDeveloperKeybindingsAfterLoad: true @@ -158,6 +159,11 @@ document.head.appendChild(style); style.textContent = `body { background-color: ${shellBackground}; color: ${shellForeground}; margin: 0; padding: 0; }`; + // set zoom level as soon as possible + if (typeof data?.zoomLevel === 'number' && typeof globalThis.vscode?.webFrame?.setZoomLevel === 'function') { + globalThis.vscode.webFrame.setZoomLevel(data.zoomLevel); + } + // restore parts if possible (we might not always store layout info) if (data?.layoutInfo) { const { layoutInfo, colorInfo } = data; diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 2cb321ab30..58722f395c 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -3,15 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ChildProcess, spawn, SpawnOptions } from 'child_process'; +import { ChildProcess, spawn, SpawnOptions, StdioOptions } from 'child_process'; import { chmodSync, existsSync, readFileSync, statSync, truncateSync, unlinkSync } from 'fs'; import { homedir, release, tmpdir } from 'os'; import type { ProfilingSession, Target } from 'v8-inspect-profiler'; import { Event } from 'vs/base/common/event'; -import { isAbsolute, resolve, join } from 'vs/base/common/path'; +import { isAbsolute, resolve, join, dirname } from 'vs/base/common/path'; import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; import { randomPort } from 'vs/base/common/ports'; -import { isString } from 'vs/base/common/types'; import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort } from 'vs/base/node/ports'; import { watchFileContents } from 'vs/platform/files/node/watcher/nodejs/nodejsWatcherLib'; @@ -19,13 +18,15 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv'; import { addArg, parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper'; import { getStdinFilePath, hasStdinWithoutTty, readFromStdin, stdinDataListener } from 'vs/platform/environment/node/stdin'; -import { createWaitMarkerFile } from 'vs/platform/environment/node/wait'; +import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait'; import product from 'vs/platform/product/common/product'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { randomPath } from 'vs/base/common/extpath'; +import { isUNC, randomPath } from 'vs/base/common/extpath'; import { Utils } from 'vs/platform/profiling/common/profiling'; -import { dirname } from 'vs/base/common/resources'; import { FileAccess } from 'vs/base/common/network'; +import { cwd } from 'vs/base/common/process'; +import { addUNCHostToAllowlist } from 'vs/base/node/unc'; +import { URI } from 'vs/base/common/uri'; function shouldSpawnCliProcess(argv: NativeParsedArgs): boolean { return !!argv['install-source'] @@ -50,6 +51,33 @@ export async function main(argv: string[]): Promise { return; } + if (args.tunnel) { + if (!product.tunnelApplicationName) { + console.error(`'tunnel' command not supported in ${product.applicationName}`); + return; + } + const tunnelArgs = argv.slice(argv.indexOf('tunnel') + 1); // all arguments behind `tunnel` + return new Promise((resolve, reject) => { + let tunnelProcess: ChildProcess; + const stdio: StdioOptions = ['ignore', 'pipe', 'pipe']; + if (process.env['VSCODE_DEV']) { + tunnelProcess = spawn('cargo', ['run', '--', 'tunnel', ...tunnelArgs], { cwd: join(getAppRoot(), 'cli'), stdio }); + } else { + const appPath = process.platform === 'darwin' + // ./Contents/MacOS/Electron => ./Contents/Resources/app/bin/code-tunnel-insiders + ? join(dirname(dirname(process.execPath)), 'Resources', 'app') + : dirname(process.execPath); + const tunnelCommand = join(appPath, 'bin', `${product.tunnelApplicationName}${isWindows ? '.exe' : ''}`); + tunnelProcess = spawn(tunnelCommand, ['tunnel', ...tunnelArgs], { cwd: cwd(), stdio }); + } + + tunnelProcess.stdout!.pipe(process.stdout); + tunnelProcess.stderr!.pipe(process.stderr); + tunnelProcess.on('exit', resolve); + tunnelProcess.on('error', reject); + }); + } + // Help if (args.help) { const executable = `${product.applicationName}${isWindows ? '.exe' : ''}`; @@ -71,9 +99,11 @@ export async function main(argv: string[]): Promise { case 'pwsh': file = 'shellIntegration.ps1'; break; // Usage: `[[ "$TERM_PROGRAM" == "vscode" ]] && . "$(code --locate-shell-integration-path zsh)"` case 'zsh': file = 'shellIntegration-rc.zsh'; break; + // Usage: `string match -q "$TERM_PROGRAM" "vscode"; and . (code --locate-shell-integration-path fish)` + case 'fish': file = 'fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish'; break; default: throw new Error('Error using --locate-shell-integration-path: Invalid shell type'); } - console.log(join(dirname(FileAccess.asFileUri('', require)).fsPath, 'out', 'vs', 'workbench', 'contrib', 'terminal', 'browser', 'media', file)); + console.log(join(getAppRoot(), 'out', 'vs', 'workbench', 'contrib', 'terminal', 'browser', 'media', file)); } // Extensions Management @@ -89,6 +119,16 @@ export async function main(argv: string[]): Promise { const source = args._[0]; const target = args._[1]; + // Windows: set the paths as allowed UNC paths given + // they are explicitly provided by the user as arguments + if (isWindows) { + for (const path of [source, target]) { + if (isUNC(path)) { + addUNCHostToAllowlist(URI.file(path).authority); + } + } + } + // Validate if ( !source || !target || source === target || // make sure source and target are provided and are not the same @@ -148,13 +188,14 @@ export async function main(argv: string[]): Promise { const processCallbacks: ((child: ChildProcess) => Promise)[] = []; - const verbose = args.verbose || args.status; - if (verbose) { + if (args.verbose) { env['ELECTRON_ENABLE_LOGGING'] = '1'; + } + if (args.verbose || args.status) { processCallbacks.push(async child => { - child.stdout!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); - child.stderr!.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); + child.stdout?.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); + child.stderr?.on('data', (data: Buffer) => console.log(data.toString('utf8').trim())); await Event.toPromise(Event.fromNodeEventEmitter(child, 'exit')); }); @@ -179,7 +220,7 @@ export async function main(argv: string[]): Promise { // 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 + await readFromStdin(stdinFilePath, !!args.verbose); // throws error if file can not be written // Make sure to open tmp file addArg(argv, stdinFilePath); @@ -218,7 +259,7 @@ export async function main(argv: string[]): Promise { // is closed and then exit the waiting process. let waitMarkerFilePath: string | undefined; if (args.wait) { - waitMarkerFilePath = createWaitMarkerFile(verbose); + waitMarkerFilePath = createWaitMarkerFileSync(args.verbose); if (waitMarkerFilePath) { addArg(argv, '--waitMarkerFilePath', waitMarkerFilePath); } @@ -332,7 +373,7 @@ export async function main(argv: string[]): Promise { return false; } if (target.type === 'page') { - return target.url.indexOf('workbench/workbench.html') > 0; + return target.url.indexOf('workbench/workbench.html') > 0 || target.url.indexOf('workbench/workbench-dev.html') > 0; } else { return true; } @@ -362,25 +403,21 @@ export async function main(argv: string[]): Promise { }); } - const jsFlags = args['js-flags']; - if (isString(jsFlags)) { - const match = /max_old_space_size=(\d+)/g.exec(jsFlags); - if (match && !args['max-memory']) { - addArg(argv, `--max-memory=${match[1]}`); - } - } - const options: SpawnOptions = { detached: true, env }; - if (!verbose) { + if (!args.verbose) { options['stdio'] = 'ignore'; } let child: ChildProcess; if (!isMacOSBigSurOrNewer) { + if (!args.verbose && args.status) { + options['stdio'] = ['ignore', 'pipe', 'ignore']; // restore ability to see output when --status is used + } + // We spawn process.execPath directly child = spawn(process.execPath, argv.slice(2), options); } else { @@ -399,13 +436,13 @@ export async function main(argv: string[]): Promise { // -a opens the given application. spawnArgs.push('-a', process.execPath); // -a: opens a specific application - if (verbose) { + if (args.verbose || args.status) { spawnArgs.push('--wait-apps'); // `open --wait-apps`: blocks until the launched app is closed (even if they were already running) // The open command only allows for redirecting stderr and stdout to files, // so we make it redirect those to temp files, and then use a logger to // redirect the file output to the console - for (const outputType of ['stdout', 'stderr']) { + for (const outputType of args.verbose ? ['stdout', 'stderr'] : ['stdout']) { // Tmp file to target output to const tmpName = randomPath(tmpdir(), `code-${outputType}`); @@ -465,6 +502,10 @@ export async function main(argv: string[]): Promise { } } +function getAppRoot() { + return dirname(FileAccess.asFileUri('').fsPath); +} + function eventuallyExit(code: number): void { setTimeout(() => process.exit(code), 0); } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index f1f28d0042..e8bc14523a 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -5,9 +5,8 @@ import { hostname, release } from 'os'; import { raceTimeout } from 'vs/base/common/async'; -import { VSBuffer } from 'vs/base/common/buffer'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { isSigPipeError, onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { isAbsolute, join } from 'vs/base/common/path'; @@ -23,9 +22,10 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv'; import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IExtensionGalleryService, IExtensionManagementCLIService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService'; -import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; +import { IExtensionGalleryService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from 'vs/platform/extensionManagement/node/extensionSignatureVerificationService'; +import { ExtensionManagementCLI } from 'vs/platform/extensionManagement/common/extensionManagementCLI'; +import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; @@ -38,8 +38,7 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; -import { ConsoleLogger, getLogLevel, ILogger, ILogService, LogLevel, MultiplexLogService } from 'vs/platform/log/common/log'; -import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; +import { ConsoleLogger, getLogLevel, ILogger, ILoggerService, ILogService, LogLevel } from 'vs/platform/log/common/log'; import { FilePolicyService } from 'vs/platform/policy/common/filePolicyService'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; import { NativePolicyService } from 'vs/platform/policy/node/nativePolicyService'; @@ -47,18 +46,22 @@ import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; import { RequestService } from 'vs/platform/request/node/requestService'; -import { IStateService } from 'vs/platform/state/node/state'; -import { StateService } from 'vs/platform/state/node/stateService'; +import { SaveStrategy, StateReadonlyService } from 'vs/platform/state/node/stateService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; -import { ITelemetryService, machineIdKey } from 'vs/platform/telemetry/common/telemetry'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { supportsTelemetry, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry, ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { UserDataProfilesReadonlyService } from 'vs/platform/userDataProfile/node/userDataProfile'; +import { resolveMachineId } from 'vs/platform/telemetry/node/telemetryUtils'; +import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; +import { LogService } from 'vs/platform/log/common/logService'; +import { LoggerService } from 'vs/platform/log/node/loggerService'; +import { localize } from 'vs/nls'; class CliMain extends Disposable { @@ -85,7 +88,7 @@ class CliMain extends Disposable { const logService = accessor.get(ILogService); const fileService = accessor.get(IFileService); const environmentService = accessor.get(INativeEnvironmentService); - const extensionManagementCLIService = accessor.get(IExtensionManagementCLIService); + const userDataProfilesService = accessor.get(IUserDataProfilesService); // Log info logService.info('CLI main', this.argv); @@ -94,7 +97,7 @@ class CliMain extends Disposable { this.registerErrorHandler(logService); // Run based on argv - await this.doRun(environmentService, extensionManagementCLIService, fileService); + await this.doRun(environmentService, fileService, userDataProfilesService, instantiationService); // Flush the remaining data in AI adapter (with 1s timeout) await Promise.all(appenders.map(a => { @@ -104,7 +107,7 @@ class CliMain extends Disposable { }); } - private async initServices(): Promise<[IInstantiationService, OneDataSystemAppender[]]> { + private async initServices(): Promise<[IInstantiationService, ITelemetryAppender[]]> { const services = new ServiceCollection(); // Product @@ -116,17 +119,23 @@ class CliMain extends Disposable { services.set(INativeEnvironmentService, environmentService); // Init folders - await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? Promises.mkdir(path, { recursive: true }) : undefined)); + await Promise.all([ + environmentService.appSettingsHome.fsPath, + environmentService.extensionsPath + ].map(path => path ? Promises.mkdir(path, { recursive: true }) : undefined)); + + // Logger + const loggerService = new LoggerService(getLogLevel(environmentService), environmentService.logsHome); + services.set(ILoggerService, loggerService); // Log - const logLevel = getLogLevel(environmentService); - const loggers: ILogger[] = []; - loggers.push(new SpdLogLogger('cli', join(environmentService.logsPath, 'cli.log'), true, false, logLevel)); - if (logLevel === LogLevel.Trace) { - loggers.push(new ConsoleLogger(logLevel)); + const logger = this._register(loggerService.createLogger('cli', { name: localize('cli', "CLI") })); + const otherLoggers: ILogger[] = []; + if (loggerService.getLogLevel() === LogLevel.Trace) { + otherLoggers.push(new ConsoleLogger(loggerService.getLogLevel())); } - const logService = this._register(new MultiplexLogService(loggers)); + const logService = this._register(new LogService(logger, otherLoggers)); services.set(ILogService, logService); // Files @@ -136,20 +145,17 @@ class CliMain extends Disposable { const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService)); fileService.registerProvider(Schemas.file, diskFileSystemProvider); - // State - const stateService = new StateService(environmentService, logService, fileService); - services.set(IStateService, stateService); - // Uri Identity const uriIdentityService = new UriIdentityService(fileService); services.set(IUriIdentityService, uriIdentityService); // User Data Profiles - const userDataProfilesService = new UserDataProfilesService(stateService, uriIdentityService, environmentService, fileService, logService); + const stateService = new StateReadonlyService(SaveStrategy.DELAYED, environmentService, logService, fileService); + const userDataProfilesService = new UserDataProfilesReadonlyService(stateService, uriIdentityService, environmentService, fileService, logService); services.set(IUserDataProfilesService, userDataProfilesService); // Policy - const policyService = isWindows && productService.win32RegValueName ? this._register(new NativePolicyService(productService.win32RegValueName)) + const policyService = isWindows && productService.win32RegValueName ? this._register(new NativePolicyService(logService, productService.win32RegValueName)) : environmentService.policyFile ? this._register(new FilePolicyService(environmentService.policyFile, fileService, logService)) : new NullPolicyService(); services.set(IPolicyService, policyService); @@ -164,57 +170,54 @@ class CliMain extends Disposable { configurationService.initialize() ]); - userDataProfilesService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); + // Get machine ID + let machineId: string | undefined = undefined; + try { + machineId = await resolveMachineId(stateService, logService); + } catch (error) { + if (error.code !== 'ENOENT') { + logService.error(error); + } + } + + // Initialize user data profiles after initializing the state + userDataProfilesService.init(); // URI Identity services.set(IUriIdentityService, new UriIdentityService(fileService)); // Request - services.set(IRequestService, new SyncDescriptor(RequestService)); + services.set(IRequestService, new SyncDescriptor(RequestService, undefined, true)); // Download Service - services.set(IDownloadService, new SyncDescriptor(DownloadService)); + services.set(IDownloadService, new SyncDescriptor(DownloadService, undefined, true)); // Extensions - services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); - services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); - services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); - services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService)); - services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService)); + services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true)); + services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true)); + services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true)); + services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService, undefined, true)); + services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService, undefined, true)); // Localizations - services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService)); + services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService, undefined, false)); // Telemetry - const appenders: OneDataSystemAppender[] = []; + const appenders: ITelemetryAppender[] = []; const isInternal = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { if (productService.aiConfig && productService.aiConfig.ariaKey) { appenders.push(new OneDataSystemAppender(isInternal, 'adsworkbench', null, productService.aiConfig.ariaKey)); // {{SQL CARBON EDIT}} Use our own event prefix } - const { installSourcePath } = environmentService; - const config: ITelemetryServiceConfig = { appenders, sendErrorTelemetry: false, - commonProperties: (async () => { - let machineId: string | undefined = undefined; - try { - const storageContents = await Promises.readFile(environmentService.stateResource.fsPath); - machineId = JSON.parse(storageContents.toString())[machineIdKey]; - } catch (error) { - if (error.code !== 'ENOENT') { - logService.error(error); - } - } - - return resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, machineId, isInternal, installSourcePath); - })(), + commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, machineId, isInternal), piiPaths: getPiiPathsFromEnvironment(environmentService) }; - services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config])); + services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [config], false)); } else { services.set(ITelemetryService, NullTelemetryService); @@ -236,36 +239,43 @@ class CliMain extends Disposable { }); // Handle unhandled errors that can occur - process.on('uncaughtException', err => onUnexpectedError(err)); + process.on('uncaughtException', err => { + if (!isSigPipeError(err)) { + onUnexpectedError(err); + } + }); process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); } - private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService, fileService: IFileService): Promise { - - // Install Source - if (this.argv['install-source']) { - return this.setInstallSource(environmentService, fileService, this.argv['install-source']); + private async doRun(environmentService: INativeEnvironmentService, fileService: IFileService, userDataProfilesService: IUserDataProfilesService, instantiationService: IInstantiationService): Promise { + let profile: IUserDataProfile | undefined = undefined; + if (environmentService.args.profile) { + profile = userDataProfilesService.profiles.find(p => p.name === environmentService.args.profile); + if (!profile) { + throw new Error(`Profile '${environmentService.args.profile}' not found.`); + } } + const profileLocation = (profile ?? userDataProfilesService.defaultProfile).extensionsResource; // List Extensions if (this.argv['list-extensions']) { - return extensionManagementCLIService.listExtensions(!!this.argv['show-versions'], this.argv['category']); + return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).listExtensions(!!this.argv['show-versions'], this.argv['category'], profileLocation); } // Install Extension else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) { - const installOptions: InstallOptions = { isMachineScoped: !!this.argv['do-not-sync'], installPreReleaseVersion: !!this.argv['pre-release'] }; - return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], installOptions, !!this.argv['force']); + const installOptions: InstallOptions = { isMachineScoped: !!this.argv['do-not-sync'], installPreReleaseVersion: !!this.argv['pre-release'], profileLocation }; + return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.asExtensionIdOrVSIX(this.argv['install-builtin-extension'] || []), installOptions, !!this.argv['force']); } // Uninstall Extension else if (this.argv['uninstall-extension']) { - return extensionManagementCLIService.uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force']); + return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force'], profileLocation); } // Locate Extension else if (this.argv['locate-extension']) { - return extensionManagementCLIService.locateExtension(this.argv['locate-extension']); + return instantiationService.createInstance(ExtensionManagementCLI, new ConsoleLogger(LogLevel.Info, false)).locateExtension(this.argv['locate-extension']); } // Telemetry @@ -277,10 +287,6 @@ class CliMain extends Disposable { private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] { return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(cwd(), input)) : input); } - - private async setInstallSource(environmentService: INativeEnvironmentService, fileService: IFileService, installSource: string): Promise { - await fileService.writeFile(URI.file(environmentService.installSourcePath), VSBuffer.fromString(installSource.slice(0, 30))); - } } export async function main(argv: NativeParsedArgs): Promise { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner.ts b/src/vs/code/node/sharedProcess/contrib/codeCacheCleaner.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner.ts rename to src/vs/code/node/sharedProcess/contrib/codeCacheCleaner.ts diff --git a/src/vs/code/node/sharedProcess/contrib/extensions.ts b/src/vs/code/node/sharedProcess/contrib/extensions.ts new file mode 100644 index 0000000000..c7b955ba64 --- /dev/null +++ b/src/vs/code/node/sharedProcess/contrib/extensions.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; +import { migrateUnsupportedExtensions } from 'vs/platform/extensionManagement/common/unsupportedExtensionsMigration'; +import { INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IStorageService } from 'vs/platform/storage/common/storage'; + +export class ExtensionsContributions extends Disposable { + constructor( + @INativeServerExtensionManagementService extensionManagementService: INativeServerExtensionManagementService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @IExtensionStorageService extensionStorageService: IExtensionStorageService, + @IGlobalExtensionEnablementService extensionEnablementService: IGlobalExtensionEnablementService, + @IStorageService storageService: IStorageService, + @ILogService logService: ILogService, + ) { + super(); + + extensionManagementService.cleanUp(); + migrateUnsupportedExtensions(extensionManagementService, extensionGalleryService, extensionStorageService, extensionEnablementService, logService); + ExtensionStorageService.removeOutdatedExtensionVersions(extensionManagementService, storageService); + } + +} diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts rename to src/vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts b/src/vs/code/node/sharedProcess/contrib/localizationsUpdater.ts similarity index 100% rename from src/vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater.ts rename to src/vs/code/node/sharedProcess/contrib/localizationsUpdater.ts diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts b/src/vs/code/node/sharedProcess/contrib/logsDataCleaner.ts similarity index 84% rename from src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts rename to src/vs/code/node/sharedProcess/contrib/logsDataCleaner.ts index f83e511cda..750c540551 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts +++ b/src/vs/code/node/sharedProcess/contrib/logsDataCleaner.ts @@ -6,7 +6,7 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; -import { basename, dirname, join } from 'vs/base/common/path'; +import { basename, dirname, joinPath } from 'vs/base/common/resources'; import { Promises } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -29,10 +29,10 @@ export class LogsDataCleaner extends Disposable { this.logService.trace('[logs cleanup]: Starting to clean up old logs.'); try { - const currentLog = basename(this.environmentService.logsPath); - const logsRoot = dirname(this.environmentService.logsPath); + const currentLog = basename(this.environmentService.logsHome); + const logsRoot = dirname(this.environmentService.logsHome); - const logFiles = await Promises.readdir(logsRoot); + const logFiles = await Promises.readdir(logsRoot.fsPath); const allSessions = logFiles.filter(logFile => /^\d{8}T\d{6}$/.test(logFile)); const oldSessions = allSessions.sort().filter(session => session !== currentLog); @@ -41,7 +41,7 @@ export class LogsDataCleaner extends Disposable { if (sessionsToDelete.length > 0) { this.logService.trace(`[logs cleanup]: Removing log folders '${sessionsToDelete.join(', ')}'`); - await Promise.all(sessionsToDelete.map(sessionToDelete => Promises.rm(join(logsRoot, sessionToDelete)))); + await Promise.all(sessionsToDelete.map(sessionToDelete => Promises.rm(joinPath(logsRoot, sessionToDelete).fsPath))); } } catch (error) { onUnexpectedError(error); diff --git a/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts new file mode 100644 index 0000000000..ffefd4fa1f --- /dev/null +++ b/src/vs/code/node/sharedProcess/contrib/storageDataCleaner.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RunOnceScheduler } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { join } from 'vs/base/common/path'; +import { Promises } from 'vs/base/node/pfs'; +import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ILogService } from 'vs/platform/log/common/log'; +import { StorageClient } from 'vs/platform/storage/common/storageIpc'; +import { EXTENSION_DEVELOPMENT_EMPTY_WINDOW_WORKSPACE } from 'vs/platform/workspace/common/workspace'; +import { NON_EMPTY_WORKSPACE_ID_LENGTH } from 'vs/platform/workspaces/node/workspaces'; +import { INativeHostService } from 'vs/platform/native/common/native'; +import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; + +export class UnusedWorkspaceStorageDataCleaner extends Disposable { + + constructor( + @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, + @ILogService private readonly logService: ILogService, + @INativeHostService private readonly nativeHostService: INativeHostService, + @IMainProcessService private readonly mainProcessService: IMainProcessService + ) { + super(); + + const scheduler = this._register(new RunOnceScheduler(() => { + this.cleanUpStorage(); + }, 30 * 1000 /* after 30s */)); + scheduler.schedule(); + } + + private async cleanUpStorage(): Promise { + this.logService.trace('[storage cleanup]: Starting to clean up workspace storage folders for unused empty workspaces.'); + + try { + const workspaceStorageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath); + const storageClient = new StorageClient(this.mainProcessService.getChannel('storage')); + + await Promise.all(workspaceStorageFolders.map(async workspaceStorageFolder => { + const workspaceStoragePath = join(this.environmentService.workspaceStorageHome.fsPath, workspaceStorageFolder); + + if (workspaceStorageFolder.length === NON_EMPTY_WORKSPACE_ID_LENGTH) { + return; // keep workspace storage for folders/workspaces that can be accessed still + } + + if (workspaceStorageFolder === EXTENSION_DEVELOPMENT_EMPTY_WINDOW_WORKSPACE.id) { + return; // keep workspace storage for empty extension development workspaces + } + + const windows = await this.nativeHostService.getWindows(); + if (windows.some(window => window.workspace?.id === workspaceStorageFolder)) { + return; // keep workspace storage for empty workspaces opened as window + } + + const isStorageUsed = await storageClient.isUsed(workspaceStoragePath); + if (isStorageUsed) { + return; // keep workspace storage for empty workspaces that are in use + } + + this.logService.trace(`[storage cleanup]: Deleting workspace storage folder ${workspaceStorageFolder} as it seems to be an unused empty workspace.`); + + await Promises.rm(workspaceStoragePath); + })); + } catch (error) { + onUnexpectedError(error); + } + } +} diff --git a/src/vs/code/node/sharedProcess/contrib/userDataProfilesCleaner.ts b/src/vs/code/node/sharedProcess/contrib/userDataProfilesCleaner.ts new file mode 100644 index 0000000000..e3ddf5ca93 --- /dev/null +++ b/src/vs/code/node/sharedProcess/contrib/userDataProfilesCleaner.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RunOnceScheduler } from 'vs/base/common/async'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; + +export class UserDataProfilesCleaner extends Disposable { + + constructor( + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService + ) { + super(); + + const scheduler = this._register(new RunOnceScheduler(() => { + userDataProfilesService.cleanUp(); + }, 10 * 1000 /* after 10s */)); + scheduler.schedule(); + } +} diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts similarity index 67% rename from src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts rename to src/vs/code/node/sharedProcess/sharedProcessMain.ts index f11c1d9751..5081feaeb4 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -3,22 +3,20 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ipcRenderer } from 'electron'; +/* eslint-disable local/code-layering, local/code-import-patterns */ import { hostname, release } from 'os'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; -import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp'; -import { CodeCacheCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner'; -import { ExtensionsCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/extensionsCleaner'; -import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; -import { LocalizationsUpdater } from 'vs/code/electron-browser/sharedProcess/contrib/localizationsUpdater'; -import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; -import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; +import { Server as UtilityProcessMessagePortServer, once } from 'vs/base/parts/ipc/node/ipc.mp'; +import { CodeCacheCleaner } from 'vs/code/node/sharedProcess/contrib/codeCacheCleaner'; +import { LanguagePackCachedDataCleaner } from 'vs/code/node/sharedProcess/contrib/languagePackCachedDataCleaner'; +import { LocalizationsUpdater } from 'vs/code/node/sharedProcess/contrib/localizationsUpdater'; +import { LogsDataCleaner } from 'vs/code/node/sharedProcess/contrib/logsDataCleaner'; +import { UnusedWorkspaceStorageDataCleaner } from 'vs/code/node/sharedProcess/contrib/storageDataCleaner'; import { IChecksumService } from 'vs/platform/checksum/common/checksumService'; import { ChecksumService } from 'vs/platform/checksum/node/checksumService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -31,12 +29,11 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro import { SharedProcessEnvironmentService } from 'vs/platform/sharedProcess/node/sharedProcessEnvironmentService'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; -import { IDefaultExtensionsProfileInitService, IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionSignatureVerificationService, IExtensionSignatureVerificationService } from 'vs/platform/extensionManagement/node/extensionSignatureVerificationService'; import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -import { ExtensionTipsService } from 'vs/platform/extensionManagement/electron-sandbox/extensionTipsService'; import { ExtensionManagementService, INativeServerExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; -import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; @@ -44,19 +41,15 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { NativeLanguagePackService } from 'vs/platform/languagePacks/node/languagePacks'; -import { ConsoleLogger, ILoggerService, ILogService, MultiplexLogService } from 'vs/platform/log/common/log'; -import { FollowerLogService, LoggerChannelClient, LogLevelChannelClient } from 'vs/platform/log/common/logIpc'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { ConsoleLogger, ILoggerService, ILogService } from 'vs/platform/log/common/log'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import product from 'vs/platform/product/common/product'; import { IProductService } from 'vs/platform/product/common/productService'; import { IRequestService } from 'vs/platform/request/common/request'; import { ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { NativeStorageService } from 'vs/platform/storage/electron-sandbox/storageService'; import { resolveCommonProperties } from 'vs/platform/telemetry/common/commonProperties'; import { ICustomEndpointTelemetryService, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc'; @@ -64,12 +57,9 @@ import { TelemetryLogAppender } from 'vs/platform/telemetry/common/telemetryLogA import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { supportsTelemetry, ITelemetryAppender, NullAppender, NullTelemetryService, getPiiPathsFromEnvironment, isInternalTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; import { CustomEndpointTelemetryService } from 'vs/platform/telemetry/node/customEndpointTelemetryService'; -import { LocalReconnectConstants, TerminalIpcChannels, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { ILocalPtyService } from 'vs/platform/terminal/electron-sandbox/terminal'; -import { PtyHostService } from 'vs/platform/terminal/node/ptyHostService'; import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration as registerUserDataSyncConfiguration } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncBackupStoreService, IUserDataSyncLogService, IUserDataSyncEnablementService, IUserDataSyncService, IUserDataSyncStoreManagementService, IUserDataSyncStoreService, IUserDataSyncUtilService, registerConfiguration as registerUserDataSyncConfiguration, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { IUserDataSyncAccountService, UserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { UserDataAutoSyncChannel, UserDataSyncAccountServiceChannel, UserDataSyncMachinesServiceChannel, UserDataSyncStoreManagementServiceChannel, UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; @@ -79,7 +69,8 @@ import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/u import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncServiceIpc'; import { UserDataSyncStoreManagementService, UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-sandbox/userDataAutoSyncService'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; +import { NativeUserDataProfileStorageService } from 'vs/platform/userDataProfile/node/userDataProfileStorageService'; import { ActiveWindowManager } from 'vs/platform/windows/node/windowTracker'; import { ISignService } from 'vs/platform/sign/common/sign'; import { SignService } from 'vs/platform/sign/node/signService'; @@ -87,9 +78,6 @@ import { ISharedTunnelsService } from 'vs/platform/tunnel/common/tunnel'; import { SharedTunnelsService } from 'vs/platform/tunnel/node/tunnelService'; import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService'; import { SharedProcessTunnelService } from 'vs/platform/tunnel/node/sharedProcessTunnelService'; -import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService'; -import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService'; -// import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; // {{SQL CARBON EDIT}} - Unused import import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { isLinux } from 'vs/base/common/platform'; @@ -100,19 +88,36 @@ import { IV8InspectProfilingService } from 'vs/platform/profiling/common/profili import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; +import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { PolicyChannelClient } from 'vs/platform/policy/common/policyIpc'; import { IPolicyService, NullPolicyService } from 'vs/platform/policy/common/policy'; -import { UserDataProfilesNativeService } from 'vs/platform/userDataProfile/electron-sandbox/userDataProfile'; -import { OneDataSystemWebAppender } from 'vs/platform/telemetry/browser/1dsAppender'; -import { DefaultExtensionsProfileInitService } from 'vs/platform/extensionManagement/electron-sandbox/defaultExtensionsProfileInit'; -import { SharedProcessRequestService } from 'vs/platform/request/electron-browser/sharedProcessRequestService'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; +import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender'; +import { UserDataProfilesCleaner } from 'vs/code/node/sharedProcess/contrib/userDataProfilesCleaner'; +import { IRemoteTunnelService } from 'vs/platform/remoteTunnel/common/remoteTunnel'; +import { UserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSyncResourceProvider'; +import { ExtensionsContributions } from 'vs/code/node/sharedProcess/contrib/extensions'; +import { localize } from 'vs/nls'; +import { LogService } from 'vs/platform/log/common/logService'; +import { ISharedProcessLifecycleService, SharedProcessLifecycleService } from 'vs/platform/lifecycle/node/sharedProcessLifecycleService'; +import { RemoteTunnelService } from 'vs/platform/remoteTunnel/node/remoteTunnelService'; +import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; +import { RequestChannelClient } from 'vs/platform/request/common/requestIpc'; +import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/common/extensionRecommendationsIpc'; +import { INativeHostService } from 'vs/platform/native/common/native'; +import { UserDataAutoSyncService } from 'vs/platform/userDataSync/node/userDataAutoSyncService'; +import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; +import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/common/mainProcessService'; +import { RemoteStorageService } from 'vs/platform/storage/common/storageService'; +import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; +import { RemoteConnectionType } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { nodeSocketFactory } from 'vs/platform/remote/node/nodeSocketFactory'; class SharedProcessMain extends Disposable { - private server = this._register(new MessagePortServer()); + private readonly server = this._register(new UtilityProcessMessagePortServer()); - private sharedProcessWorkerService: ISharedProcessWorkerService | undefined = undefined; + private lifecycleService: SharedProcessLifecycleService | undefined = undefined; constructor(private configuration: ISharedProcessConfiguration) { super(); @@ -123,27 +128,20 @@ class SharedProcessMain extends Disposable { private registerListeners(): void { // Shared process lifecycle - const onExit = () => this.dispose(); + let didExit = false; + const onExit = () => { + if (!didExit) { + didExit = true; + + this.lifecycleService?.fireOnWillShutdown(); + this.dispose(); + } + }; process.once('exit', onExit); - ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit); - - // Shared process worker lifecycle - // - // We dispose the listener when the shared process is - // disposed to avoid disposing workers when the entire - // application is shutting down anyways. - // - const eventName = 'vscode:electron-main->shared-process=disposeWorker'; - const onDisposeWorker = (event: unknown, configuration: ISharedProcessWorkerConfiguration) => this.onDisposeWorker(configuration); - ipcRenderer.on(eventName, onDisposeWorker); - this._register(toDisposable(() => ipcRenderer.removeListener(eventName, onDisposeWorker))); + once(process.parentPort, 'vscode:electron-main->shared-process=exit', onExit); } - private onDisposeWorker(configuration: ISharedProcessWorkerConfiguration): void { - this.sharedProcessWorkerService?.disposeWorker(configuration); - } - - async open(): Promise { + async init(): Promise { // Services const instantiationService = await this.initServices(); @@ -168,10 +166,11 @@ class SharedProcessMain extends Disposable { this._register(combinedDisposable( instantiationService.createInstance(CodeCacheCleaner, this.configuration.codeCachePath), instantiationService.createInstance(LanguagePackCachedDataCleaner), - instantiationService.createInstance(StorageDataCleaner, this.configuration.backupWorkspacesPath), + instantiationService.createInstance(UnusedWorkspaceStorageDataCleaner), instantiationService.createInstance(LogsDataCleaner), instantiationService.createInstance(LocalizationsUpdater), - instantiationService.createInstance(ExtensionsCleaner) + instantiationService.createInstance(ExtensionsContributions), + instantiationService.createInstance(UserDataProfilesCleaner) )); } @@ -184,7 +183,7 @@ class SharedProcessMain extends Disposable { // Main Process const mainRouter = new StaticRouter(ctx => ctx === 'main'); - const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter); + const mainProcessService = new MainProcessService(this.server, mainRouter); services.set(IMainProcessService, mainProcessService); // Policies @@ -196,22 +195,18 @@ class SharedProcessMain extends Disposable { services.set(INativeEnvironmentService, environmentService); // Logger - const logLevelClient = new LogLevelChannelClient(this.server.getChannel('logLevel', mainRouter)); - const loggerService = new LoggerChannelClient(this.configuration.logLevel, logLevelClient.onDidChangeLogLevel, mainProcessService.getChannel('logger')); + const loggerService = new LoggerChannelClient(undefined, this.configuration.logLevel, environmentService.logsHome, this.configuration.loggers.map(loggerResource => ({ ...loggerResource, resource: URI.revive(loggerResource.resource) })), mainProcessService.getChannel('logger')); services.set(ILoggerService, loggerService); // Log - const multiplexLogger = this._register(new MultiplexLogService([ - this._register(new ConsoleLogger(this.configuration.logLevel)), - this._register(loggerService.createLogger(joinPath(URI.file(environmentService.logsPath), 'sharedprocess.log'), { name: 'sharedprocess' })) - ])); - - const logService = this._register(new FollowerLogService(logLevelClient, multiplexLogger)); + const logger = this._register(loggerService.createLogger('sharedprocess', { name: localize('sharedLog', "Shared") })); + const consoleLogger = this._register(new ConsoleLogger(logger.getLevel())); + const logService = this._register(new LogService(logger, [consoleLogger])); services.set(ILogService, logService); - // Worker - this.sharedProcessWorkerService = new SharedProcessWorkerService(logService); - services.set(ISharedProcessWorkerService, this.sharedProcessWorkerService); + // Lifecycle + this.lifecycleService = this._register(new SharedProcessLifecycleService(logService)); + services.set(ISharedProcessLifecycleService, this.lifecycleService); // Files const fileService = this._register(new FileService(logService)); @@ -233,7 +228,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // User Data Profiles - const userDataProfilesService = this._register(new UserDataProfilesNativeService(this.configuration.profiles, mainProcessService, environmentService)); + const userDataProfilesService = this._register(new UserDataProfilesService(this.configuration.profiles.all, URI.revive(this.configuration.profiles.home).with({ scheme: environmentService.userRoamingDataHome.scheme }), mainProcessService.getChannel('userDataProfiles'))); services.set(IUserDataProfilesService, userDataProfilesService); // Configuration @@ -241,7 +236,7 @@ class SharedProcessMain extends Disposable { services.set(IConfigurationService, configurationService); // Storage (global access only) - const storageService = new NativeStorageService(undefined, { defaultProfile: userDataProfilesService.defaultProfile, currentProfile: userDataProfilesService.defaultProfile }, mainProcessService, environmentService); + const storageService = new RemoteStorageService(undefined, { defaultProfile: userDataProfilesService.defaultProfile, currentProfile: userDataProfilesService.defaultProfile }, mainProcessService, environmentService); services.set(IStorageService, storageService); this._register(toDisposable(() => storageService.flush())); @@ -252,23 +247,24 @@ class SharedProcessMain extends Disposable { ]); // URI Identity - services.set(IUriIdentityService, new UriIdentityService(fileService)); + const uriIdentityService = new UriIdentityService(fileService); + services.set(IUriIdentityService, uriIdentityService); // Request - services.set(IRequestService, new SharedProcessRequestService(mainProcessService, configurationService, logService)); + services.set(IRequestService, new RequestChannelClient(mainProcessService.getChannel('request'))); // Checksum - services.set(IChecksumService, new SyncDescriptor(ChecksumService)); + services.set(IChecksumService, new SyncDescriptor(ChecksumService, undefined, false /* proxied to other processes */)); // V8 Inspect profiler - services.set(IV8InspectProfilingService, new SyncDescriptor(V8InspectProfilingService)); + services.set(IV8InspectProfilingService, new SyncDescriptor(V8InspectProfilingService, undefined, false /* proxied to other processes */)); // Native Host - const nativeHostService = ProxyChannel.toService(mainProcessService.getChannel('nativeHost'), { context: this.configuration.windowId }); + const nativeHostService = ProxyChannel.toService(mainProcessService.getChannel('nativeHost')); services.set(INativeHostService, nativeHostService); // Download - services.set(IDownloadService, new SyncDescriptor(DownloadService)); + services.set(IDownloadService, new SyncDescriptor(DownloadService, undefined, true)); // Extension recommendations const activeWindowManager = this._register(new ActiveWindowManager(nativeHostService)); @@ -280,18 +276,17 @@ class SharedProcessMain extends Disposable { const appenders: ITelemetryAppender[] = []; const internalTelemetry = isInternalTelemetry(productService, configurationService); if (supportsTelemetry(productService, environmentService)) { - const logAppender = new TelemetryLogAppender(loggerService, environmentService); + const logAppender = new TelemetryLogAppender(logService, loggerService, environmentService, productService); appenders.push(logAppender); - const { installSourcePath } = environmentService; if (productService.aiConfig?.ariaKey) { - const collectorAppender = new OneDataSystemWebAppender(internalTelemetry, 'adsworkbench', null, productService.aiConfig.ariaKey); // {{SQL CARBON EDIT}} Use our own event prefix + const collectorAppender = new OneDataSystemAppender(internalTelemetry, 'adsworkbench', null, productService.aiConfig.ariaKey); // {{SQL CARBON EDIT}} - use adsworkbench this._register(toDisposable(() => collectorAppender.flush())); // Ensure the 1DS appender is disposed so that it flushes remaining data appenders.push(collectorAppender); } telemetryService = new TelemetryService({ appenders, - commonProperties: resolveCommonProperties(fileService, release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, internalTelemetry, installSourcePath), + commonProperties: resolveCommonProperties(release(), hostname(), process.arch, productService.commit, productService.version, this.configuration.machineId, internalTelemetry), sendErrorTelemetry: true, piiPaths: getPiiPathsFromEnvironment(environmentService), }, configurationService, productService); @@ -305,62 +300,56 @@ class SharedProcessMain extends Disposable { services.set(ITelemetryService, telemetryService); // Custom Endpoint Telemetry - const customEndpointTelemetryService = new CustomEndpointTelemetryService(configurationService, telemetryService, loggerService, environmentService, productService); + const customEndpointTelemetryService = new CustomEndpointTelemetryService(configurationService, telemetryService, logService, loggerService, environmentService, productService); services.set(ICustomEndpointTelemetryService, customEndpointTelemetryService); // Extension Management - services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService)); - services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService)); - services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); - services.set(IDefaultExtensionsProfileInitService, new SyncDescriptor(DefaultExtensionsProfileInitService)); + services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true)); + services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true)); + services.set(IExtensionSignatureVerificationService, new SyncDescriptor(ExtensionSignatureVerificationService, undefined, true)); + services.set(INativeServerExtensionManagementService, new SyncDescriptor(ExtensionManagementService, undefined, true)); // Extension Gallery - services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); + services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService, undefined, true)); // Extension Tips - services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService)); + services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService, undefined, false /* Eagerly scans and computes exe based recommendations */)); // Localizations - services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService)); + services.set(ILanguagePackService, new SyncDescriptor(NativeLanguagePackService, undefined, false /* proxied to other processes */)); // Diagnostics - services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); + services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService, undefined, false /* proxied to other processes */)); // Settings Sync - services.set(IUserDataSyncAccountService, new SyncDescriptor(UserDataSyncAccountService)); - services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService)); + services.set(IUserDataSyncAccountService, new SyncDescriptor(UserDataSyncAccountService, undefined, true)); + services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService, undefined, true)); 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(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService, undefined, false /* Eagerly resets installed extensions */)); + services.set(IIgnoredExtensionsManagementService, new SyncDescriptor(IgnoredExtensionsManagementService, undefined, true)); services.set(IExtensionStorageService, new SyncDescriptor(ExtensionStorageService)); - services.set(IUserDataSyncStoreManagementService, new SyncDescriptor(UserDataSyncStoreManagementService)); - services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService)); - services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService)); - services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService)); - services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService)); - services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService)); - - const ptyHostService = new PtyHostService({ - graceTime: LocalReconnectConstants.GraceTime, - shortGraceTime: LocalReconnectConstants.ShortGraceTime, - scrollback: configurationService.getValue(TerminalSettingId.PersistentSessionScrollback) ?? 100 - }, - configurationService, - environmentService, - logService - ); - ptyHostService.initialize(); - - // Terminal - services.set(ILocalPtyService, this._register(ptyHostService)); + services.set(IUserDataSyncStoreManagementService, new SyncDescriptor(UserDataSyncStoreManagementService, undefined, true)); + services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService, undefined, true)); + services.set(IUserDataSyncMachinesService, new SyncDescriptor(UserDataSyncMachinesService, undefined, true)); + services.set(IUserDataSyncBackupStoreService, new SyncDescriptor(UserDataSyncBackupStoreService, undefined, false /* Eagerly cleans up old backups */)); + services.set(IUserDataSyncEnablementService, new SyncDescriptor(UserDataSyncEnablementService, undefined, true)); + services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService, undefined, false /* Initializes the Sync State */)); + services.set(IUserDataProfileStorageService, new SyncDescriptor(NativeUserDataProfileStorageService, undefined, true)); + services.set(IUserDataSyncResourceProviderService, new SyncDescriptor(UserDataSyncResourceProviderService, undefined, true)); // Signing - services.set(ISignService, new SyncDescriptor(SignService)); + services.set(ISignService, new SyncDescriptor(SignService, undefined, false /* proxied to other processes */)); // Tunnel + const remoteSocketFactoryService = new RemoteSocketFactoryService(); + services.set(IRemoteSocketFactoryService, remoteSocketFactoryService); + remoteSocketFactoryService.register(RemoteConnectionType.WebSocket, nodeSocketFactory); services.set(ISharedTunnelsService, new SyncDescriptor(SharedTunnelsService)); services.set(ISharedProcessTunnelService, new SyncDescriptor(SharedProcessTunnelService)); + // Remote Tunnel + services.set(IRemoteTunnelService, new SyncDescriptor(RemoteTunnelService)); + return new InstantiationService(services); } @@ -404,41 +393,27 @@ class SharedProcessMain extends Disposable { const userDataSyncStoreManagementChannel = new UserDataSyncStoreManagementServiceChannel(accessor.get(IUserDataSyncStoreManagementService)); this.server.registerChannel('userDataSyncStoreManagement', userDataSyncStoreManagementChannel); - const userDataSyncChannel = new UserDataSyncChannel(accessor.get(IUserDataSyncService), accessor.get(ILogService)); + const userDataSyncChannel = new UserDataSyncChannel(accessor.get(IUserDataSyncService), accessor.get(IUserDataProfilesService), 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); - // Tunnel const sharedProcessTunnelChannel = ProxyChannel.fromService(accessor.get(ISharedProcessTunnelService)); this.server.registerChannel(ipcSharedProcessTunnelChannelName, sharedProcessTunnelChannel); - // Worker - const sharedProcessWorkerChannel = ProxyChannel.fromService(accessor.get(ISharedProcessWorkerService)); - this.server.registerChannel(ipcSharedProcessWorkerChannelName, sharedProcessWorkerChannel); - - // Default Extensions Profile Init - this.server.registerChannel('IDefaultExtensionsProfileInitService', ProxyChannel.fromService(accessor.get(IDefaultExtensionsProfileInitService))); + // Remote Tunnel + const remoteTunnelChannel = ProxyChannel.fromService(accessor.get(IRemoteTunnelService)); + this.server.registerChannel('remoteTunnel', remoteTunnelChannel); } 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(); - }); + // Listen on global error events + process.on('uncaughtException', error => onUnexpectedError(error)); + process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); // Install handler for unexpected errors setUnexpectedErrorHandler(error => { @@ -456,10 +431,16 @@ export async function main(configuration: ISharedProcessConfiguration): Promise< // 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'); + process.parentPort.postMessage('vscode:shared-process->electron-main=ipc-ready'); // await initialization and signal this back to electron-main - await sharedProcess.open(); - ipcRenderer.send('vscode:shared-process->electron-main=init-done'); + await sharedProcess.init(); + + process.parentPort.postMessage('vscode:shared-process->electron-main=init-done'); } + +process.parentPort.once('message', (e: Electron.MessageEvent) => { + main(e.data as ISharedProcessConfiguration); +}); diff --git a/src/vs/editor/browser/config/domFontInfo.ts b/src/vs/editor/browser/config/domFontInfo.ts index 8b63846129..fdf788e8e0 100644 --- a/src/vs/editor/browser/config/domFontInfo.ts +++ b/src/vs/editor/browser/config/domFontInfo.ts @@ -12,6 +12,7 @@ export function applyFontInfo(domNode: FastDomNode | HTMLElement, f domNode.setFontWeight(fontInfo.fontWeight); domNode.setFontSize(fontInfo.fontSize); domNode.setFontFeatureSettings(fontInfo.fontFeatureSettings); + domNode.setFontVariationSettings(fontInfo.fontVariationSettings); domNode.setLineHeight(fontInfo.lineHeight); domNode.setLetterSpacing(fontInfo.letterSpacing); } else { @@ -19,6 +20,7 @@ export function applyFontInfo(domNode: FastDomNode | HTMLElement, f domNode.style.fontWeight = fontInfo.fontWeight; domNode.style.fontSize = fontInfo.fontSize + 'px'; domNode.style.fontFeatureSettings = fontInfo.fontFeatureSettings; + domNode.style.fontVariationSettings = fontInfo.fontVariationSettings; domNode.style.lineHeight = fontInfo.lineHeight + 'px'; domNode.style.letterSpacing = fontInfo.letterSpacing + 'px'; } diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index 89ee977c55..ad597b6278 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -12,7 +12,7 @@ import * as platform from 'vs/base/common/platform'; import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; import { FontMeasurements } from 'vs/editor/browser/config/fontMeasurements'; import { migrateOptions } from 'vs/editor/browser/config/migrateOptions'; -import { TabFocus } from 'vs/editor/browser/config/tabFocus'; +import { TabFocus, TabFocusContext } from 'vs/editor/browser/config/tabFocus'; import { ComputeOptionsMemory, ConfigurationChangedEvent, EditorOption, editorOptionsRegistry, FindComputedEditorOptionValueById, IComputedEditorOptions, IEditorOptions, IEnvironmentalOptions } from 'vs/editor/common/config/editorOptions'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { BareFontInfo, FontInfo, IValidatedEditorOptions } from 'vs/editor/common/config/fontInfo'; @@ -47,6 +47,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat private _viewLineCount: number = 1; private _lineNumbersDigitCount: number = 1; private _reservedHeight: number = 0; + private _glyphMarginDecorationLaneCount: number = 1; private readonly _computeOptionsMemory: ComputeOptionsMemory = new ComputeOptionsMemory(); /** @@ -116,8 +117,9 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat lineNumbersDigitCount: this._lineNumbersDigitCount, emptySelectionClipboard: partialEnv.emptySelectionClipboard, pixelRatio: partialEnv.pixelRatio, - tabFocusMode: TabFocus.getTabFocusMode(), - accessibilitySupport: partialEnv.accessibilitySupport + tabFocusMode: TabFocus.getTabFocusMode(TabFocusContext.Editor), + accessibilitySupport: partialEnv.accessibilitySupport, + glyphMarginDecorationLaneCount: this._glyphMarginDecorationLaneCount }; return EditorOptionsUtil.computeOptions(this._validatedOptions, env); } @@ -193,6 +195,14 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat this._reservedHeight = reservedHeight; this._recomputeOptions(); } + + public setGlyphMarginDecorationLaneCount(decorationLaneCount: number): void { + if (this._glyphMarginDecorationLaneCount === decorationLaneCount) { + return; + } + this._glyphMarginDecorationLaneCount = decorationLaneCount; + this._recomputeOptions(); + } } function digitCount(n: number): number { diff --git a/src/vs/editor/browser/config/fontMeasurements.ts b/src/vs/editor/browser/config/fontMeasurements.ts index f2d25f4f42..2c148d2297 100644 --- a/src/vs/editor/browser/config/fontMeasurements.ts +++ b/src/vs/editor/browser/config/fontMeasurements.ts @@ -20,6 +20,7 @@ export interface ISerializedFontInfo { readonly fontWeight: string; readonly fontSize: number; readonly fontFeatureSettings: string; + readonly fontVariationSettings: string; readonly lineHeight: number; readonly letterSpacing: number; readonly isMonospace: boolean; @@ -32,7 +33,7 @@ export interface ISerializedFontInfo { readonly maxDigitWidth: number; } -class FontMeasurementsImpl extends Disposable { +export class FontMeasurementsImpl extends Disposable { private _cache: FontMeasurementsCache; private _evictUntrustedReadingsTimeout: number; @@ -128,6 +129,7 @@ class FontMeasurementsImpl extends Disposable { fontWeight: readConfig.fontWeight, fontSize: readConfig.fontSize, fontFeatureSettings: readConfig.fontFeatureSettings, + fontVariationSettings: readConfig.fontVariationSettings, lineHeight: readConfig.lineHeight, letterSpacing: readConfig.letterSpacing, isMonospace: readConfig.isMonospace, @@ -219,6 +221,7 @@ class FontMeasurementsImpl extends Disposable { fontWeight: bareFontInfo.fontWeight, fontSize: bareFontInfo.fontSize, fontFeatureSettings: bareFontInfo.fontFeatureSettings, + fontVariationSettings: bareFontInfo.fontVariationSettings, lineHeight: bareFontInfo.lineHeight, letterSpacing: bareFontInfo.letterSpacing, isMonospace: isMonospace, diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts index 723a082497..7c1dec0358 100644 --- a/src/vs/editor/browser/config/migrateOptions.ts +++ b/src/vs/editor/browser/config/migrateOptions.ts @@ -89,6 +89,8 @@ registerSimpleEditorSettingMigration('hover', [[true, { enabled: true }], [false registerSimpleEditorSettingMigration('parameterHints', [[true, { enabled: true }], [false, { enabled: false }]]); registerSimpleEditorSettingMigration('autoIndent', [[false, 'advanced'], [true, 'full']]); registerSimpleEditorSettingMigration('matchBrackets', [[true, 'always'], [false, 'never']]); +registerSimpleEditorSettingMigration('renderFinalNewline', [[true, 'on'], [false, 'off']]); +registerSimpleEditorSettingMigration('cursorSmoothCaretAnimation', [[true, 'on'], [false, 'off']]); registerEditorSettingMigration('autoClosingBrackets', (value, read, write) => { if (value === false) { @@ -170,3 +172,23 @@ registerEditorSettingMigration('quickSuggestions', (input, read, write) => { write('quickSuggestions', newValue); } }); + +// Sticky Scroll + +registerEditorSettingMigration('experimental.stickyScroll.enabled', (value, read, write) => { + if (typeof value === 'boolean') { + write('experimental.stickyScroll.enabled', undefined); + if (typeof read('stickyScroll.enabled') === 'undefined') { + write('stickyScroll.enabled', value); + } + } +}); + +registerEditorSettingMigration('experimental.stickyScroll.maxLineCount', (value, read, write) => { + if (typeof value === 'number') { + write('experimental.stickyScroll.maxLineCount', undefined); + if (typeof read('stickyScroll.maxLineCount') === 'undefined') { + write('stickyScroll.maxLineCount', value); + } + } +}); diff --git a/src/vs/editor/browser/config/tabFocus.ts b/src/vs/editor/browser/config/tabFocus.ts index b118af8147..b9db1c98e1 100644 --- a/src/vs/editor/browser/config/tabFocus.ts +++ b/src/vs/editor/browser/config/tabFocus.ts @@ -5,23 +5,29 @@ import { Emitter, Event } from 'vs/base/common/event'; +export const enum TabFocusContext { + Terminal = 'terminalFocus', + Editor = 'editorFocus' +} + class TabFocusImpl { - private _tabFocus: boolean = false; + private _tabFocusTerminal: boolean = false; + private _tabFocusEditor: boolean = false; - private readonly _onDidChangeTabFocus = new Emitter(); - public readonly onDidChangeTabFocus: Event = this._onDidChangeTabFocus.event; + private readonly _onDidChangeTabFocus = new Emitter(); + public readonly onDidChangeTabFocus: Event = this._onDidChangeTabFocus.event; - public getTabFocusMode(): boolean { - return this._tabFocus; + public getTabFocusMode(context: TabFocusContext): boolean { + return context === TabFocusContext.Terminal ? this._tabFocusTerminal : this._tabFocusEditor; } - public setTabFocusMode(tabFocusMode: boolean): void { - if (this._tabFocus === tabFocusMode) { - return; + public setTabFocusMode(tabFocusMode: boolean, context: TabFocusContext): void { + if (context === TabFocusContext.Terminal) { + this._tabFocusTerminal = tabFocusMode; + } else { + this._tabFocusEditor = tabFocusMode; } - - this._tabFocus = tabFocusMode; - this._onDidChangeTabFocus.fire(this._tabFocus); + this._onDidChangeTabFocus.fire(); } } diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index ac5875a20c..1fc25b0317 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -5,12 +5,11 @@ import * as dom from 'vs/base/browser/dom'; import { StandardWheelEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { TimeoutTimer } from 'vs/base/common/async'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { HitTestContext, MouseTarget, MouseTargetFactory, PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; -import { IMouseTarget, IMouseTargetViewZoneData, MouseTargetType } from 'vs/editor/browser/editorBrowser'; -import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorPointerMoveMonitor, createEditorPagePosition, createCoordinatesRelativeToEditor } from 'vs/editor/browser/editorDom'; +import { IMouseTarget, IMouseTargetOutsideEditor, IMouseTargetViewZoneData, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ClientCoordinates, EditorMouseEvent, EditorMouseEventFactory, GlobalEditorPointerMoveMonitor, createEditorPagePosition, createCoordinatesRelativeToEditor, PageCoordinates } from 'vs/editor/browser/editorDom'; import { ViewController } from 'vs/editor/browser/view/viewController'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { Position } from 'vs/editor/common/core/position'; @@ -20,6 +19,8 @@ import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import * as viewEvents from 'vs/editor/common/viewEvents'; import { ViewEventHandler } from 'vs/editor/common/viewEventHandler'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; +import { NavigationCommandRevealType } from 'vs/editor/browser/coreCommands'; +import { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableElement'; export interface IPointerHandlerHelper { viewDomNode: HTMLElement; @@ -34,6 +35,11 @@ export interface IPointerHandlerHelper { */ getLastRenderData(): PointerHandlerLastRenderData; + /** + * Render right now + */ + renderNow(): void; + shouldSuppressMouseDownOnViewZone(viewZoneId: string): boolean; shouldSuppressMouseDownOnWidget(widgetId: string): boolean; @@ -69,6 +75,7 @@ export class MouseHandler extends ViewEventHandler { this._context, this.viewController, this.viewHelper, + this.mouseTargetFactory, (e, testEventTarget) => this._createMouseTarget(e, testEventTarget), (e) => this._getMouseColumn(e) )); @@ -121,6 +128,19 @@ export class MouseHandler extends ViewEventHandler { this._mouseDownOperation.onPointerUp(); })); this._register(mouseEvents.onMouseDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e, capturePointerId))); + this._setupMouseWheelZoomListener(); + + this._context.addEventHandler(this); + } + + private _setupMouseWheelZoomListener(): void { + + const classifier = MouseWheelClassifier.INSTANCE; + + let prevMouseWheelTime = 0; + let gestureStartZoomLevel = EditorZoom.getZoomLevel(); + let gestureHasZoomModifiers = false; + let gestureAccumulatedDelta = 0; const onMouseWheel = (browserEvent: IMouseWheelEvent) => { this.viewController.emitMouseWheel(browserEvent); @@ -128,25 +148,50 @@ export class MouseHandler extends ViewEventHandler { if (!this._context.configuration.options.get(EditorOption.mouseWheelZoom)) { return; } + const e = new StandardWheelEvent(browserEvent); - const doMouseWheelZoom = ( + classifier.acceptStandardWheelEvent(e); + + if (classifier.isPhysicalMouseWheel()) { + if (hasMouseWheelZoomModifiers(browserEvent)) { + const zoomLevel: number = EditorZoom.getZoomLevel(); + const delta = e.deltaY > 0 ? 1 : -1; + EditorZoom.setZoomLevel(zoomLevel + delta); + e.preventDefault(); + e.stopPropagation(); + } + } else { + // we consider mousewheel events that occur within 50ms of each other to be part of the same gesture + // we don't want to consider mouse wheel events where ctrl/cmd is pressed during the inertia phase + // we also want to accumulate deltaY values from the same gesture and use that to set the zoom level + if (Date.now() - prevMouseWheelTime > 50) { + // reset if more than 50ms have passed + gestureStartZoomLevel = EditorZoom.getZoomLevel(); + gestureHasZoomModifiers = hasMouseWheelZoomModifiers(browserEvent); + gestureAccumulatedDelta = 0; + } + + prevMouseWheelTime = Date.now(); + gestureAccumulatedDelta += e.deltaY; + + if (gestureHasZoomModifiers) { + EditorZoom.setZoomLevel(gestureStartZoomLevel + gestureAccumulatedDelta / 5); + e.preventDefault(); + e.stopPropagation(); + } + } + }; + this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.MOUSE_WHEEL, onMouseWheel, { capture: true, passive: false })); + + function hasMouseWheelZoomModifiers(browserEvent: IMouseWheelEvent): boolean { + return ( platform.isMacintosh // 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) { - const zoomLevel: number = EditorZoom.getZoomLevel(); - const delta = e.deltaY > 0 ? 1 : -1; - EditorZoom.setZoomLevel(zoomLevel + delta); - e.preventDefault(); - e.stopPropagation(); - } - }; - this._register(dom.addDisposableListener(this.viewHelper.viewDomNode, dom.EventType.MOUSE_WHEEL, onMouseWheel, { capture: true, passive: false })); - - this._context.addEventHandler(this); + } } public override dispose(): void { @@ -177,10 +222,6 @@ export class MouseHandler extends ViewEventHandler { public override onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean { return false; } - public override onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { - this._mouseDownOperation.onScrollChanged(); - return false; - } // --- end event handlers public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null { @@ -220,7 +261,7 @@ export class MouseHandler extends ViewEventHandler { }); } - public _onMouseMove(e: EditorMouseEvent): void { + protected _onMouseMove(e: EditorMouseEvent): void { const targetIsWidget = this.mouseTargetFactory.mouseTargetIsWidget(e); if (!targetIsWidget) { e.preventDefault(); @@ -242,7 +283,7 @@ export class MouseHandler extends ViewEventHandler { }); } - public _onMouseLeave(e: EditorMouseEvent): void { + protected _onMouseLeave(e: EditorMouseEvent): void { if (this._mouseLeaveMonitor) { this._mouseLeaveMonitor.dispose(); this._mouseLeaveMonitor = null; @@ -254,14 +295,14 @@ export class MouseHandler extends ViewEventHandler { }); } - public _onMouseUp(e: EditorMouseEvent): void { + protected _onMouseUp(e: EditorMouseEvent): void { this.viewController.emitMouseUp({ event: e, target: this._createMouseTarget(e, true) }); } - public _onMouseDown(e: EditorMouseEvent, pointerId: number): void { + protected _onMouseDown(e: EditorMouseEvent, pointerId: number): void { const t = this._createMouseTarget(e, true); const targetIsContent = (t.type === MouseTargetType.CONTENT_TEXT || t.type === MouseTargetType.CONTENT_EMPTY); @@ -306,21 +347,18 @@ export class MouseHandler extends ViewEventHandler { }); } - public _onMouseWheel(e: IMouseWheelEvent): void { + protected _onMouseWheel(e: IMouseWheelEvent): void { this.viewController.emitMouseWheel(e); } } class MouseDownOperation extends Disposable { - private readonly _context: ViewContext; - private readonly _viewController: ViewController; - private readonly _viewHelper: IPointerHandlerHelper; private readonly _createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget; private readonly _getMouseColumn: (e: EditorMouseEvent) => number; private readonly _mouseMoveMonitor: GlobalEditorPointerMoveMonitor; - private readonly _onScrollTimeout: TimeoutTimer; + private readonly _topBottomDragScrolling: TopBottomDragScrolling; private readonly _mouseState: MouseDownState; private _currentSelection: Selection; @@ -328,21 +366,24 @@ class MouseDownOperation extends Disposable { private _lastMouseEvent: EditorMouseEvent | null; constructor( - context: ViewContext, - viewController: ViewController, - viewHelper: IPointerHandlerHelper, + private readonly _context: ViewContext, + private readonly _viewController: ViewController, + private readonly _viewHelper: IPointerHandlerHelper, + private readonly _mouseTargetFactory: MouseTargetFactory, createMouseTarget: (e: EditorMouseEvent, testEventTarget: boolean) => IMouseTarget, getMouseColumn: (e: EditorMouseEvent) => number ) { super(); - this._context = context; - this._viewController = viewController; - this._viewHelper = viewHelper; this._createMouseTarget = createMouseTarget; this._getMouseColumn = getMouseColumn; this._mouseMoveMonitor = this._register(new GlobalEditorPointerMoveMonitor(this._viewHelper.viewDomNode)); - this._onScrollTimeout = this._register(new TimeoutTimer()); + this._topBottomDragScrolling = this._register(new TopBottomDragScrolling( + this._context, + this._viewHelper, + this._mouseTargetFactory, + (position, inSelectionMode, revealType) => this._dispatchMouse(position, inSelectionMode, revealType) + )); this._mouseState = new MouseDownState(); this._currentSelection = new Selection(1, 1, 1, 1); @@ -374,7 +415,12 @@ class MouseDownOperation extends Disposable { target: position }); } else { - this._dispatchMouse(position, true); + if (position.type === MouseTargetType.OUTSIDE_EDITOR && (position.outsidePosition === 'above' || position.outsidePosition === 'below')) { + this._topBottomDragScrolling.start(position, e); + } else { + this._topBottomDragScrolling.stop(); + this._dispatchMouse(position, true, NavigationCommandRevealType.Minimal); + } } } @@ -436,7 +482,7 @@ class MouseDownOperation extends Disposable { } this._mouseState.isDragAndDrop = false; - this._dispatchMouse(position, e.shiftKey); + this._dispatchMouse(position, e.shiftKey, NavigationCommandRevealType.Minimal); if (!this._isActive) { this._isActive = true; @@ -452,7 +498,7 @@ class MouseDownOperation extends Disposable { private _stop(): void { this._isActive = false; - this._onScrollTimeout.cancel(); + this._topBottomDragScrolling.stop(); } public onHeightChanged(): void { @@ -463,27 +509,6 @@ class MouseDownOperation extends Disposable { this._mouseMoveMonitor.stopMonitoring(); } - public onScrollChanged(): void { - if (!this._isActive) { - return; - } - this._onScrollTimeout.setIfNotSet(() => { - if (!this._lastMouseEvent) { - return; - } - const position = this._findMousePosition(this._lastMouseEvent, false); - if (!position) { - // Ignoring because position is unknown - return; - } - if (this._mouseState.isDragAndDrop) { - // Ignoring because users are dragging the text - return; - } - this._dispatchMouse(position, true); - }, 10); - } - public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): void { this._currentSelection = e.selections[0]; } @@ -496,41 +521,45 @@ class MouseDownOperation extends Disposable { const mouseColumn = this._getMouseColumn(e); if (e.posy < editorContent.y) { - const verticalOffset = Math.max(viewLayout.getCurrentScrollTop() - (editorContent.y - e.posy), 0); + const outsideDistance = editorContent.y - e.posy; + const verticalOffset = Math.max(viewLayout.getCurrentScrollTop() - outsideDistance, 0); const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset); if (viewZoneData) { const newPosition = this._helpPositionJumpOverViewZone(viewZoneData); if (newPosition) { - return MouseTarget.createOutsideEditor(mouseColumn, newPosition); + return MouseTarget.createOutsideEditor(mouseColumn, newPosition, 'above', outsideDistance); } } const aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); - return MouseTarget.createOutsideEditor(mouseColumn, new Position(aboveLineNumber, 1)); + return MouseTarget.createOutsideEditor(mouseColumn, new Position(aboveLineNumber, 1), 'above', outsideDistance); } if (e.posy > editorContent.y + editorContent.height) { + const outsideDistance = e.posy - editorContent.y - editorContent.height; const verticalOffset = viewLayout.getCurrentScrollTop() + e.relativePos.y; const viewZoneData = HitTestContext.getZoneAtCoord(this._context, verticalOffset); if (viewZoneData) { const newPosition = this._helpPositionJumpOverViewZone(viewZoneData); if (newPosition) { - return MouseTarget.createOutsideEditor(mouseColumn, newPosition); + return MouseTarget.createOutsideEditor(mouseColumn, newPosition, 'below', outsideDistance); } } const belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); - return MouseTarget.createOutsideEditor(mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber))); + return MouseTarget.createOutsideEditor(mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber)), 'below', outsideDistance); } const possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + e.relativePos.y); if (e.posx < editorContent.x) { - return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, 1)); + const outsideDistance = editorContent.x - e.posx; + return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, 1), 'left', outsideDistance); } if (e.posx > editorContent.x + editorContent.width) { - return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber))); + const outsideDistance = e.posx - editorContent.x - editorContent.width; + return MouseTarget.createOutsideEditor(mouseColumn, new Position(possibleLineNumber, model.getLineMaxColumn(possibleLineNumber)), 'right', outsideDistance); } return null; @@ -574,7 +603,7 @@ class MouseDownOperation extends Disposable { return null; } - private _dispatchMouse(position: IMouseTarget, inSelectionMode: boolean): void { + private _dispatchMouse(position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType): void { if (!position.position) { return; } @@ -582,6 +611,7 @@ class MouseDownOperation extends Disposable { position: position.position, mouseColumn: position.mouseColumn, startedOnLineNumbers: this._mouseState.startedOnLineNumbers, + revealType, inSelectionMode: inSelectionMode, mouseDownCount: this._mouseState.count, @@ -598,6 +628,134 @@ class MouseDownOperation extends Disposable { } } +class TopBottomDragScrolling extends Disposable { + + private _operation: TopBottomDragScrollingOperation | null; + + constructor( + private readonly _context: ViewContext, + private readonly _viewHelper: IPointerHandlerHelper, + private readonly _mouseTargetFactory: MouseTargetFactory, + private readonly _dispatchMouse: (position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType) => void, + ) { + super(); + this._operation = null; + } + + public override dispose(): void { + super.dispose(); + this.stop(); + } + + public start(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): void { + if (this._operation) { + this._operation.setPosition(position, mouseEvent); + } else { + this._operation = new TopBottomDragScrollingOperation(this._context, this._viewHelper, this._mouseTargetFactory, this._dispatchMouse, position, mouseEvent); + } + } + + public stop(): void { + if (this._operation) { + this._operation.dispose(); + this._operation = null; + } + } +} + +class TopBottomDragScrollingOperation extends Disposable { + + private _position: IMouseTargetOutsideEditor; + private _mouseEvent: EditorMouseEvent; + private _lastTime: number; + private _animationFrameDisposable: IDisposable; + + constructor( + private readonly _context: ViewContext, + private readonly _viewHelper: IPointerHandlerHelper, + private readonly _mouseTargetFactory: MouseTargetFactory, + private readonly _dispatchMouse: (position: IMouseTarget, inSelectionMode: boolean, revealType: NavigationCommandRevealType) => void, + position: IMouseTargetOutsideEditor, + mouseEvent: EditorMouseEvent + ) { + super(); + this._position = position; + this._mouseEvent = mouseEvent; + this._lastTime = Date.now(); + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute()); + } + + public override dispose(): void { + this._animationFrameDisposable.dispose(); + } + + public setPosition(position: IMouseTargetOutsideEditor, mouseEvent: EditorMouseEvent): void { + this._position = position; + this._mouseEvent = mouseEvent; + } + + /** + * update internal state and return elapsed ms since last time + */ + private _tick(): number { + const now = Date.now(); + const elapsed = now - this._lastTime; + this._lastTime = now; + return elapsed; + } + + /** + * get the number of lines per second to auto-scroll + */ + private _getScrollSpeed(): number { + const lineHeight = this._context.configuration.options.get(EditorOption.lineHeight); + const viewportInLines = this._context.configuration.options.get(EditorOption.layoutInfo).height / lineHeight; + const outsideDistanceInLines = this._position.outsideDistance / lineHeight; + + if (outsideDistanceInLines <= 1.5) { + return Math.max(30, viewportInLines * (1 + outsideDistanceInLines)); + } + if (outsideDistanceInLines <= 3) { + return Math.max(60, viewportInLines * (2 + outsideDistanceInLines)); + } + return Math.max(200, viewportInLines * (7 + outsideDistanceInLines)); + } + + private _execute(): void { + const lineHeight = this._context.configuration.options.get(EditorOption.lineHeight); + const scrollSpeedInLines = this._getScrollSpeed(); + const elapsed = this._tick(); + const scrollInPixels = scrollSpeedInLines * (elapsed / 1000) * lineHeight; + const scrollValue = (this._position.outsidePosition === 'above' ? -scrollInPixels : scrollInPixels); + + this._context.viewModel.viewLayout.deltaScrollNow(0, scrollValue); + this._viewHelper.renderNow(); + + const viewportData = this._context.viewLayout.getLinesViewportData(); + const edgeLineNumber = (this._position.outsidePosition === 'above' ? viewportData.startLineNumber : viewportData.endLineNumber); + + // First, try to find a position that matches the horizontal position of the mouse + let mouseTarget: IMouseTarget; + { + const editorPos = createEditorPagePosition(this._viewHelper.viewDomNode); + const horizontalScrollbarHeight = this._context.configuration.options.get(EditorOption.layoutInfo).horizontalScrollbarHeight; + const pos = new PageCoordinates(this._mouseEvent.pos.x, editorPos.y + editorPos.height - horizontalScrollbarHeight - 0.1); + const relativePos = createCoordinatesRelativeToEditor(this._viewHelper.viewDomNode, editorPos, pos); + mouseTarget = this._mouseTargetFactory.createMouseTarget(this._viewHelper.getLastRenderData(), editorPos, pos, relativePos, null); + } + if (!mouseTarget.position || mouseTarget.position.lineNumber !== edgeLineNumber) { + if (this._position.outsidePosition === 'above') { + mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, 1), 'above', this._position.outsideDistance); + } else { + mouseTarget = MouseTarget.createOutsideEditor(this._position.mouseColumn, new Position(edgeLineNumber, this._context.viewModel.getLineMaxColumn(edgeLineNumber)), 'below', this._position.outsideDistance); + } + } + + this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None); + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute()); + } +} + class MouseDownState { private static readonly CLEAR_MOUSE_DOWN_COUNT_TIME = 400; // ms diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 1c265289f9..6fd42c4229 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -99,8 +99,8 @@ export class MouseTarget { public static createOverlayWidget(element: Element | null, mouseColumn: number, detail: string): IMouseTargetOverlayWidget { return { type: MouseTargetType.OVERLAY_WIDGET, element, mouseColumn, position: null, range: null, detail }; } - public static createOutsideEditor(mouseColumn: number, position: Position): IMouseTargetOutsideEditor { - return { type: MouseTargetType.OUTSIDE_EDITOR, element: null, mouseColumn, position, range: this._deduceRage(position) }; + public static createOutsideEditor(mouseColumn: number, position: Position, outsidePosition: 'above' | 'below' | 'left' | 'right', outsideDistance: number): IMouseTargetOutsideEditor { + return { type: MouseTargetType.OUTSIDE_EDITOR, element: null, mouseColumn, position, range: this._deduceRage(position), outsidePosition, outsideDistance }; } private static _typeToString(type: MouseTargetType): string { @@ -495,6 +495,16 @@ export class MouseTargetFactory { const request = new HitTestRequest(ctx, editorPos, pos, relativePos, target); try { const r = MouseTargetFactory._createMouseTarget(ctx, request, false); + + if (r.type === MouseTargetType.CONTENT_TEXT) { + // Snap to the nearest soft tab boundary if atomic soft tabs are enabled. + if (ctx.stickyTabStops && r.position !== null) { + const position = MouseTargetFactory._snapToSoftTabBoundary(r.position, ctx.viewModel); + const range = EditorRange.fromPositions(position, position).plusRange(r.range); + return request.fulfillContentText(position, range, r.detail); + } + } + // console.log(MouseTarget.toString(r)); return r; } catch (err) { @@ -789,7 +799,7 @@ export class MouseTargetFactory { const columnHorizontalOffset = visibleRange.left; - if (request.mouseContentHorizontalOffset === columnHorizontalOffset) { + if (Math.abs(request.mouseContentHorizontalOffset - columnHorizontalOffset) < 1) { return request.fulfillContentText(pos, null, { mightBeForeignElement: !!injectedText, injectedText }); } @@ -818,11 +828,13 @@ export class MouseTargetFactory { const spanNodeClientRect = spanNode.getBoundingClientRect(); const mouseIsOverSpanNode = (spanNodeClientRect.left <= mouseCoordinates.clientX && mouseCoordinates.clientX <= spanNodeClientRect.right); + let rng: EditorRange | null = null; + for (let i = 1; i < points.length; i++) { const prev = points[i - 1]; const curr = points[i]; if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) { - const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column); + rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column); // See https://github.com/microsoft/vscode/issues/152819 // Due to the use of zwj, the browser's hit test result is skewed towards the left @@ -831,16 +843,17 @@ export class MouseTargetFactory { const prevDelta = Math.abs(prev.offset - request.mouseContentHorizontalOffset); const nextDelta = Math.abs(curr.offset - request.mouseContentHorizontalOffset); - const resultPos = ( + pos = ( prevDelta < nextDelta ? new Position(lineNumber, prev.column) : new Position(lineNumber, curr.column) ); - return request.fulfillContentText(resultPos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); + break; } } - return request.fulfillContentText(pos, null, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); + + return request.fulfillContentText(pos, rng, { mightBeForeignElement: !mouseIsOverSpanNode || !!injectedText, injectedText }); } /** @@ -851,22 +864,31 @@ export class MouseTargetFactory { // In Chrome, especially on Linux it is possible to click between lines, // so try to adjust the `hity` below so that it lands in the center of a line const lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset); - const lineVerticalOffset = ctx.getVerticalOffsetForLineNumber(lineNumber); - const lineCenteredVerticalOffset = lineVerticalOffset + Math.floor(ctx.lineHeight / 2); - let adjustedPageY = request.pos.y + (lineCenteredVerticalOffset - request.mouseVerticalOffset); + const lineStartVerticalOffset = ctx.getVerticalOffsetForLineNumber(lineNumber); + const lineEndVerticalOffset = lineStartVerticalOffset + ctx.lineHeight; - if (adjustedPageY <= request.editorPos.y) { - adjustedPageY = request.editorPos.y + 1; - } - if (adjustedPageY >= request.editorPos.y + request.editorPos.height) { - adjustedPageY = request.editorPos.y + request.editorPos.height - 1; - } + const isBelowLastLine = ( + lineNumber === ctx.viewModel.getLineCount() + && request.mouseVerticalOffset > lineEndVerticalOffset + ); - const adjustedPage = new PageCoordinates(request.pos.x, adjustedPageY); + if (!isBelowLastLine) { + const lineCenteredVerticalOffset = Math.floor((lineStartVerticalOffset + lineEndVerticalOffset) / 2); + let adjustedPageY = request.pos.y + (lineCenteredVerticalOffset - request.mouseVerticalOffset); - const r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates()); - if (r.type === HitTestResultType.Content) { - return r; + if (adjustedPageY <= request.editorPos.y) { + adjustedPageY = request.editorPos.y + 1; + } + if (adjustedPageY >= request.editorPos.y + request.editorPos.height) { + adjustedPageY = request.editorPos.y + request.editorPos.height - 1; + } + + const adjustedPage = new PageCoordinates(request.pos.x, adjustedPageY); + + const r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates()); + if (r.type === HitTestResultType.Content) { + return r; + } } // Also try to hit test without the adjustment (for the edge cases that we are near the top or bottom) @@ -990,15 +1012,11 @@ export class MouseTargetFactory { result = new ContentHitTestResult(normalizedPosition, result.spanNode, injectedText); } } - // Snap to the nearest soft tab boundary if atomic soft tabs are enabled. - if (result.type === HitTestResultType.Content && ctx.stickyTabStops) { - result = new ContentHitTestResult(this._snapToSoftTabBoundary(result.position, ctx.viewModel), result.spanNode, result.injectedText); - } return result; } } -export function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y: number): Range { +function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y: number): Range { const range = document.createRange(); // Get the element under the point @@ -1015,8 +1033,14 @@ export function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y: // Grab its rect const rect = el.getBoundingClientRect(); - // And its font - const font = window.getComputedStyle(el, null).getPropertyValue('font'); + // And its font (the computed shorthand font property might be empty, see #3217) + const fontStyle = window.getComputedStyle(el, null).getPropertyValue('font-style'); + const fontVariant = window.getComputedStyle(el, null).getPropertyValue('font-variant'); + const fontWeight = window.getComputedStyle(el, null).getPropertyValue('font-weight'); + const fontSize = window.getComputedStyle(el, null).getPropertyValue('font-size'); + const lineHeight = window.getComputedStyle(el, null).getPropertyValue('line-height'); + const fontFamily = window.getComputedStyle(el, null).getPropertyValue('font-family'); + const font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`; // And also its txt content const text = (el as any).innerText; diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index e52b61d5fa..6e200b9690 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -14,6 +14,7 @@ import { ViewController } from 'vs/editor/browser/view/viewController'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { TextAreaSyntethicEvents } from 'vs/editor/browser/controller/textAreaInput'; +import { NavigationCommandRevealType } from 'vs/editor/browser/coreCommands'; /** * Currently only tested on iOS 13/ iPadOS. @@ -66,6 +67,7 @@ export class PointerEventHandler extends MouseHandler { position: target.position, mouseColumn: target.position.column, startedOnLineNumbers: false, + revealType: NavigationCommandRevealType.Minimal, mouseDownCount: event.tapCount, inSelectionMode: false, altKey: false, @@ -86,7 +88,7 @@ export class PointerEventHandler extends MouseHandler { } } - public override _onMouseDown(e: EditorMouseEvent, pointerId: number): void { + protected override _onMouseDown(e: EditorMouseEvent, pointerId: number): void { if ((e.browserEvent as any).pointerType === 'touch') { return; } @@ -120,7 +122,7 @@ class TouchHandler extends MouseHandler { event.initEvent(TextAreaSyntethicEvents.Tap, false, true); this.viewHelper.dispatchTextAreaEvent(event); - this.viewController.moveTo(target.position); + this.viewController.moveTo(target.position, NavigationCommandRevealType.Minimal); } } diff --git a/src/vs/editor/browser/controller/textAreaHandler.css b/src/vs/editor/browser/controller/textAreaHandler.css index 86ffbb922e..7585c1c2fd 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.css +++ b/src/vs/editor/browser/controller/textAreaHandler.css @@ -15,6 +15,7 @@ overflow: hidden; color: transparent; background-color: transparent; + z-index: -10; } /*.monaco-editor .inputarea { position: fixed !important; @@ -28,7 +29,10 @@ background: white !important; line-height: 15px !important; font-size: 14px !important; + z-index: 10 !important; }*/ .monaco-editor .inputarea.ime-input { z-index: 10; + caret-color: var(--vscode-editorCursor-foreground); + color: var(--vscode-editor-foreground); } diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index c352bf4985..4cec87c0fd 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -18,7 +18,7 @@ import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/v import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/lineNumbers'; import { Margin } from 'vs/editor/browser/viewParts/margin/margin'; import { RenderLineNumbersType, EditorOption, IComputedEditorOptions, EditorOptions } from 'vs/editor/common/config/editorOptions'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { WordCharacterClass, getMapForWordSeparators } from 'vs/editor/common/core/wordCharacterClassifier'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; @@ -34,6 +34,7 @@ import { MOUSE_CURSOR_TEXT_CSS_CLASS_NAME } from 'vs/base/browser/ui/mouseCursor import { TokenizationRegistry } from 'vs/editor/common/languages'; import { ColorId, ITokenPresentation } from 'vs/editor/common/encodedTokenAttributes'; import { Color } from 'vs/base/common/color'; +import { IME } from 'vs/base/common/ime'; export interface IVisibleRangeProvider { visibleRangeForPosition(position: Position): HorizontalPosition | null; @@ -112,10 +113,12 @@ export class TextAreaHandler extends ViewPart { private _accessibilitySupport!: AccessibilitySupport; private _accessibilityPageSize!: number; + private _textAreaWrapping!: boolean; + private _textAreaWidth!: number; private _contentLeft: number; private _contentWidth: number; private _contentHeight: number; - private _fontInfo: BareFontInfo; + private _fontInfo: FontInfo; private _lineHeight: number; private _emptySelectionClipboard: boolean; private _copyWithSyntaxHighlighting: boolean; @@ -166,7 +169,9 @@ export class TextAreaHandler extends ViewPart { this.textArea = createFastDomNode(document.createElement('textarea')); PartFingerprints.write(this.textArea, PartFingerprint.TextArea); this.textArea.setClassName(`inputarea ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`); - this.textArea.setAttribute('wrap', 'off'); + this.textArea.setAttribute('wrap', this._textAreaWrapping && !this._visibleTextArea ? 'on' : 'off'); + const { tabSize } = this._context.viewModel.model.getOptions(); + this.textArea.domNode.style.tabSize = `${tabSize * this._fontInfo.spaceWidth}px`; this.textArea.setAttribute('autocorrect', 'off'); this.textArea.setAttribute('autocapitalize', 'off'); this.textArea.setAttribute('autocomplete', 'off'); @@ -179,9 +184,7 @@ export class TextAreaHandler extends ViewPart { this.textArea.setAttribute('aria-haspopup', 'false'); this.textArea.setAttribute('aria-autocomplete', 'both'); - if (options.get(EditorOption.domReadOnly) && options.get(EditorOption.readOnly)) { - this.textArea.setAttribute('readonly', 'true'); - } + this._ensureReadOnlyAttribute(); this.textAreaCover = createFastDomNode(document.createElement('div')); this.textAreaCover.setPosition('absolute'); @@ -195,6 +198,12 @@ export class TextAreaHandler extends ViewPart { }, getValueInRange: (range: Range, eol: EndOfLinePreference): string => { return this._context.viewModel.getValueInRange(range, eol); + }, + getValueLengthInRange: (range: Range, eol: EndOfLinePreference): number => { + return this._context.viewModel.getValueLengthInRange(range, eol); + }, + modifyPosition: (position: Position, offset: number): Position => { + return this._context.viewModel.modifyPosition(position, offset); } }; @@ -224,7 +233,7 @@ export class TextAreaHandler extends ViewPart { mode }; }, - getScreenReaderContent: (currentState: TextAreaState): TextAreaState => { + getScreenReaderContent: (): TextAreaState => { if (this._accessibilitySupport === AccessibilitySupport.Disabled) { // We know for a fact that a screen reader is not attached // On OSX, we write the character before the cursor to allow for "long-press" composition @@ -239,16 +248,25 @@ export class TextAreaHandler extends ViewPart { } if (textBefore.length > 0) { - return new TextAreaState(textBefore, textBefore.length, textBefore.length, position, position); + return new TextAreaState(textBefore, textBefore.length, textBefore.length, Range.fromPositions(position), 0); } } + // on macOS, write current selection into textarea will allow system text services pick selected text, + // but we still want to limit the amount of text given Chromium handles very poorly text even of a few + // thousand chars + // (https://github.com/microsoft/vscode/issues/27799) + const LIMIT_CHARS = 500; + if (platform.isMacintosh && !selection.isEmpty() && simpleModel.getValueLengthInRange(selection, EndOfLinePreference.TextDefined) < LIMIT_CHARS) { + const text = simpleModel.getValueInRange(selection, EndOfLinePreference.TextDefined); + return new TextAreaState(text, 0, text.length, selection, 0); + } // on Safari, document.execCommand('cut') and document.execCommand('copy') will just not work // if the textarea has no content selected. So if there is an editor selection, ensure something // is selected in the textarea. if (browser.isSafari && !selection.isEmpty()) { const placeholderText = 'vscode-placeholder'; - return new TextAreaState(placeholderText, 0, placeholderText.length, null, null); + return new TextAreaState(placeholderText, 0, placeholderText.length, null, undefined); } return TextAreaState.EMPTY; @@ -264,13 +282,13 @@ export class TextAreaHandler extends ViewPart { const position = selection.getStartPosition(); const [wordAtPosition, positionOffsetInWord] = this._getAndroidWordAtPosition(position); if (wordAtPosition.length > 0) { - return new TextAreaState(wordAtPosition, positionOffsetInWord, positionOffsetInWord, position, position); + return new TextAreaState(wordAtPosition, positionOffsetInWord, positionOffsetInWord, Range.fromPositions(position), 0); } } return TextAreaState.EMPTY; } - return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown); + return PagedScreenReaderStrategy.fromEditorSelection(simpleModel, this._selections[0], this._accessibilityPageSize, this._accessibilitySupport === AccessibilitySupport.Unknown); }, deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => { @@ -361,7 +379,8 @@ export class TextAreaHandler extends ViewPart { const visibleBeforeCharCount = Math.min(startModelPosition.column - 1, desiredVisibleBeforeCharCount); const distanceToModelLineStart = startModelPosition.column - 1 - visibleBeforeCharCount; const hiddenLineTextBefore = lineTextBeforeSelection.substring(0, lineTextBeforeSelection.length - visibleBeforeCharCount); - const widthOfHiddenTextBefore = measureText(hiddenLineTextBefore, this._fontInfo); + const { tabSize } = this._context.viewModel.model.getOptions(); + const widthOfHiddenTextBefore = measureText(hiddenLineTextBefore, this._fontInfo, tabSize); return { distanceToModelLineStart, widthOfHiddenTextBefore }; })(); @@ -398,6 +417,9 @@ export class TextAreaHandler extends ViewPart { distanceToModelLineEnd, ); + // We turn off wrapping if the - -
- -
Press 'run' to execute code...
-
...write your results into #results...
-
- - - diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem.txt deleted file mode 100644 index 9d348ac090..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem.txt +++ /dev/null @@ -1,283 +0,0 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. - -Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. - -Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. - -Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. - -Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. - -Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. - -Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. - -Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. - -Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. - -Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. - -Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. - -Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. - -Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. - -Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. - -Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. - -Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. - -Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. - -Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. - -Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. - -Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. - -Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. - -Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. - -Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. - -Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. - -Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. - -Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. - -Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. - -In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. - -Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. - -Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. - -Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. - -Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. - -Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. - -Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. - -Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. - -Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. - -Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. - -Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. - -Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. - -Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. - -Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. - -Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. - -Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. - -Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. - -Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. - -Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. - -Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. - -Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. - -Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. - -Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. - -Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. - -Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. - -Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. - -Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. - -Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. - -Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. - -Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. - -Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. - -Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. - -Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. - -Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. - -Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. - -Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. - -Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. - -Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. - -Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. - -Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. - -Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. - -Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. - -Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. - -Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. - -Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. - -Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. - -Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. - -Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. - -Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. - -Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. - -Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. - -Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. - -Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. - -Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. - -Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - -Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. - -Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. - -Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. - -Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. - -Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. - -Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. - -Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. - -Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. - -Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. - -Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. - -Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. - -In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. - -Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. - -Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. - -Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. - -Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. - -Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. - -In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. - -Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. - -Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. - -Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. - -Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. - -Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. - -Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. - -Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. - -Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. - -Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. - -Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. - -Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. - -Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. - -Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. - -Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. - -Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. - -Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. - -Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. - -Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. - -Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. - -Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. - -Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. - -Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. - -Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. - -Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. - -Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. - -Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. - -Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. - -Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. - -Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. - -Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. - -Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. - -Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. - -Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. - -In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. - -Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. - -Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. - -Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. - -Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_big5.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_big5.txt deleted file mode 100644 index 91b79941f1..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_big5.txt +++ /dev/null @@ -1,283 +0,0 @@ -¤¤¤åabc Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. - -Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. - -Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. - -Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. - -Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. - -Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. - -Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. - -Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. - -Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. - -Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. - -Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. - -Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. - -Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. - -Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. - -Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. - -Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. - -Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. - -Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. - -Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. - -Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. - -Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. - -Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. - -Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. - -Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. - -Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. - -Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. - -Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. - -In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. - -Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. - -Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. - -Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. - -Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. - -Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. - -Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. - -Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. - -Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. - -Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. - -Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. - -Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. - -Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. - -Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. - -Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. - -Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. - -Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. - -Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. - -Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. - -Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. - -Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. - -Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. - -Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. - -Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. - -Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. - -Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. - -Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. - -Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. - -Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. - -Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. - -Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. - -Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. - -Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. - -Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. - -Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. - -Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. - -Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. - -Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. - -Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. - -Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. - -Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. - -Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. - -Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. - -Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. - -Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. - -Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. - -Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. - -Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. - -Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. - -Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. - -Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. - -Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. - -Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. - -Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. - -Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - -Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. - -Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. - -Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. - -Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. - -Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. - -Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. - -Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. - -Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. - -Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. - -Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. - -Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. - -In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. - -Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. - -Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. - -Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. - -Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. - -Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. - -In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. - -Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. - -Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. - -Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. - -Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. - -Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. - -Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. - -Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. - -Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. - -Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. - -Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. - -Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. - -Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. - -Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. - -Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. - -Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. - -Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. - -Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. - -Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. - -Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. - -Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. - -Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. - -Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. - -Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. - -Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. - -Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. - -Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. - -Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. - -Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. - -Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. - -Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. - -Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. - -Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. - -Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. - -In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. - -Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. - -Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. - -Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. - -¤¤¤åabc Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_cp1252.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_cp1252.txt deleted file mode 100644 index f56b7cf1fc..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_cp1252.txt +++ /dev/null @@ -1,283 +0,0 @@ -öäüß Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. - -Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. - -Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. - -Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. - -Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. - -Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. - -Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. - -Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. - -Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. - -Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. - -Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. - -Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. - -Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. - -Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. - -Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. - -Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. - -Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. - -Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. - -Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. - -Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. - -Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. - -Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. - -Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. - -Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. - -Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. - -Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. - -Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. - -In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. - -Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. - -Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. - -Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. - -Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. - -Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. - -Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. - -Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. - -Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. - -Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. - -Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. - -Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. - -Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. - -Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. - -Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. - -Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. - -Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. - -Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. - -Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. - -Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. - -Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. - -Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. - -Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. - -Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. - -Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. - -Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. - -Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. - -Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. - -Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. - -Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. - -Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. - -Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. - -Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. - -Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. - -Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. - -Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. - -Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. - -Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. - -Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. - -Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. - -Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. - -Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. - -Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. - -Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. - -Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. - -Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. - -Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. - -Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. - -Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. - -Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. - -Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. - -Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. - -Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. - -Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. - -Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - -Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. - -Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. - -Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. - -Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. - -Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. - -Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. - -Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. - -Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. - -Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. - -Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. - -Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. - -In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. - -Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. - -Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. - -Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. - -Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. - -Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. - -In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. - -Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. - -Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. - -Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. - -Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. - -Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. - -Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. - -Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. - -Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. - -Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. - -Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. - -Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. - -Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. - -Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. - -Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. - -Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. - -Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. - -Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. - -Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. - -Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. - -Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. - -Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. - -Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. - -Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. - -Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. - -Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. - -Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. - -Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. - -Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. - -Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. - -Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. - -Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. - -Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. - -Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. - -In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. - -Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. - -Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. - -Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. - -öäüß Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_cp866.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_cp866.txt deleted file mode 100644 index b11625814d..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_cp866.txt +++ /dev/null @@ -1,283 +0,0 @@ -€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯àáâãäåæçèéêëìíîï Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. - -Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. - -Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. - -Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. - -Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. - -Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. - -Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. - -Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. - -Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. - -Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. - -Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. - -Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. - -Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. - -Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. - -Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. - -Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. - -Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. - -Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. - -Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. - -Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. - -Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. - -Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. - -Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. - -Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. - -Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. - -Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. - -Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. - -In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. - -Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. - -Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. - -Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. - -Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. - -Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. - -Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. - -Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. - -Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. - -Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. - -Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. - -Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. - -Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. - -Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. - -Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. - -Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. - -Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. - -Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. - -Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. - -Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. - -Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. - -Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. - -Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. - -Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. - -Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. - -Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. - -Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. - -Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. - -Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. - -Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. - -Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. - -Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. - -Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. - -Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. - -Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. - -Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. - -Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. - -Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. - -Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. - -Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. - -Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. - -Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. - -Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. - -Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. - -Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. - -Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. - -Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. - -Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. - -Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. - -Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. - -Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. - -Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. - -Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. - -Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. - -Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - -Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. - -Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. - -Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. - -Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. - -Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. - -Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. - -Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. - -Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. - -Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. - -Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. - -Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. - -In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. - -Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. - -Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. - -Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. - -Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. - -Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. - -In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. - -Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. - -Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. - -Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. - -Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. - -Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. - -Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. - -Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. - -Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. - -Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. - -Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. - -Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. - -Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. - -Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. - -Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. - -Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. - -Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. - -Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. - -Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. - -Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. - -Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. - -Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. - -Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. - -Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. - -Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. - -Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. - -Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. - -Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. - -Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. - -Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. - -Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. - -Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. - -Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. - -Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. - -In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. - -Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. - -Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. - -Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. - -€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯àáâãäåæçèéêëìíîï Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_gbk.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_gbk.txt deleted file mode 100644 index 2e1de22414..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_gbk.txt +++ /dev/null @@ -1,283 +0,0 @@ -Öйúabc Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. - -Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. - -Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. - -Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. - -Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. - -Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. - -Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. - -Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. - -Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. - -Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. - -Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. - -Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. - -Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. - -Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. - -Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. - -Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. - -Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. - -Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. - -Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. - -Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. - -Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. - -Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. - -Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. - -Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. - -Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. - -Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. - -Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. - -In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. - -Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. - -Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. - -Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. - -Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. - -Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. - -Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. - -Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. - -Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. - -Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. - -Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. - -Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. - -Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. - -Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. - -Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. - -Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. - -Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. - -Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. - -Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. - -Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. - -Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. - -Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. - -Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. - -Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. - -Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. - -Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. - -Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. - -Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. - -Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. - -Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. - -Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. - -Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. - -Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. - -Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. - -Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. - -Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. - -Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. - -Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. - -Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. - -Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. - -Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. - -Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. - -Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. - -Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. - -Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. - -Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. - -Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. - -Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. - -Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. - -Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. - -Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. - -Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. - -Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. - -Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. - -Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - -Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. - -Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. - -Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. - -Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. - -Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. - -Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. - -Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. - -Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. - -Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. - -Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. - -Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. - -In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. - -Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. - -Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. - -Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. - -Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. - -Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. - -In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. - -Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. - -Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. - -Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. - -Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. - -Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. - -Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. - -Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. - -Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. - -Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. - -Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. - -Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. - -Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. - -Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. - -Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. - -Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. - -Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. - -Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. - -Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. - -Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. - -Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. - -Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. - -Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. - -Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. - -Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. - -Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. - -Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. - -Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. - -Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. - -Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. - -Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. - -Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. - -Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. - -Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. - -In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. - -Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. - -Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. - -Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. - -Öйúabc Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_shiftjis.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_shiftjis.txt deleted file mode 100644 index 3e106d9df4..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_shiftjis.txt +++ /dev/null @@ -1,283 +0,0 @@ -’†•¶abc Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. - -Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. - -Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. - -Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. - -Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. - -Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. - -Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. - -Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. - -Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. - -Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. - -Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. - -Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. - -Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. - -Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. - -Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. - -Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. - -Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. - -Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. - -Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. - -Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. - -Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. - -Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. - -Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. - -Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. - -Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. - -Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. - -Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. - -In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. - -Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. - -Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. - -Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. - -Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. - -Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. - -Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. - -Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. - -Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. - -Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. - -Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. - -Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. - -Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. - -Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. - -Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. - -Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. - -Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. - -Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. - -Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. - -Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. - -Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. - -Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. - -Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. - -Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. - -Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. - -Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. - -Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. - -Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. - -Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. - -Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. - -Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. - -Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. - -Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. - -Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. - -Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. - -Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. - -Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. - -Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. - -Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. - -Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. - -Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. - -Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. - -Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. - -Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. - -Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. - -Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. - -Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. - -Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. - -Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. - -Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. - -Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. - -Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. - -Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. - -Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. - -Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - -Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. - -Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. - -Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. - -Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. - -Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. - -Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. - -Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. - -Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. - -Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. - -Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. - -Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. - -In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. - -Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. - -Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. - -Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. - -Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. - -Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. - -In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. - -Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. - -Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. - -Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. - -Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. - -Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. - -Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. - -Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. - -Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. - -Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. - -Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. - -Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. - -Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. - -Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. - -Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. - -Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. - -Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. - -Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. - -Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. - -Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. - -Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. - -Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. - -Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. - -Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. - -Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. - -Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. - -Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. - -Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. - -Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. - -Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. - -Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. - -Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. - -Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. - -Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. - -In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. - -Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. - -Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. - -Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. - -’†•¶abc Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf16be.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf16be.txt deleted file mode 100644 index 468eee8000..0000000000 Binary files a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf16be.txt and /dev/null differ diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf16le.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf16le.txt deleted file mode 100644 index b9a7dff430..0000000000 Binary files a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf16le.txt and /dev/null differ diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf8bom.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf8bom.txt deleted file mode 100644 index edd30bae3c..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/lorem_utf8bom.txt +++ /dev/null @@ -1,283 +0,0 @@ -öäüß Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur vulputate, ipsum quis interdum fermentum, lorem sem fermentum eros, vitae auctor neque lacus in nisi. Suspendisse potenti. Maecenas et scelerisque elit, in tincidunt quam. Sed eu tincidunt quam. Nullam justo ex, imperdiet a imperdiet et, fermentum sit amet eros. Aenean quis tempus sem. Pellentesque accumsan magna mi, ut mollis velit sagittis id. Etiam quis ipsum orci. Fusce purus ante, accumsan a lobortis at, venenatis eu nisl. Praesent ornare sed ante placerat accumsan. Suspendisse tempus dignissim fermentum. Nunc a leo ac lacus sodales iaculis eu vitae mi. In feugiat ante at massa finibus cursus. Suspendisse posuere fringilla ornare. Mauris elementum ac quam id convallis. Vestibulum non elit quis urna volutpat aliquam a eu lacus. - -Aliquam vestibulum imperdiet neque, suscipit aliquam elit ultrices bibendum. Suspendisse ultrices pulvinar cursus. Morbi risus nisi, cursus consequat rutrum vitae, molestie sed dui. Fusce posuere, augue quis dignissim aliquam, nisi ipsum porttitor ante, quis fringilla nisl turpis ac nisi. Nulla varius enim eget lorem vehicula gravida. Donec finibus malesuada leo nec semper. Proin ac enim eros. Vivamus non tincidunt nisi, vel tristique lorem. - -Nunc consequat ex id eros dignissim, id rutrum risus laoreet. Sed euismod non erat eu ultricies. Etiam vehicula gravida lacus ut porta. Vestibulum eu eros quis nunc aliquet luctus. Cras quis semper ligula. Nullam gravida vehicula quam sed porta. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In porta cursus vulputate. Quisque porta a nisi eget cursus. Aliquam risus leo, luctus ac magna in, efficitur cursus magna. In condimentum non mi id semper. Donec interdum ante eget commodo maximus. - -Vivamus sit amet vestibulum lectus. Fusce tincidunt mi sapien, dictum sollicitudin diam vulputate in. Integer fringilla consequat mollis. Cras aliquet consequat felis eget feugiat. Nunc tempor cursus arcu, vitae ornare nunc varius et. Vestibulum et tortor vel ante viverra porttitor. Nam at tortor ullamcorper, facilisis augue quis, tristique erat. Aenean ut euismod nibh. Quisque eu tincidunt est, nec euismod eros. - -Proin vehicula nibh non viverra egestas. Phasellus sem dolor, ultricies ac sagittis tristique, lacinia a purus. Vestibulum in ante eros. Pellentesque lacus nulla, tristique vitae interdum vel, malesuada ac diam. Aenean bibendum posuere turpis in accumsan. Ut est nulla, ullamcorper quis turpis at, viverra sagittis mauris. Sed in interdum purus. Praesent scelerisque nibh eget sem euismod, ut imperdiet mi venenatis. Vivamus pulvinar orci sed dapibus auctor. Nulla facilisi. Vestibulum tincidunt erat nec porttitor egestas. Mauris quis risus ante. Nulla facilisi. - -Aliquam ullamcorper ornare lobortis. Phasellus quis sem et ipsum mollis malesuada sed in ex. Ut aliquam ex eget metus finibus maximus. Proin suscipit mauris eu nibh lacinia, quis feugiat dui dapibus. Nam sed libero est. Aenean vulputate orci sit amet diam faucibus, eu sagittis sapien volutpat. Nam imperdiet felis turpis, at pretium odio pulvinar in. Sed vestibulum id eros nec ultricies. Sed quis aliquam tortor, vitae ullamcorper tellus. Donec egestas laoreet eros, id suscipit est rutrum nec. Sed auctor nulla eget metus aliquam, ut condimentum enim elementum. - -Aliquam suscipit non turpis sit amet bibendum. Fusce velit ligula, euismod et maximus at, luctus sed neque. Quisque pretium, nisl at ullamcorper finibus, lectus leo mattis sapien, vel euismod mauris diam ullamcorper ex. Nulla ut risus finibus, lacinia ligula at, auctor erat. Mauris consectetur sagittis ligula vel dapibus. Nullam libero libero, lobortis aliquam libero vel, venenatis ultricies leo. Duis porttitor, nibh congue fermentum posuere, erat libero pulvinar tortor, a pellentesque nunc ipsum vel sem. Nullam volutpat, eros sit amet facilisis consectetur, ipsum est vehicula massa, non vestibulum neque elit in mauris. Nunc hendrerit ipsum non enim bibendum, vitae rhoncus mi egestas. Etiam ullamcorper massa vel nisl sagittis, nec bibendum arcu malesuada. Aenean aliquet turpis justo, a consectetur arcu mollis convallis. Etiam tellus ipsum, ultricies vitae lorem et, ornare facilisis orci. Praesent fringilla justo urna, vel mollis neque pulvinar vestibulum. - -Donec non iaculis erat. Aliquam et mi sed nunc pulvinar ultricies in ut ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Praesent feugiat lacus ac dignissim semper. Phasellus vitae quam nisi. Morbi vel diam ultricies risus lobortis ornare. Fusce maximus et ligula quis iaculis. Sed congue ex eget felis convallis, sit amet hendrerit elit tempor. Donec vehicula blandit ante eget commodo. Vestibulum eleifend diam at feugiat euismod. Etiam magna tellus, dignissim eget fermentum vel, vestibulum vitae mauris. Nam accumsan et erat id sagittis. Donec lacinia, odio ut ornare ultricies, dolor velit accumsan tortor, non finibus erat tellus quis ligula. Nunc quis metus in leo volutpat ornare vulputate eu nisl. - -Donec quis viverra ex. Nullam id feugiat mauris, eu fringilla nulla. Vestibulum id maximus elit. Cras elementum elit sed felis lobortis, eget sagittis nisi hendrerit. Vivamus vitae elit neque. Donec vulputate lacus ut libero ultrices accumsan. Vivamus accumsan nulla orci, in dignissim est laoreet sagittis. Proin at commodo velit. Curabitur in velit felis. Aliquam erat volutpat. Sed consequat, nulla et cursus sodales, nisi lacus mattis risus, quis eleifend erat ex nec turpis. Sed suscipit ultrices lorem in hendrerit. - -Morbi vitae lacus nec libero ornare tempus eu et diam. Suspendisse magna ipsum, fermentum vel odio quis, molestie aliquam urna. Fusce mollis turpis a eros accumsan porttitor. Pellentesque rhoncus dolor sit amet magna rutrum, et dapibus justo tempor. Sed purus nisi, maximus vitae fringilla eu, molestie nec urna. Fusce malesuada finibus pretium. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec sed aliquet eros. Pellentesque luctus diam ante, eget euismod nisl aliquet eu. Sed accumsan elit purus, tempor varius ligula tempus nec. Curabitur ornare leo suscipit suscipit fermentum. Morbi eget nulla est. Maecenas faucibus interdum tristique. - -Etiam ut elit eros. Nulla pharetra suscipit molestie. Nulla facilisis bibendum nisl non molestie. Curabitur turpis lectus, facilisis vel diam non, vulputate ultrices mauris. Aenean placerat aliquam convallis. Suspendisse sed scelerisque tellus. Vivamus lacinia neque eget risus cursus suscipit. Proin consequat dolor vel neque tempor, eu aliquam sem scelerisque. Duis non eros a purus malesuada pharetra non et nulla. Suspendisse potenti. Mauris libero eros, finibus vel nulla id, sagittis dapibus ante. Proin iaculis sed nunc et cursus. - -Quisque accumsan lorem sit amet lorem aliquet euismod. Curabitur fermentum rutrum posuere. Etiam ultricies, sem id pellentesque suscipit, urna magna lacinia eros, quis efficitur risus nisl at lacus. Nulla quis lacus tortor. Mauris placerat ex in dolor tincidunt, vel aliquet nisi pretium. Cras iaculis risus vitae pellentesque aliquet. Quisque a enim imperdiet, ullamcorper arcu vitae, rutrum risus. Nullam consectetur libero at felis fringilla, nec congue nibh dignissim. Nam et lobortis felis, eu pellentesque ligula. Aenean facilisis, ligula non imperdiet maximus, massa orci gravida sapien, at sagittis lacus nisl in lacus. Nulla quis mauris luctus, scelerisque felis consequat, tempus risus. Fusce auctor nisl non nulla luctus molestie. Maecenas sapien nisl, auctor non dolor et, iaculis scelerisque lorem. Suspendisse egestas enim aliquet, accumsan mauris nec, posuere quam. Nulla iaculis dui dui, sit amet vestibulum erat ultricies ac. - -Cras eget dolor erat. Proin at nisl ut leo consectetur ultricies vel ut arcu. Nulla in felis malesuada, ullamcorper tortor et, convallis massa. Nunc urna justo, ornare in nibh vitae, hendrerit condimentum libero. Etiam vitae libero in purus venenatis fringilla. Nullam velit nulla, consequat ut turpis non, egestas hendrerit nibh. Duis tortor turpis, interdum non ante ac, cursus accumsan lectus. Cras pharetra bibendum augue quis dictum. Sed euismod vestibulum justo. Proin porta lobortis purus. Duis venenatis diam tortor, sit amet condimentum eros rhoncus a. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc at magna nec diam lobortis efficitur sit amet ut lacus. Nulla quis orci tortor. Pellentesque tempus velit a odio finibus porta. - -Proin feugiat mauris a tellus scelerisque convallis. Maecenas libero magna, blandit nec ultrices id, congue vel mi. Aliquam lacinia, quam vel condimentum convallis, tortor turpis aliquam odio, sed blandit libero lacus et eros. In eleifend iaculis magna ac finibus. Praesent auctor facilisis tellus in congue. Sed molestie lobortis dictum. Nam quis dignissim augue, vel euismod lorem. Curabitur posuere dapibus luctus. Donec ultricies dictum lectus, quis blandit arcu commodo ac. Aenean tincidunt ligula in nunc imperdiet dignissim. Curabitur egestas sollicitudin sapien ut semper. Aenean nec dignissim lacus. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec aliquam dictum vehicula. Donec tortor est, volutpat non nisi nec, varius gravida ex. Nunc vel tristique nunc, vitae mattis nisi. Nunc nec luctus ex, vitae tincidunt lectus. In hac habitasse platea dictumst. Curabitur lobortis ex eget tincidunt tempor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut a vehicula mi. - -Fusce eu libero finibus, interdum nulla a, placerat neque. Cras bibendum tempor libero nec feugiat. Cras ut sodales eros. Proin viverra, massa sit amet viverra egestas, neque nisl porta ex, sit amet hendrerit libero ligula vel urna. Mauris suscipit lacus id justo rhoncus suscipit. Etiam vel libero tellus. Maecenas non diam molestie, condimentum tellus a, bibendum enim. Mauris aliquet imperdiet tellus, eget sagittis dolor. Sed blandit in neque et luctus. Cras elementum sagittis nunc, vel mollis lorem euismod et. Donec posuere at lacus eget suscipit. - -Nulla nunc mi, pretium non massa vel, tempor semper magna. Nunc a leo pulvinar, tincidunt nunc at, dignissim mi. Aliquam erat volutpat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut viverra nulla a nisl finibus, at hendrerit ligula ullamcorper. Donec a lorem semper, tempor magna et, lobortis libero. Mauris id sapien leo. Donec dignissim, quam vitae porttitor dignissim, quam justo mattis dui, vel consequat odio elit quis orci. Etiam nec pretium neque, sit amet pretium orci. Duis ac tortor venenatis, feugiat purus non, feugiat nunc. Proin scelerisque nisl in turpis aliquam vulputate. - -Praesent sed est semper, fringilla lorem vitae, tincidunt nibh. Cras eros metus, auctor at mauris sit amet, sodales semper orci. Nunc a ornare ex. Curabitur bibendum arcu congue urna vulputate egestas. Vestibulum finibus id risus et accumsan. Aenean ut volutpat tellus. Aenean tincidunt malesuada urna sit amet vestibulum. Mauris vel tellus dictum, varius lacus quis, dictum arcu. - -Aenean quis metus eu erat feugiat cursus vel at ligula. Proin dapibus sodales urna, id euismod lectus tempus id. Pellentesque ex ligula, convallis et erat vel, vulputate condimentum nisl. Pellentesque pharetra nulla quis massa eleifend hendrerit. Praesent sed massa ipsum. Maecenas vehicula dolor massa, id sodales urna faucibus et. Mauris ac quam non massa tincidunt feugiat et at lacus. Fusce libero massa, vulputate vel scelerisque non, mollis in leo. Ut sit amet ultricies odio. Suspendisse in sapien viverra, facilisis purus ut, pretium libero. - -Vivamus tristique pharetra molestie. Nam a volutpat purus. Praesent consequat gravida nisi, ac blandit nisi suscipit ut. Quisque posuere, ligula a ultrices laoreet, ligula nunc vulputate libero, ut rutrum erat odio tincidunt justo. Sed vitae leo at leo fringilla bibendum. Vestibulum ut augue nec dolor auctor accumsan. Praesent laoreet id eros pulvinar commodo. Suspendisse potenti. Ut pharetra, mauris vitae blandit fringilla, odio ante tincidunt lorem, sit amet tempor metus diam ut turpis. - -Praesent quis egestas arcu. Nullam at porta arcu. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi vulputate ligula malesuada ligula luctus, vulputate tempus erat bibendum. Nunc ullamcorper non lectus at euismod. Etiam nibh felis, tincidunt a metus vel, pellentesque rhoncus neque. Etiam at diam in erat luctus interdum. Nunc vel ipsum pulvinar, sollicitudin lacus ac, tempus urna. Etiam vel lacinia sapien. Pellentesque sagittis velit vel mi efficitur iaculis. Integer euismod sit amet urna in sagittis. Cras eleifend ut nibh in facilisis. Donec et lacus vitae nunc placerat sodales. Nulla sed hendrerit ligula, at dapibus sapien. - -Praesent at iaculis ex. Curabitur est purus, cursus a faucibus quis, dictum id velit. Donec dignissim fringilla viverra. Nunc mauris felis, laoreet sit amet sagittis at, vestibulum in libero. Maecenas quis orci turpis. Quisque ut nibh vitae magna mollis consequat id at mauris. Aliquam eu odio eget nulla bibendum sodales. Quisque vel orci eleifend nisi pretium lacinia. Suspendisse eget risus eget mi volutpat molestie eget quis lacus. Duis nisi libero, tincidunt nec nulla id, faucibus cursus felis. - -Donec tempor eget risus pellentesque molestie. Phasellus porta neque vel arcu egestas, nec blandit velit fringilla. Nullam porta faucibus justo vitae laoreet. Pellentesque viverra id nunc eu varius. Nulla pulvinar lobortis iaculis. Etiam vestibulum odio nec velit tristique, a tristique nisi mattis. In sed fringilla orci, vitae efficitur odio. Quisque dui odio, ornare eget velit at, lacinia consequat libero. Quisque lectus nulla, aliquet eu leo in, porta rutrum diam. Donec nec mattis neque. Nam rutrum, odio ac eleifend bibendum, dolor arcu rutrum neque, eget porta elit tellus a lacus. Sed massa metus, sollicitudin et sapien eu, finibus tempus orci. Proin et sapien sit amet erat molestie interdum. In quis rutrum velit, faucibus ultrices tellus. - -Sed sagittis sed justo eget tincidunt. Maecenas ut leo sagittis, feugiat magna et, viverra velit. Maecenas ex arcu, feugiat at consequat vitae, auctor eu massa. Integer egestas, enim vitae maximus convallis, est lectus pretium mauris, ac posuere lectus nisl quis quam. Aliquam tempus laoreet mi, vitae dapibus dolor varius dapibus. Suspendisse potenti. Donec sit amet purus nec libero dapibus tristique. Pellentesque viverra bibendum ligula. Donec sed felis et ex lobortis laoreet. Phasellus a fringilla libero, vitae malesuada nulla. Pellentesque blandit mattis lacus, et blandit tortor laoreet consequat. Suspendisse libero nunc, viverra sed fermentum in, accumsan egestas arcu. Proin in placerat elit. Sed interdum imperdiet malesuada. Suspendisse aliquet quis mauris eget sollicitudin. - -Vivamus accumsan tellus non erat volutpat, quis dictum dolor feugiat. Praesent rutrum nunc ac est mollis cursus. Fusce semper volutpat dui ut egestas. Curabitur sit amet posuere massa. Cras tincidunt nulla et mi mollis imperdiet. Suspendisse scelerisque ex id sodales vulputate. In nunc augue, pharetra in placerat eu, mattis id tellus. Vivamus cursus efficitur vehicula. Nulla aliquet vehicula aliquet. - -Sed cursus tellus sed porta pulvinar. Sed vitae nisi neque. Nullam aliquet, lorem et efficitur scelerisque, arcu diam aliquam felis, sed pulvinar lorem odio et turpis. Praesent convallis pulvinar turpis eu iaculis. Aliquam nec gravida mi. Curabitur eu nibh tempor, blandit justo in, ultrices felis. Fusce placerat metus non mi sagittis rutrum. Morbi sed dui fringilla, sagittis mauris eget, imperdiet nunc. Phasellus hendrerit sem elit, id hendrerit libero auctor sit amet. Integer sodales elit sit amet consequat cursus. - -Nam semper est eget nunc mollis, in pellentesque lectus fringilla. In finibus vel diam id semper. Nunc mattis quis erat eu consectetur. In hac habitasse platea dictumst. Nullam et ipsum vestibulum ex pulvinar ultricies sit amet id velit. Aenean suscipit mi tortor, a lobortis magna viverra non. Nulla condimentum aliquet ante et ullamcorper. Pellentesque porttitor arcu a posuere tempus. Aenean lacus quam, imperdiet eu justo vitae, pretium efficitur ex. Duis id purus id magna rhoncus ultrices id eu risus. Nunc dignissim et libero id dictum. - -Quisque a tincidunt neque. Phasellus commodo mi sit amet tempor fringilla. Ut rhoncus, neque non porttitor elementum, libero nulla egestas augue, sed fringilla sapien felis ac velit. Phasellus viverra rhoncus mollis. Nam ullamcorper leo vel erat laoreet luctus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vivamus semper a metus a cursus. Nulla sed orci egestas, efficitur purus ac, malesuada tellus. Aenean rutrum velit at tellus fermentum mollis. Aliquam eleifend euismod metus. - -In hac habitasse platea dictumst. Vestibulum volutpat neque vitae porttitor laoreet. Nam at tellus consequat, sodales quam in, pulvinar arcu. Maecenas varius convallis diam, ac lobortis tellus pellentesque quis. Maecenas eget augue massa. Nullam volutpat nibh ac justo rhoncus, ut iaculis tellus rutrum. Fusce efficitur efficitur libero quis condimentum. Curabitur congue neque non tincidunt tristique. Fusce eget tempor ex, at pellentesque odio. Praesent luctus dictum vestibulum. Etiam non orci nunc. Vivamus vitae laoreet purus, a lobortis velit. Curabitur tincidunt purus ac lectus elementum pellentesque. Quisque sed tincidunt est. - -Sed vel ultrices massa, vitae ultricies justo. Cras finibus mauris nec lacus tempus dignissim. Cras faucibus maximus velit, eget faucibus orci luctus vehicula. Nulla massa nunc, porta ac consequat eget, rhoncus non tellus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Fusce sed maximus metus, vel imperdiet ipsum. Ut scelerisque lectus at blandit porttitor. Ut vulputate nunc pharetra, aliquet sapien ac, sollicitudin sapien. Aenean eget ante lorem. Nam accumsan venenatis tellus id dignissim. - -Curabitur fringilla, magna non maximus dapibus, nulla sapien vestibulum lectus, sit amet semper dolor neque vitae nisl. Nunc ultrices vehicula augue sed iaculis. Maecenas nec diam mollis, suscipit orci et, vestibulum ante. Pellentesque eu nisl tortor. Nunc eleifend, lacus quis volutpat volutpat, nisi mi molestie sem, quis mollis ipsum libero a tellus. Ut viverra dolor mattis convallis interdum. Sed tempus nisl at nunc scelerisque aliquet. Quisque tempor tempor lorem id feugiat. Nullam blandit lectus velit, vitae porta lacus tincidunt a. Vivamus sit amet arcu ultrices, tincidunt mi quis, viverra quam. Aenean fringilla libero elementum lorem semper, quis pulvinar eros gravida. Nullam sodales blandit mauris, sed fermentum velit fermentum sit amet. Donec malesuada mauris in augue sodales vulputate. Vestibulum gravida turpis id elit rhoncus dignissim. Integer non congue lorem, eu viverra orci. - -Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec at dolor magna. Aliquam consectetur erat augue, id iaculis velit pharetra ac. Integer rutrum venenatis dignissim. Integer non sodales elit. Curabitur ut magna ut nibh feugiat aliquam ac ut risus. Morbi nibh quam, aliquam id placerat nec, vestibulum eget velit. Suspendisse at dignissim quam. Vivamus aliquet sem sed nisl volutpat, ut cursus orci ultrices. Aliquam ultrices lacinia enim, vitae aliquet neque. - -Quisque scelerisque finibus diam in mattis. Cras cursus auctor velit. Aliquam sem leo, fermentum et maximus et, molestie a libero. Aenean justo elit, rutrum a ornare id, egestas eget enim. Aenean auctor tristique erat. Curabitur condimentum libero lacus, nec consequat orci vestibulum sed. Fusce elit ligula, blandit vitae sapien vitae, dictum ultrices risus. Nam laoreet suscipit sapien, at interdum velit faucibus sit amet. Duis quis metus egestas lectus elementum posuere non nec libero. Aliquam a dolor bibendum, facilisis nunc a, maximus diam. Vestibulum suscipit tristique magna, non dignissim turpis sodales sed. Nunc ornare, velit ac facilisis fringilla, dolor mi consectetur lorem, vitae finibus erat justo suscipit urna. Maecenas sit amet eros erat. Nunc non arcu ornare, suscipit lorem eget, sodales mauris. Aliquam tincidunt, quam nec mollis lacinia, nisi orci fermentum libero, consequat eleifend lectus quam et sapien. Vestibulum a quam urna. - -Cras arcu leo, euismod ac ullamcorper at, faucibus sed massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus porttitor velit in enim interdum, non commodo metus ornare. Morbi vel lorem quis nisl luctus tristique quis vitae nisl. Suspendisse condimentum tortor enim, nec eleifend ipsum euismod et. Sed gravida quam ut tristique lacinia. Mauris eu interdum ipsum, ac ultrices odio. Nullam auctor tellus a risus porttitor vehicula. Nulla blandit euismod dictum. In pharetra, enim iaculis pulvinar interdum, dui nunc placerat nunc, sit amet pretium lectus nulla vitae quam. Phasellus quis enim sollicitudin, varius nulla id, ornare purus. Donec quam lacus, vestibulum quis nunc ac, mollis dictum nisi. Cras ut mollis elit. Maecenas ultrices ligula at risus faucibus scelerisque. Etiam vitae porttitor purus. Curabitur blandit lectus urna, ut hendrerit tortor feugiat ut. - -Phasellus fringilla, sapien pellentesque commodo pharetra, ante libero aliquam tellus, ut consectetur augue libero a sapien. Maecenas blandit luctus nisl eget aliquet. Maecenas vitae porta dolor, faucibus laoreet sapien. Suspendisse lobortis, ipsum sed vehicula aliquam, elit purus scelerisque dui, rutrum consectetur diam odio et lorem. In nec lacinia metus. Donec viverra libero est, vel bibendum erat condimentum quis. Donec feugiat purus leo. In laoreet vitae felis a porttitor. Mauris ullamcorper, lacus id condimentum suscipit, neque magna pellentesque arcu, eget cursus neque tellus id metus. Curabitur volutpat ac orci vel ultricies. - -Sed ut finibus erat. Sed diam purus, varius non tincidunt quis, ultrices sit amet ipsum. Donec et egestas nulla. Suspendisse placerat nisi at dui laoreet iaculis. Aliquam aliquet leo at augue faucibus molestie. Nullam lacus augue, hendrerit sed nisi eu, faucibus porta est. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam ut leo aliquet sem fermentum rutrum quis ac justo. Integer placerat aliquam nisl ut sagittis. Proin erat orci, lobortis et sem eget, eleifend fringilla augue. Mauris varius laoreet arcu, sed tincidunt felis. Pellentesque venenatis lorem odio, id pulvinar velit molestie feugiat. Donec mattis lacus sed eleifend pulvinar. - -Sed condimentum ex in tincidunt hendrerit. Etiam eget risus lacinia, euismod nibh eu, pellentesque quam. Proin elit eros, convallis id mauris ac, bibendum ultrices lectus. Morbi venenatis, purus id fermentum consequat, nunc libero tincidunt ligula, non dictum ligula orci nec quam. Nulla nec ultrices lorem. Aenean maximus augue vel dictum pharetra. Etiam turpis urna, pellentesque quis malesuada eu, molestie faucibus felis. - -Vestibulum pharetra augue ut quam blandit congue in nec risus. Proin eu nibh eu dui eleifend porta vitae id lectus. Proin lacus nibh, lobortis sed ligula vitae, interdum lobortis erat. Suspendisse potenti. In sollicitudin quis sapien ut aliquet. Mauris ac nulla arcu. Fusce tristique justo quis lectus mollis, eu volutpat lectus finibus. Vivamus venenatis facilisis ex ut vestibulum. - -Etiam varius lobortis purus, in hendrerit elit tristique at. In tempus, augue vestibulum fermentum gravida, ligula tellus vulputate arcu, eu molestie ex sapien at purus. Vestibulum nec egestas metus. Duis pulvinar quam nec consequat interdum. Aenean non dapibus lacus. Aliquam sit amet aliquet nulla. Sed venenatis volutpat purus nec convallis. Phasellus aliquet semper sodales. Cras risus sapien, condimentum auctor urna a, pulvinar ornare nisl. Sed tincidunt felis elit, ut elementum est bibendum ac. Morbi interdum justo vel dui faucibus condimentum. - -Sed convallis eu sem at tincidunt. Nullam at auctor est, et ullamcorper ipsum. Pellentesque eget ante ante. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer euismod, sapien sed dapibus ornare, nibh enim maximus lacus, lacinia placerat urna quam quis felis. Morbi accumsan id nisl ut condimentum. Donec bibendum nisi est, sed volutpat lorem rhoncus in. Vestibulum ac lacinia nunc, eget volutpat magna. Integer aliquam pharetra ipsum, id placerat nunc volutpat quis. Etiam urna diam, rhoncus sit amet varius vel, euismod vel sem. Nullam vel molestie urna. Vivamus ornare erat at venenatis euismod. Suspendisse potenti. Fusce diam justo, tincidunt vel sem at, commodo faucibus nisl. Duis gravida efficitur diam, vel sagittis erat pulvinar ut. - -Quisque vel pharetra felis. Duis efficitur tortor dolor, vitae porttitor erat fermentum sed. Sed eu mi purus. Etiam dignissim tortor eu tempus molestie. Aenean pretium erat enim, in hendrerit ante hendrerit at. Sed ut risus vel nunc venenatis ultricies quis in lacus. Pellentesque vitae purus euismod, placerat risus non, ullamcorper augue. Quisque varius quam ligula, nec aliquet ex faucibus vitae. Quisque rhoncus sit amet leo tincidunt mattis. Cras id mauris eget purus pretium gravida sit amet eu augue. Aliquam dapibus odio augue, id lacinia velit pulvinar eu. - -Mauris fringilla, tellus nec pharetra iaculis, neque nisi ultrices massa, et tincidunt sem dui sed mi. Curabitur erat lorem, venenatis quis tempus lacinia, tempus sit amet nunc. Aliquam at neque ac metus commodo dictum quis vitae justo. Phasellus eget lacus tempus, blandit lorem vel, rutrum est. Aenean pharetra sem ut augue lobortis dignissim. Sed rhoncus at nulla id ultrices. Cras id condimentum felis. In suscipit luctus vulputate. Donec tincidunt lacus nec enim tincidunt sollicitudin ut quis enim. Nam at libero urna. Praesent sit amet massa vitae massa ullamcorper vehicula. - -Nullam bibendum augue ut turpis condimentum bibendum. Proin sit amet urna hendrerit, sodales tortor a, lobortis lectus. Integer sagittis velit turpis, et tincidunt nisi commodo eget. Duis tincidunt elit finibus accumsan cursus. Aenean dignissim scelerisque felis vel lacinia. Nunc lacinia maximus luctus. In hac habitasse platea dictumst. Vestibulum eget urna et enim tempor tempor. Nam feugiat, felis vel vestibulum tempus, orci justo viverra diam, id dapibus lorem justo in ligula. - -Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. In ac pellentesque sem. Vestibulum lacinia magna dui, eu lacinia augue placerat et. Maecenas pulvinar congue est. Pellentesque commodo dui non pulvinar scelerisque. Etiam interdum est posuere sem bibendum, ac commodo magna dictum. Cras ipsum turpis, rhoncus nec posuere vitae, laoreet a arcu. Integer ac massa sit amet enim placerat lacinia sed ultrices arcu. Suspendisse sem nibh, luctus sit amet volutpat in, pellentesque eu metus. Ut gravida neque eget mi accumsan tempus. Nam sit amet aliquet nibh. - -Pellentesque a purus cursus nulla hendrerit congue quis et odio. Aenean hendrerit, leo ullamcorper sagittis hendrerit, erat dui molestie quam, sed condimentum lacus risus sed tellus. Morbi a dapibus lectus, ut feugiat ex. Phasellus pretium quam et sapien mollis, vel iaculis dui dignissim. Sed ullamcorper est turpis, a viverra lorem consectetur in. Aenean aliquet nibh non cursus rutrum. Suspendisse at tristique urna, id lobortis urna. In hac habitasse platea dictumst. Phasellus libero velit, rutrum sed tellus nec, dapibus tincidunt ligula. Quisque vel dui venenatis, consequat nisl ut, lacinia ipsum. Phasellus vitae magna pellentesque, lobortis est id, faucibus quam. Nam eleifend faucibus dui vel pellentesque. - -Etiam ut est non lacus tincidunt interdum. Maecenas sed massa urna. Quisque ut nibh tortor. Pellentesque felis ipsum, tempor finibus ipsum et, euismod pretium metus. Donec sit amet est ipsum. Quisque rhoncus justo non finibus elementum. Nulla nec lectus ac tortor placerat fringilla. Phasellus ac ultrices nunc, eu efficitur nisl. Nulla rhoncus nunc vitae ante dictum tincidunt. Nunc ultrices, massa sit amet malesuada dignissim, lectus lacus consequat sapien, non eleifend metus sem in eros. Phasellus mauris ante, dictum sit amet suscipit ac, rhoncus eget nisi. Phasellus at orci mollis, imperdiet neque eget, faucibus nulla. In at purus massa. Pellentesque quis rutrum lectus. - -Integer eu faucibus turpis, sit amet mollis massa. Vestibulum id nulla commodo, rutrum ipsum sed, semper ante. Phasellus condimentum orci nec nibh convallis, ac maximus orci ullamcorper. Maecenas vitae sollicitudin mi. Integer et finibus lectus, et condimentum ligula. Donec elementum tristique quam vitae dapibus. Morbi euismod ipsum in tristique ullamcorper. - -Duis fermentum non enim eu auctor. Quisque lacinia nibh vehicula nibh posuere, eu volutpat turpis facilisis. Ut ac faucibus nulla. Sed eleifend quis ex et pellentesque. Vestibulum sollicitudin in libero id fringilla. Phasellus dignissim purus consequat, condimentum dui sit amet, condimentum ante. Pellentesque ac consectetur massa, quis sagittis est. Nulla maximus tristique risus accumsan convallis. Curabitur imperdiet ac lacus a ultrices. Nulla facilisi. Sed quis quam quis lectus placerat lobortis vel sed turpis. In mollis dui id neque iaculis, ut aliquet tellus malesuada. Proin at luctus odio, vel blandit sapien. Praesent dignissim tortor vehicula libero fringilla, nec ultrices erat suscipit. Maecenas scelerisque purus in dapibus fermentum. - -Curabitur magna odio, mattis in tortor ut, porttitor congue est. Vestibulum mollis lacinia elementum. Fusce maximus erat vitae nunc rutrum lobortis. Integer ligula eros, auctor vel elit non, posuere luctus lacus. Maecenas quis auctor massa. Ut ipsum lacus, efficitur posuere euismod et, hendrerit efficitur est. Phasellus fringilla, quam id tincidunt pretium, nunc dui sollicitudin orci, eu dignissim nisi metus ut magna. Integer lobortis interdum dolor, non bibendum purus posuere et. Donec non lectus aliquet, pretium dolor eu, cursus massa. Sed ut dui sapien. In sed vestibulum massa. Pellentesque blandit, dui non sodales vehicula, orci metus mollis nunc, non pharetra ex tellus ac est. Mauris sagittis metus et fermentum pretium. Nulla facilisi. Quisque quis ante ut nulla placerat mattis ut quis nisi. - -Sed quis nulla ligula. Quisque dignissim ligula urna, sed aliquam purus semper at. Suspendisse potenti. Nunc massa lectus, pharetra vehicula arcu bibendum, imperdiet sodales ipsum. Nam ac sapien diam. Mauris iaculis fringilla mattis. Pellentesque tempus eros sit amet justo volutpat mollis. Phasellus ac turpis ipsum. Morbi vel ante elit. Aenean posuere quam consequat velit varius suscipit. Donec tempor quam ut nibh cursus efficitur. - -Morbi molestie dolor nec sem egestas suscipit. Etiam placerat pharetra lectus, et ullamcorper risus tristique in. Sed faucibus ullamcorper lectus eget fringilla. Maecenas malesuada hendrerit congue. Sed eget neque a erat placerat tincidunt. Aliquam vitae dignissim turpis. Fusce at placerat magna, a laoreet lectus. Maecenas a purus nec diam gravida fringilla. Nam malesuada euismod ante non vehicula. In faucibus bibendum leo, faucibus posuere nisl pretium quis. Fusce finibus bibendum finibus. Vestibulum eu justo maximus, hendrerit diam nec, dignissim sapien. Aenean dolor lacus, malesuada quis vestibulum ac, venenatis ac ipsum. Cras a est id nunc finibus facilisis. Cras lacinia neque et interdum vehicula. Suspendisse vulputate tellus elit, eget tempor dui finibus vel. - -Cras sed pretium odio. Proin hendrerit elementum felis in tincidunt. Nam sed turpis vel justo molestie accumsan condimentum eu nunc. Praesent lobortis euismod rhoncus. Nulla vitae euismod nibh, quis mattis mi. Fusce ultrices placerat porttitor. Duis sem ipsum, pellentesque sit amet odio a, molestie vulputate mauris. - -Duis blandit mollis ligula, sit amet mattis ligula finibus sit amet. Nunc a leo molestie, placerat diam et, vestibulum leo. Suspendisse facilisis neque purus, nec pellentesque ligula fermentum nec. Aenean malesuada mauris lorem, eu blandit arcu pulvinar quis. Duis laoreet urna lacus, non maximus arcu rutrum ultricies. Nulla augue dolor, suscipit eu mollis eu, aliquam condimentum diam. Ut semper orci luctus, pharetra turpis at, euismod mi. Nulla leo diam, finibus sit amet purus sed, maximus dictum lorem. Integer eu mi id turpis laoreet rhoncus. - -Integer a mauris tincidunt, finibus orci ut, pretium mauris. Nulla molestie nunc mi, id finibus lorem elementum sed. Proin quis laoreet ante. Integer nulla augue, commodo id molestie quis, rutrum ut turpis. Suspendisse et tortor turpis. Sed ut pharetra massa. Pellentesque elementum blandit sem, ut elementum tellus egestas a. Fusce eu purus nibh. - -Cras dignissim ligula scelerisque magna faucibus ullamcorper. Proin at condimentum risus, auctor malesuada quam. Nullam interdum interdum egestas. Nulla aliquam nisi vitae felis mollis dictum. Suspendisse dapibus consectetur tortor. Ut ut nisi non sem bibendum tincidunt. Vivamus suscipit leo quis gravida dignissim. - -Aliquam interdum, leo id vehicula mollis, eros eros rhoncus diam, non mollis ligula mi eu mauris. Sed ultrices vel velit sollicitudin tincidunt. Nunc auctor metus at ligula gravida elementum. Praesent interdum eu elit et mollis. Duis egestas quam sit amet velit dignissim consequat. Aliquam ac turpis nec nunc convallis sagittis. Fusce blandit, erat ac fringilla consectetur, dolor eros sodales leo, vel aliquet risus nisl et diam. Aliquam luctus felis vitae est eleifend euismod facilisis et lacus. Sed leo tellus, auctor eu arcu in, volutpat sagittis nisl. Pellentesque nisl ligula, placerat vel ullamcorper at, vulputate ac odio. Morbi ac faucibus orci, et tempus nulla. Proin rhoncus rutrum dolor, in venenatis mauris. Suspendisse a fermentum augue, non semper mi. Nunc eget pretium neque. Phasellus augue erat, feugiat ac aliquam congue, rutrum non sapien. Pellentesque ac diam gravida, consectetur felis at, ornare neque. - -Nullam interdum mattis sapien quis porttitor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Phasellus aliquet rutrum ipsum id euismod. Maecenas consectetur massa et mi porta viverra. Nunc quam nibh, dignissim vitae maximus et, ullamcorper nec lorem. Nunc vitae justo dapibus, luctus lacus vitae, pretium elit. Maecenas et efficitur leo. Curabitur mauris lectus, placerat quis vehicula vitae, auctor ut urna. Quisque rhoncus pharetra luctus. In hac habitasse platea dictumst. Integer sit amet metus nec eros malesuada aliquam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi hendrerit mi ac leo aliquam, sit amet ultricies libero commodo. Mauris dapibus purus metus, sit amet viverra nibh imperdiet et. Nullam porta nulla tellus, quis vehicula diam imperdiet non. Vivamus enim massa, bibendum in fermentum in, ultrices at ex. - -Suspendisse fermentum id nibh eget accumsan. Duis dapibus bibendum erat ut sollicitudin. Aliquam nec felis risus. Pellentesque rhoncus ligula id sem maximus mollis sed nec massa. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus ipsum ipsum, sodales sed enim id, convallis faucibus eros. Donec ultricies dictum tincidunt. Cras vitae nibh arcu. Pellentesque cursus, sapien nec consequat fermentum, ipsum ante suscipit dui, imperdiet hendrerit est nisl eu massa. Quisque vitae sem ligula. Aenean iaculis metus ut mauris interdum laoreet. Vivamus sed gravida dolor. - -Morbi nulla metus, porttitor sed eros sit amet, efficitur efficitur est. In vel nisl urna. Ut aliquet tellus at congue convallis. Phasellus imperdiet lobortis sollicitudin. Integer sodales, sem eu ultricies pharetra, erat erat porttitor odio, eget dapibus libero ipsum eget velit. Phasellus gravida nulla nisl, eu pharetra mi auctor vel. Sed blandit pharetra velit, ut egestas libero placerat non. Aliquam a interdum quam. Proin at tortor nec dui sollicitudin tempus sed vestibulum elit. Nunc non sollicitudin velit. - -Aenean consequat diam velit, sed rutrum tortor faucibus dictum. Quisque at semper augue. Duis ut est eget mi ornare bibendum id et ligula. Phasellus consequat tortor non leo pulvinar posuere. Proin vestibulum eleifend felis, in hendrerit tortor sollicitudin eu. Phasellus hendrerit, lacus vel laoreet interdum, dui tortor consequat justo, commodo ultricies arcu felis vitae enim. Vivamus eu sapien at leo suscipit rutrum eu at justo. Aenean et dolor a libero ullamcorper posuere. Integer laoreet placerat nisi in vulputate. Mauris laoreet eget risus sed cursus. Donec scelerisque neque a libero eleifend hendrerit. Nulla varius condimentum nunc sit amet fermentum. Aliquam lorem ex, varius nec mollis ut, ultrices in neque. Morbi sit amet porta leo. Integer iaculis fermentum lacus in vestibulum. - -Ut gravida, tellus ut maximus ultrices, erat est venenatis nisl, vitae pretium massa ex ac magna. Sed non purus eget ligula aliquet volutpat non quis arcu. Nam aliquam tincidunt risus, sit amet fringilla sapien vulputate ut. Mauris luctus suscipit pellentesque. Nunc porttitor dapibus ex quis tempus. Ut ullamcorper metus a eros vulputate, vitae viverra lectus convallis. Mauris semper imperdiet augue quis tincidunt. Integer porta pretium magna, sed cursus sem scelerisque sollicitudin. Nam efficitur, nibh pretium eleifend vestibulum, purus diam posuere sem, in egestas mauris augue sit amet urna. - -Vestibulum tincidunt euismod massa in congue. Duis interdum metus non laoreet fringilla. Donec at ligula congue, tincidunt nunc non, scelerisque nunc. Donec bibendum magna non est scelerisque feugiat at nec neque. Ut orci tortor, tempus eget massa non, dignissim faucibus dolor. Nam odio risus, accumsan pretium neque eget, accumsan dignissim dui. In ut neque auctor, scelerisque tellus sed, ullamcorper nisi. Suspendisse varius cursus quam at hendrerit. Vivamus elit libero, sagittis vitae sem ac, vulputate iaculis ligula. - -Sed lobortis laoreet purus sit amet rutrum. Pellentesque feugiat non leo vel lacinia. Quisque feugiat nisl a orci bibendum vestibulum. In et sollicitudin urna. Morbi a arcu ac metus faucibus tempus. Nam eu imperdiet sapien, suscipit mattis tortor. Aenean blandit ipsum nisi, a eleifend ligula euismod at. Integer tincidunt pharetra felis, mollis placerat mauris hendrerit at. Curabitur convallis, est sit amet luctus volutpat, massa lacus cursus augue, sed eleifend magna quam et risus. Aliquam lobortis tincidunt metus vitae porttitor. Suspendisse potenti. Aenean ullamcorper, neque id commodo luctus, nulla nunc lobortis quam, id dapibus neque dui nec mauris. Etiam quis lorem quis elit commodo ornare. Ut pharetra purus ultricies enim ultrices efficitur. Proin vehicula tincidunt molestie. Mauris et placerat sem. - -Aliquam erat volutpat. Suspendisse velit turpis, posuere ac lacus eu, lacinia laoreet velit. Sed interdum felis neque, id blandit sem malesuada sit amet. Ut sagittis justo erat, efficitur semper orci tempor sed. Donec enim massa, posuere varius lectus egestas, pellentesque posuere mi. Cras tincidunt ut libero sed mattis. Suspendisse quis magna et tellus posuere interdum vel at purus. Pellentesque fringilla tristique neque, id aliquet tellus ultricies non. Duis ut tellus vel odio lobortis vulputate. - -Integer at magna ac erat convallis vestibulum. Sed lobortis porttitor mauris. Fusce varius lorem et volutpat pulvinar. Aenean ac vulputate lectus, vitae consequat velit. Suspendisse ex dui, varius ut risus ut, dictum scelerisque sem. Vivamus urna orci, volutpat ut convallis ac, venenatis vitae urna. In hac habitasse platea dictumst. Etiam eu purus arcu. Aenean vulputate leo urna, vel tristique dui sagittis euismod. Suspendisse non tellus efficitur ante rhoncus volutpat at et sapien. - -Sed dapibus accumsan porttitor. Phasellus facilisis lectus finibus ligula dignissim, id pulvinar lectus feugiat. Nullam egestas commodo nisi posuere aliquet. Morbi sit amet tortor sagittis, rutrum dui nec, dapibus sapien. Sed posuere tortor tortor, interdum auctor magna varius vitae. Vestibulum id sagittis augue. Curabitur fermentum arcu sem, eu condimentum quam rutrum non. Phasellus rutrum nibh quis lectus rhoncus pretium. Curabitur dictum interdum elit. Vestibulum maximus sodales imperdiet. Mauris auctor nec purus sed venenatis. In in urna purus. - -Duis placerat molestie suscipit. Morbi a elit id purus efficitur consequat. Nunc ac commodo turpis. Etiam sit amet lacus a ipsum tempus venenatis sed vel nibh. Duis elementum aliquam mi sed tristique. Morbi ligula tortor, semper ac est vel, lobortis maximus erat. Curabitur ipsum felis, laoreet vel condimentum eget, ullamcorper sit amet mauris. Nulla facilisi. Nam at purus sed mi egestas placerat vitae vel magna. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Suspendisse at dignissim diam. Phasellus consectetur eget neque vel viverra. Donec sollicitudin mattis dolor vel malesuada. Vivamus vehicula leo neque, vitae fermentum leo posuere et. Praesent dui est, finibus sit amet tristique quis, pharetra vel nibh. - -Duis nulla leo, accumsan eu odio eget, sagittis semper orci. Quisque ullamcorper ligula quam, commodo porttitor mauris ullamcorper eu. Cras varius sagittis felis in aliquam. Duis sodales risus ac justo vehicula, nec mattis diam lacinia. Cras eget lectus ipsum. Ut commodo, enim vitae malesuada hendrerit, ex dolor egestas lectus, sit amet hendrerit metus diam nec est. Vestibulum tortor metus, lobortis sit amet ante eget, tempor molestie lacus. In molestie et urna et semper. Mauris mollis, sem non hendrerit condimentum, sapien nisi cursus est, non suscipit quam justo non metus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam enim est, porta ac feugiat vitae, rutrum in lorem. Duis vehicula tortor ut posuere maximus. - -Nullam vestibulum non tellus sed commodo. Quisque mattis elit sit amet sapien sollicitudin, ut condimentum nisl congue. Aenean sagittis massa vel elit faucibus fermentum. Donec tincidunt nisi nec nisl sodales pellentesque. Mauris congue congue ligula ut suscipit. Vivamus velit tortor, tempor et gravida eget, fermentum sit amet ante. Nullam fringilla, lorem at ultrices cursus, urna neque ornare dolor, eu lacinia orci enim sed nibh. Ut a ullamcorper lectus, id mattis purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean maximus sollicitudin posuere. Nunc at augue lacus. Aenean efficitur leo sit amet lacinia efficitur. - -Quisque venenatis quam mi, in pharetra odio vulputate eu. In vel nisl pulvinar, pulvinar ligula ut, sodales risus. Sed efficitur lectus at vestibulum tincidunt. Vestibulum eu ullamcorper elit. Fusce vestibulum magna enim, et tempor lacus posuere vitae. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer leo elit, luctus nec mattis sit amet, sollicitudin in turpis. - -Proin convallis venenatis leo, vitae tristique erat iaculis nec. Nulla facilisi. Duis porttitor, sapien et bibendum vulputate, sem libero sodales lacus, non malesuada felis erat ut libero. Nam non felis semper, finibus est a, mattis mauris. Praesent nec eros quam. Nulla hendrerit, augue consectetur eleifend ultricies, purus mi condimentum nulla, eget dapibus est nunc sed libero. Nullam elementum dui erat, vitae luctus libero sollicitudin et. Nulla odio magna, placerat in augue eu, dapibus imperdiet odio. Suspendisse imperdiet metus sit amet rhoncus dapibus. Cras at enim et urna vehicula cursus eu a mauris. Integer magna ante, eleifend ac placerat vitae, porta at nisi. Cras eget malesuada orci. Curabitur nunc est, vulputate id viverra et, dignissim sed odio. Curabitur non mattis sem. Sed bibendum, turpis vitae vehicula faucibus, nunc quam ultricies lectus, vitae viverra felis turpis at libero. - -Nullam ut egestas ligula. Proin hendrerit justo a lectus commodo venenatis. Nulla facilisi. Ut cursus lorem quis est bibendum condimentum. Aenean in tristique odio. Fusce tempor hendrerit ipsum. Curabitur mollis felis justo, quis dapibus erat auctor vel. Sed augue lectus, finibus ut urna quis, ullamcorper vestibulum dui. Etiam molestie aliquam tempor. Integer mattis sollicitudin erat, et tristique elit varius vel. Mauris a ex justo. - -Nam eros est, imperdiet non volutpat rutrum, pellentesque accumsan ligula. Duis sit amet turpis metus. Aenean in rhoncus metus, ac fringilla ex. Suspendisse condimentum egestas purus, ut pharetra odio vulputate vel. Duis tincidunt massa a placerat ultrices. Mauris ultricies nibh sit amet condimentum malesuada. Duis tincidunt id ipsum sed congue. - -Praesent eu ex augue. Nullam in porta ligula. In tincidunt accumsan arcu, in pellentesque magna tristique in. Mauris eleifend libero ac nisl viverra faucibus. Nam sollicitudin dolor in commodo hendrerit. Cras at orci metus. Ut quis laoreet orci. Vivamus ultrices leo pellentesque tempor aliquet. Maecenas ut eros vitae purus placerat vestibulum. Etiam vitae gravida dolor, quis rhoncus diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. - -Suspendisse fringilla lacinia sagittis. Integer tincidunt consectetur tristique. Morbi non orci convallis, congue sapien quis, vulputate nunc. Donec a libero vel magna elementum facilisis non quis mi. Mauris posuere tellus non ipsum ultrices elementum. Vivamus massa velit, facilisis quis placerat aliquet, aliquet nec leo. Praesent a maximus sem. Sed neque elit, feugiat vel quam non, molestie sagittis nunc. Etiam luctus nunc ac mauris scelerisque, nec rhoncus lacus convallis. Nunc pharetra, nunc ac pulvinar aliquam, ex ipsum euismod augue, nec porttitor lacus turpis vitae neque. Fusce bibendum odio id tortor faucibus pellentesque. Sed ac porta nibh, eu gravida erat. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aliquam quis ullamcorper felis. Nulla mattis sagittis ante ac tincidunt. Integer ac felis efficitur, viverra libero et, facilisis ligula. Suspendisse a metus a massa rhoncus posuere. Phasellus suscipit ligula ut lacus facilisis, ac pellentesque ex tempor. Quisque consectetur massa mi, ac molestie libero dictum quis. Proin porttitor ligula quis erat tincidunt venenatis. Proin congue nunc sed elit gravida, nec consectetur lectus sodales. Etiam tincidunt convallis ipsum at vestibulum. Quisque maximus enim et mauris porttitor, et molestie magna tristique. Morbi vitae metus elit. Maecenas sed volutpat turpis. Aliquam vitae dolor vestibulum, elementum purus eget, dapibus nibh. Nullam egestas dui ac rutrum semper. - -Etiam hendrerit est metus, et condimentum metus aliquam ac. Pellentesque id neque id ipsum rhoncus vulputate. Aliquam erat nisl, posuere sit amet ligula ac, fermentum blandit felis. Vivamus fermentum mi risus, non lacinia purus viverra id. Aenean ac sapien consequat, finibus mauris nec, porta sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed quis consectetur ex, dignissim bibendum nulla. Phasellus ac libero at quam vehicula euismod non eu leo. Phasellus a sapien augue. - -Maecenas ligula dui, bibendum vitae mauris et, auctor laoreet felis. Duis non libero a mi semper mattis. Quisque consequat luctus massa, quis tristique eros auctor feugiat. Maecenas sodales euismod neque vitae facilisis. Nullam laoreet imperdiet velit at pellentesque. Etiam massa odio, facilisis a consequat vitae, placerat vel magna. Nunc sagittis eros nec urna fringilla, pulvinar vestibulum nibh scelerisque. Sed magna metus, cursus eu consequat et, pharetra a est. Suspendisse elementum neque a dui malesuada lacinia. Donec sed ipsum volutpat, cursus urna id, ullamcorper arcu. Maecenas laoreet nisl eget velit egestas sollicitudin. Etiam nisl turpis, mollis id dignissim vitae, tristique vehicula ante. Maecenas eget placerat est, at rutrum augue. Vivamus faucibus lacinia ullamcorper. Sed pulvinar urna sodales ante sodales, at gravida leo dictum. - -Morbi maximus, quam a lobortis bibendum, enim felis varius elit, ac vehicula elit nisl ut lacus. Quisque ut arcu augue. Praesent id turpis quam. Sed sed arcu eros. Maecenas at cursus lorem, ac eleifend nisi. Fusce mattis felis at commodo pharetra. Praesent ac commodo ipsum. Quisque finibus et eros vitae tincidunt. In hac habitasse platea dictumst. Praesent purus ipsum, luctus lobortis ornare quis, auctor eget justo. Nam vel enim sollicitudin, faucibus tortor eu, sagittis eros. Ut nec consectetur erat. Donec ultricies malesuada ligula, a hendrerit sapien volutpat in. Maecenas sed enim vitae sapien pulvinar faucibus. - -Proin semper nunc nibh, non consequat neque ullamcorper vel. Maecenas lobortis sagittis blandit. Aenean et arcu ultricies turpis malesuada malesuada. Ut quam ex, laoreet ut blandit cursus, feugiat vitae dolor. Etiam ex lacus, scelerisque vel erat vel, efficitur tincidunt magna. Morbi tristique lacinia dolor, in egestas magna ultrices vitae. Integer ultrices leo ac tempus venenatis. Praesent ac porta tortor. Vivamus ornare blandit tristique. Nulla rutrum finibus pellentesque. In non dui elementum, fermentum ipsum vel, varius magna. Pellentesque euismod tortor risus, ac pellentesque nisl faucibus eget. - -Vivamus eu enim purus. Cras ultrices rutrum egestas. Sed mollis erat nibh, at posuere nisl luctus nec. Nunc vulputate, sapien id auctor molestie, nisi diam tristique ante, non convallis tellus nibh at orci. Morbi a posuere purus, in ullamcorper ligula. Etiam elementum sit amet dui imperdiet iaculis. Proin vitae tincidunt ipsum, sit amet placerat lectus. Curabitur commodo sapien quam, et accumsan lectus fringilla non. Nullam eget accumsan enim, ac pharetra mauris. Sed quis tristique velit, vitae commodo nisi. Duis turpis dui, maximus ut risus at, finibus consequat nunc. Maecenas sed est accumsan, aliquet diam in, facilisis risus. Curabitur vehicula rutrum auctor. Nam iaculis risus pulvinar maximus viverra. Nulla vel augue et ex sagittis blandit. - -Ut sem nulla, porta ac ante ac, posuere laoreet eros. Donec sodales posuere justo a auctor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Cras mollis at orci hendrerit porta. Nullam sodales tortor tortor, non lacinia diam finibus id. Duis libero orci, suscipit ac odio et, dictum consequat ipsum. Pellentesque eu ligula sagittis, volutpat eros at, lacinia lorem. Cras euismod tellus in iaculis tempor. Quisque accumsan, magna a congue venenatis, ante ipsum aliquam lectus, at egestas enim nunc at justo. Quisque sem purus, viverra ut tristique ut, maximus id enim. Etiam quis placerat sem. In sollicitudin, lacus eu rutrum mollis, nulla eros luctus elit, vel dapibus urna purus nec urna. Phasellus egestas massa quam, ac molestie erat hendrerit a. Praesent ultrices neque ut turpis molestie auctor. Etiam molestie placerat purus, et euismod erat aliquam in. Morbi id suscipit justo. - -Proin est ante, consequat at varius a, mattis quis felis. Sed accumsan nibh sit amet ipsum elementum posuere. Vestibulum bibendum id diam sit amet gravida. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi nec dolor vel ipsum dignissim hendrerit vel non ipsum. Praesent facilisis orci quis elit auctor lobortis. Phasellus cursus risus lectus, vel lobortis libero dapibus in. Quisque tristique tempus leo a pulvinar. Pellentesque a magna tincidunt, pellentesque massa nec, laoreet orci. Morbi congue ornare dolor quis commodo. Phasellus massa nisi, tincidunt at eros dictum, hendrerit lobortis urna. Maecenas porta, magna id mattis molestie, nibh tellus lobortis sem, eget tincidunt ipsum quam eu turpis. - -Ut gravida orci risus, vel rutrum mauris vehicula id. Etiam bibendum, neque a placerat condimentum, ex orci imperdiet lectus, quis dapibus arcu lacus eget lectus. Sed consequat non mi sit amet venenatis. Fusce vestibulum erat libero, eget hendrerit risus vulputate sollicitudin. Integer sed eleifend felis. Donec commodo, sem eu mattis placerat, urna odio aliquam tellus, et laoreet justo tellus eget erat. Fusce sed suscipit tortor. Nam hendrerit nibh ac nunc auctor lacinia. Pellentesque placerat condimentum ipsum, eget semper tortor hendrerit vel. Nullam non urna eu lacus pellentesque congue ut id eros. - -Nunc finibus leo in rhoncus tristique. Sed eu ipsum nec nisl egestas faucibus eget a felis. Pellentesque vitae nisi in nulla accumsan fermentum. Sed venenatis feugiat eleifend. Fusce porttitor varius placerat. Aliquam aliquet lacus sit amet mattis mollis. Sed vel nulla quis dolor suscipit vehicula ac viverra lorem. Duis viverra ipsum eget nulla ullamcorper fermentum. Mauris tincidunt arcu quis quam fringilla ornare. Donec et iaculis tortor. Nam ultricies libero vel ipsum aliquet efficitur. Morbi eget dolor aliquam, tempus sapien eget, viverra ante. Donec varius mollis ex, sed efficitur purus euismod interdum. Quisque vel sapien non neque tincidunt semper. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. - -Suspendisse sit amet purus leo. Fusce lectus lorem, aliquam ac nulla eget, imperdiet ornare eros. Nullam sem augue, varius in nisi non, sollicitudin pellentesque ante. Etiam eu odio condimentum, tempor libero et, egestas arcu. Cras pellentesque eleifend aliquet. Pellentesque non blandit ligula. Ut congue viverra rhoncus. Phasellus mattis mi ac eros placerat, eu feugiat tellus ultrices. Aenean mollis laoreet libero eu imperdiet. Cras sed pulvinar mi, ac vehicula ligula. Vestibulum sit amet ex massa. In a egestas eros. - -Mauris pretium ipsum risus, venenatis cursus ante imperdiet id. Praesent eu turpis nec risus feugiat maximus ullamcorper ac lectus. Integer placerat at mi vel dapibus. Vestibulum fermentum turpis sit amet turpis viverra, id aliquet diam suscipit. Nam nec ex sed ante ullamcorper pharetra quis sit amet risus. Sed ac faucibus velit, id feugiat nibh. Nullam eget ipsum ex. Vivamus tincidunt non nunc non faucibus. Quisque bibendum viverra facilisis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at nisi hendrerit quam suscipit egestas. Curabitur laoreet maximus ultricies. Duis ut tellus ac augue molestie dictum. - -Suspendisse rhoncus iaculis erat, ut ullamcorper est tristique eget. Donec auctor nec risus at gravida. Vivamus volutpat vulputate tellus, vel ultricies eros suscipit eget. Ut pulvinar id mi eu tempus. Morbi malesuada augue in dui varius, nec blandit neque vehicula. Donec ornare nec nisl in mollis. Morbi enim nisi, rhoncus nec est id, dapibus tempus urna. Ut id elit a felis vestibulum consectetur. Duis lectus quam, pharetra sit amet diam sed, posuere vestibulum erat. Fusce vitae maximus massa. Nullam id metus tempus, iaculis risus eu, lobortis urna. Quisque in congue urna. Pellentesque placerat neque in augue dapibus, non varius ex malesuada. Curabitur ut eleifend libero. Fusce vitae ligula luctus, fermentum enim vitae, ultrices erat. - -Sed viverra augue turpis, scelerisque egestas sapien mattis eu. Duis laoreet magna at ex pharetra dapibus. Praesent eget odio vel quam venenatis dictum. Nulla in sollicitudin dolor. Mauris lobortis nec eros vel rhoncus. Vestibulum porta viverra venenatis. Curabitur vel scelerisque quam, a egestas velit. Praesent volutpat tincidunt magna at laoreet. - -Cras nec lorem odio. Pellentesque quis dui urna. Praesent at tellus ac lectus scelerisque placerat nec eu risus. Vestibulum sit amet mattis ligula. Vivamus sed nisi at leo elementum accumsan at sit amet arcu. Aenean mattis tellus nec leo gravida, eget hendrerit nisl faucibus. Mauris pellentesque luctus condimentum. Maecenas pretium sapien nunc, eget commodo dolor maximus id. Mauris vestibulum accumsan massa a dictum. Phasellus interdum quam ligula, ut maximus diam blandit aliquam. Nunc vitae ex eu erat condimentum consectetur. Maecenas interdum condimentum volutpat. - -Donec et enim a libero rutrum laoreet. Praesent a condimentum sem, at tincidunt quam. In vel molestie risus. Sed urna dui, molestie vitae mollis laoreet, tempor quis lectus. Praesent vitae auctor est, et aliquet nunc. Curabitur vulputate blandit nulla, at gravida metus. Maecenas gravida dui eu iaculis tristique. Pellentesque posuere turpis nec auctor eleifend. Suspendisse bibendum diam eu tellus lobortis, et laoreet quam congue. In hac habitasse platea dictumst. Morbi dictum neque velit, eget rutrum eros ultrices sit amet. - -Phasellus fermentum risus pharetra consectetur bibendum. Donec magna tortor, lacinia vitae nibh quis, aliquet pretium lorem. Donec turpis nisi, pretium eu enim volutpat, mattis malesuada augue. Nullam vel tellus iaculis, sollicitudin elit eget, tincidunt lacus. Fusce elementum elementum felis et iaculis. Suspendisse porta eros nec neque malesuada, in malesuada ante sollicitudin. Vivamus bibendum viverra molestie. - -Integer feugiat, erat nec convallis aliquam, velit felis congue erat, molestie eleifend tellus erat in tellus. Nunc et justo purus. Donec egestas fermentum dui non feugiat. Quisque in sapien sagittis, gravida quam id, iaculis lectus. Cras sagittis rhoncus bibendum. Fusce quis metus in velit scelerisque tincidunt at non ipsum. Vivamus efficitur ante eu odio vulputate, vitae ultricies risus vehicula. Proin eget odio eu sem tincidunt feugiat vel id lorem. - -Vestibulum sit amet nulla dignissim, euismod mi in, fermentum tortor. Donec ut aliquet libero, lacinia accumsan velit. Donec et nulla quam. Nullam laoreet odio nec nunc imperdiet, a congue eros venenatis. Quisque nec tellus sit amet neque interdum posuere. Duis quis mi gravida, tincidunt diam convallis, ultricies augue. Mauris consequat risus non porttitor congue. Ut in ligula consequat, viverra nunc a, eleifend enim. Duis ligula urna, imperdiet nec facilisis et, ornare eu ex. Proin lobortis lectus a lobortis porttitor. Nulla leo metus, egestas eu libero sed, pretium faucibus felis. Vestibulum non sem tortor. Nam cursus est leo. Vivamus luctus enim odio, non interdum sem dapibus a. Aenean accumsan consequat lectus in imperdiet. - -Donec vehicula laoreet ipsum in posuere. Quisque vel quam imperdiet, sollicitudin nisi quis, suscipit velit. Morbi id sodales mauris. Curabitur tellus arcu, feugiat sed dui sit amet, sodales sagittis libero. Aenean vel suscipit metus, non placerat leo. Vestibulum quis nulla elit. Proin scelerisque non ante ut commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. - -Sed non urna dolor. Suspendisse convallis mi porta pulvinar ultrices. Suspendisse quam ipsum, hendrerit non scelerisque molestie, interdum dictum nunc. Morbi condimentum condimentum turpis eu luctus. Pellentesque sagittis sollicitudin odio, sed ultricies felis ornare sit amet. Sed ultrices ex leo, a tincidunt nisl gravida sed. Nullam ornare accumsan porta. Praesent consectetur id est nec sollicitudin. - -In hac habitasse platea dictumst. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed sed ultrices nibh. Duis accumsan suscipit eros, a dictum odio tempus sit amet. Aenean imperdiet erat ac lacus finibus, scelerisque cursus massa imperdiet. Mauris molestie risus ut lacinia posuere. Nulla et sodales purus. Maecenas orci erat, placerat in tristique quis, placerat in mi. - -Donec sollicitudin pellentesque odio in feugiat. Morbi eu dolor ut mauris congue sollicitudin. Aliquam erat volutpat. Nulla id varius dui. Curabitur finibus urna ante, consectetur interdum nisi volutpat a. Quisque quis mi tristique, consequat tellus eget, rutrum sapien. Vivamus vitae tellus vulputate, rutrum ex eu, vulputate sem. Suspendisse viverra lorem tellus, vel interdum orci gravida quis. Ut laoreet arcu at mi ullamcorper finibus. Duis porta sagittis vestibulum. Sed commodo nisl vitae urna sollicitudin, nec lacinia est sodales. Curabitur imperdiet sodales dui sed iaculis. Sed ac tellus maximus, eleifend quam sit amet, feugiat elit. Aenean viverra, dui at mattis varius, est odio vestibulum sapien, sit amet mollis libero massa nec velit. Etiam quis sodales justo. - -Ut ultricies, sem eget sodales feugiat, nunc arcu congue elit, ac tempor justo massa nec purus. Maecenas enim nunc, pharetra eget dictum sit amet, tempus pellentesque velit. Suspendisse venenatis ligula in nulla mattis, et imperdiet ex tincidunt. Etiam vulputate, tellus et ultrices suscipit, enim velit laoreet massa, vitae congue odio enim ac urna. Morbi quam lorem, iaculis ac varius sagittis, euismod quis dolor. In ut dui eu purus feugiat consectetur. Vestibulum cursus velit quis lacus pellentesque iaculis. Cras in risus sed mauris porta rutrum. Nulla facilisi. Nullam eu bibendum est, non pellentesque lectus. Sed imperdiet feugiat lorem, quis convallis ante auctor in. Maecenas justo magna, scelerisque sit amet tellus eget, varius elementum risus. Duis placerat et quam sed varius. - -Duis nec nibh vitae nibh dignissim mollis quis sed felis. Curabitur vitae quam placerat, venenatis purus ut, euismod nisl. Curabitur porttitor nibh eu pulvinar ullamcorper. Suspendisse posuere nec ipsum ac dapibus. Cras convallis consectetur urna. Phasellus a nibh in dolor lacinia posuere id eget augue. In eu pharetra lorem, vitae cursus lacus. Aliquam tincidunt nibh lectus. Aenean facilisis ultricies posuere. Sed ut placerat orci. Curabitur scelerisque gravida blandit. Maecenas placerat ligula eget suscipit fringilla. Mauris a tortor justo. Aliquam hendrerit semper mollis. Phasellus et tincidunt libero. Etiam vel quam libero. - -Quisque aliquet tempor ex. Ut ante sem, vehicula at enim vel, gravida porta elit. Etiam vitae lacus a neque lobortis consectetur. Mauris sed interdum odio. Mauris elementum ex blandit tempor cursus. Integer in enim in leo viverra elementum. Fusce consectetur metus et sem rutrum, mattis euismod diam semper. Nunc sed ipsum vel urna consequat vehicula. Donec cursus pretium lorem, vestibulum pretium felis commodo sit amet. Nam blandit felis enim, eget gravida ex faucibus a. In nec neque massa. Etiam laoreet posuere ipsum. Praesent volutpat nunc dolor, ac vulputate magna facilisis non. Aenean congue turpis vel lectus sollicitudin tristique. Sed nec consequat purus, non vehicula quam. Etiam ultricies, est ac dictum tincidunt, turpis turpis pretium massa, a vulputate libero justo at nibh. - -Aliquam erat volutpat. Cras ultrices augue ac sollicitudin lobortis. Curabitur et aliquet purus. Duis feugiat semper facilisis. Phasellus lobortis cursus velit, a sollicitudin tortor. Nam feugiat sapien non dapibus condimentum. Morbi at mi bibendum, commodo quam at, laoreet enim. Integer eu ultrices enim. Sed vestibulum eu urna ut dictum. Curabitur at mattis leo, sed cursus massa. Aliquam porttitor, felis quis fermentum porttitor, justo velit feugiat nulla, eget condimentum sem dui ut sapien. - -In fringilla elit eu orci aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Ut eget fringilla tellus. Curabitur fermentum, mi et condimentum suscipit, elit neque bibendum dui, et hendrerit nunc metus id ipsum. Morbi placerat mi in hendrerit congue. Ut feugiat mauris eget scelerisque viverra. Vivamus sit amet erat dictum, sagittis lectus nec, pulvinar lorem. Sed non enim ac dui sollicitudin aliquet. Quisque ut lacus dolor. Fusce hendrerit malesuada euismod. Nulla faucibus vel mauris eu mollis. Mauris est diam, fringilla ac arcu feugiat, efficitur volutpat turpis. Aliquam venenatis cursus massa sed porttitor. Ut ac finibus enim, in tincidunt sapien. - -Nunc faucibus semper turpis a lacinia. Phasellus gravida, libero vel pulvinar ornare, ex sem tincidunt lectus, sit amet convallis augue risus at tortor. Quisque sit amet ipsum id nulla posuere vestibulum. Pellentesque scelerisque mauris vel leo viverra sodales. Nulla viverra aliquam ex, ut rutrum enim fermentum venenatis. Aenean eget dapibus ex, eget faucibus metus. Vestibulum volutpat leo in diam semper, eget porta magna suscipit. Sed sit amet nulla blandit, aliquam dolor ac, gravida velit. Sed vel velit viverra, maximus est id, convallis justo. - -Curabitur nulla ante, vulputate at libero vel, ullamcorper rutrum nibh. Pellentesque porttitor eu mauris id mattis. Duis vulputate augue elit, eget interdum justo pretium vel. Maecenas eu vulputate arcu, eget posuere purus. Suspendisse viverra a velit dictum eleifend. Suspendisse vitae dapibus diam. Donec vehicula justo in ante interdum, eu luctus diam placerat. Vivamus convallis ipsum eu orci suscipit, sed fermentum enim euismod. Maecenas faucibus elit vitae ex ornare tristique. Donec vestibulum nec elit sit amet porttitor. Aenean tempor lectus eget tortor hendrerit luctus. Nullam interdum vitae lectus vel feugiat. Cras in risus non magna consectetur lobortis. Sed faucibus enim quis gravida convallis. - -Phasellus eget massa sit amet libero ultrices suscipit. Vivamus at risus sapien. Nam mollis nunc eget velit dictum maximus. Sed pellentesque, nunc ac fringilla lacinia, quam enim mattis ex, sed euismod tortor metus eu neque. Ut mattis nisl ut lectus rhoncus, sodales bibendum eros porta. Nulla porttitor enim nec diam sagittis, eget porta velit efficitur. Vestibulum ultricies eros neque. Phasellus rutrum suscipit enim, in interdum ante gravida vitae. Sed in sagittis diam, non commodo velit. - -Morbi hendrerit odio orci, nec tincidunt odio rhoncus nec. Mauris neque velit, vehicula a lorem at, suscipit tristique dui. Sed finibus, nisl in mattis convallis, turpis neque sodales lacus, eu porta enim magna non diam. Nam commodo sodales risus consectetur malesuada. In eget elementum justo. Phasellus sit amet massa imperdiet, dapibus nunc sit amet, suscipit orci. Fusce condimentum laoreet feugiat. Ut ut viverra ante. Praesent bibendum interdum commodo. Nulla mollis nisi a est ornare volutpat. Sed at ligula eu nisi dapibus tempus. Proin cursus vestibulum justo, nec efficitur justo dignissim vel. Nunc quis maximus eros. - -Cras viverra, diam a tristique mattis, libero felis vulputate tellus, a ornare felis leo a dui. Nulla ante nulla, finibus ut tellus ut, blandit pharetra nibh. Proin eleifend fermentum ex, eget auctor libero vulputate in. Nullam ultricies, mauris placerat pretium placerat, leo urna lobortis leo, vel placerat arcu libero sed mauris. Aliquam mauris ligula, ornare at urna at, eleifend gravida ligula. Vestibulum consectetur ut nulla non scelerisque. Donec ornare, sem nec elementum aliquam, urna nulla bibendum metus, eu euismod dui ligula ac est. Fusce laoreet erat eu ex lobortis, quis bibendum ligula interdum. Sed vel mi erat. Vivamus id lacus ac enim mattis tempor. Nunc ultricies pellentesque enim sed euismod. Fusce tincidunt convallis elit quis aliquam. Mauris nulla ipsum, sollicitudin quis diam ac, feugiat volutpat tellus. In nibh nibh, vulputate quis tincidunt quis, pulvinar eget magna. Pellentesque quis finibus dolor. Suspendisse viverra vitae lectus non eleifend. - -Nunc ut orci et sapien maximus semper. Nulla dignissim sem urna, ac varius lectus ultricies id. Quisque aliquet pulvinar pretium. In ultricies molestie tellus vehicula porta. Nam enim lorem, aliquam eget ex et, hendrerit volutpat quam. Maecenas diam lacus, pellentesque eget tempus ac, pharetra eu elit. Donec vel eros a sem facilisis vulputate. Nullam ac nisi vulputate, laoreet nisl ac, eleifend sem. Nullam mi massa, rhoncus sed pharetra interdum, tincidunt eget nunc. Aliquam viverra mattis posuere. Mauris et dui sed nisl sollicitudin fermentum quis ut arcu. Nam placerat eget orci at tincidunt. Curabitur vel turpis metus. Phasellus nibh nulla, fermentum scelerisque sem vel, gravida tincidunt velit. Pellentesque vel quam tempor, finibus massa pellentesque, condimentum dui. - -Donec at mattis neque. Etiam velit diam, consequat auctor mauris id, hendrerit faucibus metus. Maecenas ullamcorper eros a est sodales, ac consectetur odio scelerisque. Donec leo metus, imperdiet at pellentesque vel, feugiat id erat. Suspendisse at magna enim. Vestibulum placerat sodales lorem id sollicitudin. Aenean at euismod ligula, eget mollis diam. Phasellus pulvinar, orci nec pretium condimentum, est erat facilisis purus, quis feugiat augue elit aliquam nulla. Aenean vitae tortor id risus congue tincidunt. Sed dolor enim, mattis a ullamcorper id, volutpat ac leo. - -Proin vehicula feugiat augue, id feugiat quam sodales quis. Donec et ultricies massa, a lacinia nulla. Duis aliquam augue ornare euismod viverra. Ut lectus risus, rutrum sit amet efficitur a, luctus nec nisl. Cras volutpat ullamcorper congue. Sed vitae odio metus. Phasellus aliquet euismod varius. - -Nullam sem ex, malesuada ut magna ut, pretium mollis arcu. Nam porttitor eros cursus mi lacinia faucibus. Suspendisse aliquet eleifend iaculis. Maecenas sit amet viverra tortor. Nunc a mollis risus. Etiam tempus dolor in tortor malesuada mattis. Ut tincidunt venenatis est sit amet dignissim. Vestibulum massa enim, tristique sed scelerisque eu, fringilla ac velit. Donec efficitur quis urna sit amet malesuada. Vestibulum consequat ac ligula in dapibus. Maecenas massa massa, molestie non posuere nec, elementum ut magna. In nisi erat, mollis non venenatis eu, faucibus in justo. Morbi gravida non ex non egestas. Pellentesque finibus laoreet diam, eu commodo augue congue vitae. - -Aenean sem mi, ullamcorper dapibus lobortis vitae, interdum tincidunt tortor. Vivamus eget vulputate libero. Ut bibendum posuere lectus, vel tincidunt tortor aliquet at. Phasellus malesuada orci et bibendum accumsan. Aliquam quis libero vel leo mollis porta. Sed sagittis leo ac lacus dictum, ac malesuada elit finibus. Suspendisse pharetra luctus commodo. Vivamus ultricies a odio non interdum. Vivamus scelerisque tincidunt turpis quis tempor. Pellentesque tortor ligula, varius non nunc eu, blandit sollicitudin neque. Nunc imperdiet, diam et tristique luctus, ipsum ex condimentum nunc, sit amet aliquam justo velit sed libero. Duis vel suscipit ligula. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed tincidunt neque vel massa ultricies, id dictum leo consequat. Curabitur lobortis ultricies tellus, eget mattis nisl aliquam sit amet. - -Proin at suscipit justo. Vivamus ut vestibulum nisl. Pellentesque enim odio, pharetra non magna sed, efficitur auctor magna. Praesent tincidunt ante quis ante hendrerit viverra. Pellentesque vel ipsum id magna vulputate efficitur. Sed nec neque accumsan, pulvinar sapien quis, euismod mauris. Donec condimentum laoreet sapien quis gravida. Quisque sed mattis purus. Vestibulum placerat vel neque maximus scelerisque. - -Vestibulum mattis quam quis efficitur elementum. Duis dictum dolor ac scelerisque commodo. Fusce sollicitudin nisi sit amet dictum placerat. Suspendisse euismod pharetra eleifend. In eros nisl, porttitor sed mauris at, consectetur aliquet mauris. Donec euismod viverra neque sed fermentum. Phasellus libero magna, accumsan ut ultricies vitae, dignissim eget metus. Donec tellus turpis, interdum eget maximus nec, hendrerit eget massa. Curabitur auctor ligula in iaculis auctor. In ultrices quam suscipit cursus finibus. Aenean id mi at dolor interdum iaculis vitae ut lorem. Nullam sed nibh fringilla, lacinia odio nec, placerat erat. In dui libero, viverra ac viverra ac, pellentesque sit amet turpis. - -Nulla in enim ex. Sed feugiat est et consectetur venenatis. Cras varius facilisis dui vel convallis. Vestibulum et elit eget tellus feugiat pellentesque. In ut ante eu purus aliquet posuere. Nulla nec ornare sem, sed luctus lorem. Nam varius iaculis odio, eget faucibus nisl ullamcorper in. Sed eget cursus felis, nec efficitur nisi. - -Vivamus commodo et sem quis pulvinar. Pellentesque libero ante, venenatis vitae ligula sit amet, ornare sollicitudin nulla. Mauris eget tellus hendrerit, pulvinar metus quis, tempor nisi. Proin magna ex, laoreet sed tortor quis, varius fermentum enim. Integer eu dolor dictum, vulputate tortor et, aliquet ligula. Vestibulum vitae justo id mauris luctus sollicitudin. Suspendisse eget auctor neque, sodales egestas lorem. Vestibulum lacinia egestas metus vitae euismod. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ex tellus, volutpat nec pulvinar sit amet, condimentum vitae dui. Curabitur vel felis sodales, lacinia nunc iaculis, ullamcorper augue. Pellentesque consequat dolor quis eros efficitur malesuada. Nulla ut malesuada lectus. - -Morbi et tristique ante. Aliquam erat volutpat. Vivamus vitae dui nec turpis pellentesque fermentum. Quisque eget velit massa. Pellentesque tristique aliquam nisl, eu sollicitudin justo venenatis sed. Duis eleifend sem eros, ut aliquam libero porttitor id. Sed non nunc consequat, rhoncus diam eu, commodo erat. Praesent fermentum in lectus id blandit. Donec quis ipsum at justo volutpat finibus. Nulla blandit justo nulla, at mollis lacus consequat eget. Aenean sollicitudin quis eros ut ullamcorper. - -Pellentesque venenatis nulla ut mi aliquet feugiat. Cras semper vel magna nec pharetra. Integer mattis felis et sapien commodo imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis quis luctus felis. Vestibulum justo nibh, aliquam non lectus vitae, molestie placerat justo. Donec lorem nibh, gravida sit amet hendrerit ac, maximus id ipsum. Nunc ac libero sodales risus eleifend sagittis. Phasellus est massa, lobortis elementum ex sed, scelerisque consectetur neque. Nunc faucibus neque id lorem malesuada, eget convallis ex mattis. - -Sed turpis tortor, fermentum non turpis id, posuere varius nibh. Donec iaculis lorem dui. Etiam eros ante, sodales eget venenatis at, consectetur eget risus. Curabitur non aliquam ante, a pretium justo. Maecenas tempor nisl tortor, vitae dictum nisi ultrices eu. Duis eget dui ultrices, porttitor lacus sed, lobortis purus. Quisque mattis elit nec neque sagittis, sed commodo leo blandit. Mauris sodales interdum eleifend. Vestibulum condimentum consectetur augue, id luctus diam convallis et. - -Nunc suscipit risus in justo accumsan, a placerat magna tincidunt. Proin a nisl ipsum. Sed libero dui, tristique in augue quis, auctor tristique risus. Sed porttitor ex augue, eu porta augue molestie a. Duis rhoncus purus libero, eu tempus turpis condimentum at. Sed mollis nisi id lectus placerat tincidunt. Maecenas non scelerisque elit, quis rutrum orci. Donec in tellus pharetra urna ornare lobortis. Phasellus id risus at nisi varius rutrum eu ut turpis. - -Duis dictum justo quis nisl porta, eget tincidunt magna suscipit. Sed velit massa, ullamcorper eu sodales ac, pretium a massa. Duis et rutrum tortor. Nulla accumsan hendrerit sapien, cursus volutpat eros egestas eget. Donec sollicitudin at ante quis sollicitudin. Aenean blandit feugiat diam, id feugiat eros faucibus eget. Donec viverra dolor vel justo scelerisque dignissim. Nulla semper sem nunc, rhoncus semper tellus ultricies sed. Duis in ornare diam. Donec vehicula feugiat varius. Maecenas ut suscipit est. Vivamus sem sem, finibus at dolor sit amet, euismod dapibus ligula. Vestibulum fringilla odio dapibus, congue massa eget, congue sem. Donec feugiat magna eget tortor lacinia scelerisque non et ipsum. - -Suspendisse potenti. Nunc convallis sollicitudin ex eget venenatis. Sed iaculis nibh ex, vel ornare ligula congue dignissim. Quisque sollicitudin dolor ac dui vestibulum, sit amet molestie nisi aliquet. Donec at risus felis. Aenean sollicitudin metus a feugiat porta. Aenean a tortor ut dolor cursus sagittis. Vivamus consectetur porttitor nunc in facilisis. Proin sit amet mi vel lectus consectetur ultrices. - -Sed cursus lectus vitae nunc tristique, nec commodo turpis dapibus. Pellentesque luctus ex id facilisis ornare. Morbi quis placerat dolor. Donec in lectus in arcu mattis porttitor ac sit amet metus. Cras congue mauris non risus sodales, vitae feugiat ipsum bibendum. Nulla venenatis urna sed libero elementum, a cursus lorem commodo. Mauris faucibus lobortis eros nec commodo. - -Nullam suscipit ligula ullamcorper lorem commodo blandit. Nulla porta nibh quis pulvinar placerat. Vivamus eu arcu justo. Vestibulum imperdiet est ut fermentum porttitor. Pellentesque consectetur libero in sapien efficitur scelerisque. Curabitur ac erat sit amet odio aliquet dignissim. Pellentesque mi sem, rhoncus et luctus at, porttitor rutrum lectus. Vestibulum sollicitudin sollicitudin suscipit. Aenean efficitur dolor non ultrices imperdiet. Donec vel sem ex. - -Sed convallis mauris aliquam rutrum cursus. Ut tempor porttitor sodales. Etiam eu risus ac augue gravida egestas et eu dolor. Proin id magna ex. Suspendisse quis lectus quis lorem ultricies tempus. Donec porttitor velit vitae tincidunt faucibus. Aliquam vitae semper nisi. Morbi ultrices, leo non pretium dapibus, dui libero pellentesque ex, vel placerat enim ante vitae dui. Nunc varius, sem sit amet sagittis lobortis, lectus odio scelerisque mauris, ut vestibulum orci magna quis neque. Sed id congue justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris congue nisi est, malesuada mollis elit tincidunt sed. Curabitur sed ex sit amet felis tristique elementum vitae vel nibh. - -Etiam mollis pretium lobortis. Mauris augue lacus, efficitur at lacus sed, mollis tincidunt lectus. Aliquam erat volutpat. Donec at euismod elit, et mattis felis. Sed id lobortis urna. Morbi imperdiet vestibulum leo, sed maximus leo blandit eu. Aliquam semper lorem neque, nec euismod turpis mattis mollis. Quisque lobortis urna ultrices odio pretium, ac venenatis orci faucibus. Suspendisse bibendum odio ligula, sed lobortis massa pharetra nec. Donec turpis justo, iaculis at dictum ac, finibus eu libero. Maecenas quis porttitor mi, sit amet aliquet neque. - -Vivamus auctor vulputate ante, at egestas lorem. Donec eu risus in nulla mollis ultricies at et urna. Duis accumsan porta egestas. Ut vel euismod augue. Fusce convallis nulla ante, nec fringilla velit aliquet at. Nam malesuada dapibus ligula, a aliquam nibh scelerisque ac. Praesent malesuada neque et pellentesque interdum. Curabitur volutpat at turpis vitae tristique. Vivamus porttitor semper congue. Quisque suscipit lacus mi, rhoncus ultrices tortor auctor quis. Maecenas neque neque, molestie ac facilisis eget, luctus ac lorem. In ut odio ut lacus suscipit pulvinar vitae sed elit. Nulla imperdiet, sem quis euismod sagittis, dui erat luctus dolor, faucibus faucibus erat sem eget nunc. Nam accumsan placerat malesuada. Maecenas convallis finibus pulvinar. - -Cras at placerat tortor. Morbi facilisis auctor felis sit amet molestie. Donec sodales sed lorem vitae suscipit. Etiam fermentum pharetra ipsum, nec luctus orci gravida eu. Pellentesque gravida, est non condimentum tempus, mauris ligula molestie est, in congue dolor nisl vel sapien. Duis congue tempor augue, id rutrum eros porta dapibus. Etiam rutrum eget est eget vestibulum. Aenean mollis arcu vel consequat varius. Praesent at condimentum felis. Duis nec interdum nisl. Donec commodo lorem sed sapien scelerisque malesuada non eu urna. In blandit non ipsum at porta. Nam lobortis leo vitae dui auctor, non feugiat quam bibendum. Donec auctor lectus sagittis laoreet maximus. Maecenas rhoncus laoreet porttitor. Vestibulum porttitor augue ut lectus hendrerit, eget posuere mi gravida. - -Sed mattis ex in erat pulvinar, eu imperdiet magna dapibus. Etiam nisi nibh, tempus non tellus sit amet, mattis tempor odio. Quisque nec lorem feugiat, lobortis odio et, commodo nunc. Maecenas semper purus nisi, nec vehicula nibh eleifend vitae. Nulla fermentum a lectus at maximus. Phasellus finibus metus non euismod ultrices. Etiam a pulvinar ante. Quisque convallis nec metus sit amet facilisis. Praesent laoreet massa et sollicitudin laoreet. Vestibulum in mauris aliquet, convallis mi ut, elementum purus. Nulla purus nulla, sodales at hendrerit quis, tempus sed lectus. - -Nam ut laoreet neque, ut maximus nibh. Maecenas quis justo pellentesque, sollicitudin elit at, venenatis velit. Aenean nunc velit, vehicula scelerisque odio at, consectetur laoreet purus. Duis dui purus, malesuada quis ipsum sit amet, tempor interdum libero. Curabitur porta scelerisque sapien, vitae cursus diam condimentum eu. Phasellus sed orci quam. Nullam vitae dui quis purus tincidunt vestibulum. Curabitur quis nulla porta, cursus arcu non, auctor enim. Etiam sollicitudin ex id sem vehicula mollis. Morbi viverra laoreet tincidunt. Praesent ut semper dui. Nam sit amet pretium neque. Mauris vitae luctus diam, in lacinia purus. Maecenas ut placerat justo, ut porta felis. Integer eu mauris ante. - -Aenean porttitor tellus diam, tempor consequat metus efficitur id. Suspendisse ut felis at erat tempor dictum at nec sapien. Sed vestibulum interdum felis, ac mattis mauris porta in. Nunc et condimentum massa. Sed cursus dictum justo et luctus. Integer convallis enim nisl, a rutrum lectus ultricies in. Donec dapibus lacus at nulla dapibus, id sollicitudin velit hendrerit. Fusce a magna at orci mollis rutrum ac a dolor. Aliquam erat volutpat. Morbi varius porta nunc, sit amet sodales ex hendrerit commodo. Donec tincidunt tortor sapien, vitae egestas sapien vehicula eget. - -Suspendisse potenti. Donec pulvinar felis nec leo malesuada interdum. Integer posuere placerat maximus. Donec nibh ipsum, tincidunt vitae luctus vitae, bibendum at leo. Sed cursus nisl ut ex faucibus aliquet sed nec eros. Curabitur molestie posuere felis. Integer faucibus velit eget consequat iaculis. Mauris sed vulputate odio. Phasellus maximus, elit a pharetra egestas, lorem magna semper tellus, vestibulum semper diam felis at sapien. Suspendisse facilisis, nisl sit amet euismod vehicula, libero nulla vehicula dolor, quis fermentum nibh elit sit amet diam. - -Morbi lorem enim, euismod eu varius ut, scelerisque quis odio. Nam tempus vitae eros id molestie. Nunc pretium in nulla eget accumsan. Quisque mattis est ut semper aliquet. Maecenas eget diam elementum, fermentum ipsum a, euismod sapien. Duis quam ligula, cursus et velit nec, ullamcorper tincidunt magna. Donec vulputate nisl est, et ullamcorper urna tempor sit amet. - -Proin lacinia dui non turpis congue pretium. Morbi posuere metus vel purus imperdiet interdum. Morbi venenatis vel eros non ultricies. Nulla vel semper elit. Ut quis purus tincidunt, auctor justo ut, faucibus turpis. Proin quis mattis erat, at faucibus ligula. Mauris in mauris enim. Donec facilisis enim at est feugiat hendrerit. Nam vel nisi lorem. Fusce ultricies convallis diam, in feugiat tortor luctus quis. Donec tempor, leo vitae volutpat aliquam, magna elit feugiat leo, quis placerat sapien felis eget arcu. Donec ornare fermentum eleifend. Integer a est orci. - -Proin rhoncus egestas leo. Nulla ultricies porta elit quis ornare. Nunc fermentum interdum vehicula. In in ligula lorem. Donec nec arcu sit amet orci lobortis iaculis. Mauris at mollis erat, sit amet mollis tortor. Mauris laoreet justo ullamcorper porttitor auctor. Aenean sit amet aliquam lectus, id fermentum eros. Praesent urna sem, vehicula ac fermentum id, dapibus ut purus. Vestibulum vitae tempus nunc. Donec at nunc ornare metus volutpat porta at eget magna. Donec varius aliquet metus, eu lobortis risus aliquam sed. Ut dapibus fermentum velit, ac tincidunt libero faucibus at. - -In in purus auctor, feugiat massa quis, facilisis nisi. Donec dolor purus, gravida eget dolor ac, porttitor imperdiet urna. Donec faucibus placerat erat, a sagittis ante finibus ac. Sed venenatis dignissim elit, in iaculis felis posuere faucibus. Praesent sed viverra dolor. Mauris sed nulla consectetur nunc laoreet molestie in ut metus. Proin ac ex sit amet magna vulputate hendrerit ac condimentum urna. Proin ligula metus, gravida et sollicitudin facilisis, iaculis ut odio. Cras tincidunt urna et augue varius, ut facilisis urna consequat. Aenean vehicula finibus quam. Ut iaculis eu diam ac mollis. Nam mi lorem, tristique eget varius at, sodales at urna. - -Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vitae dictum erat, et auctor ipsum. Nullam nunc nunc, sollicitudin quis magna a, vestibulum fermentum mauris. Praesent at erat dolor. Proin laoreet tristique nulla vel efficitur. Nam sed ultrices nibh, id rutrum nunc. Curabitur eleifend a erat sit amet sollicitudin. Nullam metus quam, laoreet vitae dapibus id, placerat sed leo. Aliquam erat volutpat. Donec turpis nisl, cursus eu ex sit amet, lacinia pellentesque nisl. Sed id ipsum massa. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec interdum scelerisque lorem eu mattis. - -Vivamus ac tristique massa, nec facilisis nisl. Nam ipsum neque, tincidunt vel urna in, cursus imperdiet enim. Nam pellentesque egestas tempus. Morbi facilisis imperdiet libero vitae fringilla. Nam lacinia ligula at sapien facilisis malesuada. Nullam accumsan pulvinar sem, et cursus libero porta sit amet. Curabitur vulputate erat elit, ut pulvinar erat maximus vel. - -Cras aliquet metus ut purus sagittis, vel venenatis ante consectetur. Pellentesque nulla lacus, viverra viverra mattis non, placerat vitae nibh. Donec enim turpis, accumsan sit amet tincidunt eu, imperdiet non metus. Morbi ipsum eros, tincidunt vel est ac, tristique porttitor nibh. Praesent ut ullamcorper mauris. Sed laoreet sit amet diam congue venenatis. Integer porta purus nec orci sagittis posuere. - -Donec vehicula mauris eget lacus mollis venenatis et sed nibh. Nam sodales ligula ipsum, scelerisque lacinia ligula sagittis in. Nam sit amet ipsum at erat malesuada congue. Aenean ut sollicitudin sapien. Etiam at tempor odio. Mauris vitae purus ut magna suscipit consequat. Vivamus quis sapien neque. Nulla vulputate sem sit amet massa pellentesque, eleifend tristique ligula egestas. Suspendisse tincidunt gravida mi, in pulvinar lectus egestas non. Aenean imperdiet ex sit amet nunc sollicitudin porta. Integer justo odio, ultricies at interdum in, rhoncus vitae sem. Sed porttitor arcu quis purus aliquet hendrerit. Praesent tempor tortor at dolor dictum pulvinar. Nulla aliquet nunc non ligula scelerisque accumsan. Donec nulla justo, congue vitae massa in, faucibus hendrerit magna. Donec non egestas purus. - -öäüß Vivamus iaculis, lacus efficitur faucibus porta, dui nulla facilisis ligula, ut sodales odio nunc id sapien. Cras viverra auctor ipsum, dapibus mattis neque dictum sed. Sed convallis fermentum molestie. Nulla facilisi turpis duis. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/small.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/small.txt deleted file mode 100644 index da2e8042fb..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/small.txt +++ /dev/null @@ -1 +0,0 @@ -Small File \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/small_umlaut.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/small_umlaut.txt deleted file mode 100644 index a01c1626b3..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/small_umlaut.txt +++ /dev/null @@ -1 +0,0 @@ -Small File with Ümlaut \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some.utf16le b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some.utf16le deleted file mode 100644 index 41c12add67..0000000000 Binary files a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some.utf16le and /dev/null differ diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_big5.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_big5.txt deleted file mode 100644 index b9e2570fef..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_big5.txt +++ /dev/null @@ -1 +0,0 @@ -¤¤¤åabc \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_cp1252.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_cp1252.txt deleted file mode 100644 index 2ea52dc709..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_cp1252.txt +++ /dev/null @@ -1,3 +0,0 @@ -ObjectCount = LoadObjects("Öffentlicher Ordner"); - -Private = "Persönliche Information" diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_cyrillic.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_cyrillic.txt deleted file mode 100644 index f8ee306671..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_cyrillic.txt +++ /dev/null @@ -1 +0,0 @@ -€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯àáâãäåæçèéêëìíîï \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_gbk.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_gbk.txt deleted file mode 100644 index eab73d1951..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_gbk.txt +++ /dev/null @@ -1 +0,0 @@ -Öйúabc \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_shiftjis.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_shiftjis.txt deleted file mode 100644 index efa955b3ec..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_shiftjis.txt +++ /dev/null @@ -1 +0,0 @@ -’†•¶abc \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_small_cp1252.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_small_cp1252.txt deleted file mode 100644 index 0ad555462f..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_small_cp1252.txt +++ /dev/null @@ -1 +0,0 @@ -Private = "Persönlicheß Information" \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_utf16le.css b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_utf16le.css deleted file mode 100644 index aea04aa2cd..0000000000 Binary files a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_utf16le.css and /dev/null differ diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_utf8_bom.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_utf8_bom.txt deleted file mode 100644 index 36cdec0c88..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/some_utf8_bom.txt +++ /dev/null @@ -1 +0,0 @@ -This is some UTF 8 with BOM file. \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/utf16_be_nobom.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/utf16_be_nobom.txt deleted file mode 100644 index 63c2941209..0000000000 Binary files a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/utf16_be_nobom.txt and /dev/null differ diff --git a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/utf16_le_nobom.txt b/src/vs/workbench/services/textfile/test/electron-browser/fixtures/utf16_le_nobom.txt deleted file mode 100644 index 7b94ff215b..0000000000 Binary files a/src/vs/workbench/services/textfile/test/electron-browser/fixtures/utf16_le_nobom.txt and /dev/null differ diff --git a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts deleted file mode 100644 index 5f2f6ba52c..0000000000 --- a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { tmpdir } from 'os'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IFileService } from 'vs/platform/files/common/files'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { Schemas } from 'vs/base/common/network'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { Promises } from 'vs/base/node/pfs'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { FileService } from 'vs/platform/files/common/fileService'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { flakySuite, getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; -import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; -import { detectEncodingByBOM } from 'vs/workbench/services/textfile/test/node/encoding/encoding.test'; -import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/electron-browser/workbenchTestServices'; -import createSuite from 'vs/workbench/services/textfile/test/common/textFileService.io.test'; -import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; - -flakySuite('Files - NativeTextFileService i/o', function () { - const disposables = new DisposableStore(); - - let service: ITextFileService; - let testDir: string; - - function readFile(path: string): Promise; - function readFile(path: string, encoding: BufferEncoding): Promise; - function readFile(path: string, encoding?: BufferEncoding): Promise { - return Promises.readFile(path, encoding); - } - - createSuite({ - setup: async () => { - const instantiationService = workbenchInstantiationService(disposables); - - const logService = new NullLogService(); - const fileService = new FileService(logService); - - const fileProvider = new DiskFileSystemProvider(logService); - disposables.add(fileService.registerProvider(Schemas.file, fileProvider)); - disposables.add(fileProvider); - - const collection = new ServiceCollection(); - collection.set(IFileService, fileService); - - collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new WorkingCopyService(), instantiationService, new UriIdentityService(fileService))); - - service = instantiationService.createChild(collection).createInstance(TestNativeTextFileServiceWithEncodingOverrides); - - testDir = getRandomTestPath(tmpdir(), 'vsctests', 'textfileservice'); - const sourceDir = getPathFromAmdModule(require, './fixtures'); - - await Promises.copy(sourceDir, testDir, { preserveSymlinks: false }); - - return { service, testDir }; - }, - - teardown: () => { - (service.files).dispose(); - - disposables.clear(); - - return Promises.rm(testDir); - }, - - exists: Promises.exists, - stat: Promises.stat, - readFile, - detectEncodingByBOM - }); -}); diff --git a/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.io.test.ts b/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.io.test.ts new file mode 100644 index 0000000000..6f3239fc94 --- /dev/null +++ b/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.io.test.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { NullLogService } from 'vs/platform/log/common/log'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { Schemas } from 'vs/base/common/network'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IFileService, IStat } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { join } from 'vs/base/common/path'; +import { UTF16le, detectEncodingByBOMFromBuffer, UTF8_with_bom, UTF16be, toCanonicalName } from 'vs/workbench/services/textfile/common/encoding'; +import { VSBuffer } from 'vs/base/common/buffer'; +import files from 'vs/workbench/services/textfile/test/common/fixtures/files'; +import createSuite from 'vs/workbench/services/textfile/test/common/textFileService.io.test'; +import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; +import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; +import { TestInMemoryFileSystemProvider } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestNativeTextFileServiceWithEncodingOverrides, workbenchInstantiationService } from 'vs/workbench/test/electron-sandbox/workbenchTestServices'; + +suite('Files - NativeTextFileService i/o', function () { + const disposables = new DisposableStore(); + + let service: ITextFileService; + let fileProvider: TestInMemoryFileSystemProvider; + const testDir = 'test'; + + createSuite({ + setup: async () => { + const instantiationService = workbenchInstantiationService(undefined, disposables); + + const logService = new NullLogService(); + const fileService = new FileService(logService); + + fileProvider = new TestInMemoryFileSystemProvider(); + disposables.add(fileService.registerProvider(Schemas.file, fileProvider)); + disposables.add(fileProvider); + + const collection = new ServiceCollection(); + collection.set(IFileService, fileService); + + collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new WorkingCopyService(), instantiationService, new UriIdentityService(fileService))); + + service = instantiationService.createChild(collection).createInstance(TestNativeTextFileServiceWithEncodingOverrides); + + await fileProvider.mkdir(URI.file(testDir)); + for (const fileName in files) { + await fileProvider.writeFile( + URI.file(join(testDir, fileName)), + files[fileName], + { create: true, overwrite: false, unlock: false, atomic: false } + ); + } + + return { service, testDir }; + }, + + teardown: async () => { + (service.files).dispose(); + + disposables.clear(); + }, + + exists, + stat, + readFile, + detectEncodingByBOM + }); + + async function exists(fsPath: string): Promise { + try { + await fileProvider.readFile(URI.file(fsPath)); + return true; + } + catch (e) { + return false; + } + } + + async function readFile(fsPath: string): Promise; + async function readFile(fsPath: string, encoding: string): Promise; + async function readFile(fsPath: string, encoding?: string): Promise { + const file = await fileProvider.readFile(URI.file(fsPath)); + + if (!encoding) { + return VSBuffer.wrap(file); + } + + return new TextDecoder(toCanonicalName(encoding)).decode(file); + } + + async function stat(fsPath: string): Promise { + return fileProvider.stat(URI.file(fsPath)); + } + + async function detectEncodingByBOM(fsPath: string): Promise { + try { + const buffer = await readFile(fsPath); + + return detectEncodingByBOMFromBuffer(buffer.slice(0, 3), 3); + } catch (error) { + return null; // ignore errors (like file not found) + } + } +}); diff --git a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts b/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.test.ts similarity index 92% rename from src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts rename to src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.test.ts index 963b929640..8482e2d55e 100644 --- a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/electron-sandbox/nativeTextFileService.test.ts @@ -12,7 +12,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; -import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { TestNativeTextFileServiceWithEncodingOverrides, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/electron-sandbox/workbenchTestServices'; import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; @@ -28,7 +28,7 @@ suite('Files - NativeTextFileService', function () { let instantiationService: IInstantiationService; setup(() => { - instantiationService = workbenchInstantiationService(disposables); + instantiationService = workbenchInstantiationService(undefined, disposables); const logService = new NullLogService(); const fileService = new FileService(logService); diff --git a/src/vs/workbench/services/textfile/test/node/encoding/encoding.integrationTest.ts b/src/vs/workbench/services/textfile/test/node/encoding/encoding.integrationTest.ts index 5cf70a9a7d..608cd49adb 100644 --- a/src/vs/workbench/services/textfile/test/node/encoding/encoding.integrationTest.ts +++ b/src/vs/workbench/services/textfile/test/node/encoding/encoding.integrationTest.ts @@ -5,11 +5,22 @@ import * as assert from 'assert'; import * as terminalEncoding from 'vs/base/node/terminalEncoding'; +import * as encoding from 'vs/workbench/services/textfile/common/encoding'; -suite('Encoding', () => { +suite('Encoding', function () { + + this.timeout(10000); test('resolve terminal encoding (detect)', async function () { const enc = await terminalEncoding.resolveTerminalEncoding(); assert.ok(enc.length > 0); }); + + test('resolve terminal encoding (environment)', async function () { + process.env['VSCODE_CLI_ENCODING'] = 'utf16le'; + + const enc = await terminalEncoding.resolveTerminalEncoding(); + assert.ok(await encoding.encodingExists(enc)); + assert.strictEqual(enc, 'utf16le'); + }); }); diff --git a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts index 381359a825..c843b040b7 100644 --- a/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts +++ b/src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts @@ -6,12 +6,11 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as encoding from 'vs/workbench/services/textfile/common/encoding'; -import * as terminalEncoding from 'vs/base/node/terminalEncoding'; import * as streams from 'vs/base/common/stream'; import * as iconv from '@vscode/iconv-lite-umd'; -import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer'; import { splitLines } from 'vs/base/common/strings'; +import { FileAccess } from 'vs/base/common/network'; export async function detectEncodingByBOM(file: string): Promise { try { @@ -80,62 +79,49 @@ function readExactlyByFile(file: string, totalBytes: number): Promise { test('detectBOM does not return error for non existing file', async () => { - const file = getPathFromAmdModule(require, './fixtures/not-exist.css'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/not-exist.css').fsPath; const detectedEncoding = await detectEncodingByBOM(file); assert.strictEqual(detectedEncoding, null); }); test('detectBOM UTF-8', async () => { - const file = getPathFromAmdModule(require, './fixtures/some_utf8.css'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_utf8.css').fsPath; const detectedEncoding = await detectEncodingByBOM(file); assert.strictEqual(detectedEncoding, 'utf8bom'); }); test('detectBOM UTF-16 LE', async () => { - const file = getPathFromAmdModule(require, './fixtures/some_utf16le.css'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_utf16le.css').fsPath; const detectedEncoding = await detectEncodingByBOM(file); assert.strictEqual(detectedEncoding, 'utf16le'); }); test('detectBOM UTF-16 BE', async () => { - const file = getPathFromAmdModule(require, './fixtures/some_utf16be.css'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_utf16be.css').fsPath; const detectedEncoding = await detectEncodingByBOM(file); assert.strictEqual(detectedEncoding, 'utf16be'); }); test('detectBOM ANSI', async function () { - const file = getPathFromAmdModule(require, './fixtures/some_ansi.css'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_ansi.css').fsPath; const detectedEncoding = await detectEncodingByBOM(file); assert.strictEqual(detectedEncoding, null); }); - test('detectBOM ANSI', async function () { - const file = getPathFromAmdModule(require, './fixtures/empty.txt'); + test('detectBOM ANSI (2)', async function () { + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/empty.txt').fsPath; const detectedEncoding = await detectEncodingByBOM(file); assert.strictEqual(detectedEncoding, null); }); - test('resolve terminal encoding (detect)', async function () { - const enc = await terminalEncoding.resolveTerminalEncoding(); - assert.ok(enc.length > 0); - }); - - test('resolve terminal encoding (environment)', async function () { - process.env['VSCODE_CLI_ENCODING'] = 'utf16le'; - - const enc = await terminalEncoding.resolveTerminalEncoding(); - assert.ok(await encoding.encodingExists(enc)); - assert.strictEqual(enc, 'utf16le'); - }); - test('detectEncodingFromBuffer (JSON saved as PNG)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some.json.png'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some.json.png').fsPath; const buffer = await readExactlyByFile(file, 512); const mimes = encoding.detectEncodingFromBuffer(buffer); @@ -143,42 +129,42 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (PNG saved as TXT)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some.png.txt'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some.png.txt').fsPath; const buffer = await readExactlyByFile(file, 512); const mimes = encoding.detectEncodingFromBuffer(buffer); assert.strictEqual(mimes.seemsBinary, true); }); test('detectEncodingFromBuffer (XML saved as PNG)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some.xml.png'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some.xml.png').fsPath; const buffer = await readExactlyByFile(file, 512); const mimes = encoding.detectEncodingFromBuffer(buffer); assert.strictEqual(mimes.seemsBinary, false); }); test('detectEncodingFromBuffer (QWOFF saved as TXT)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some.qwoff.txt'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some.qwoff.txt').fsPath; const buffer = await readExactlyByFile(file, 512); const mimes = encoding.detectEncodingFromBuffer(buffer); assert.strictEqual(mimes.seemsBinary, true); }); test('detectEncodingFromBuffer (CSS saved as QWOFF)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some.css.qwoff'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some.css.qwoff').fsPath; const buffer = await readExactlyByFile(file, 512); const mimes = encoding.detectEncodingFromBuffer(buffer); assert.strictEqual(mimes.seemsBinary, false); }); test('detectEncodingFromBuffer (PDF)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some.pdf'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some.pdf').fsPath; const buffer = await readExactlyByFile(file, 512); const mimes = encoding.detectEncodingFromBuffer(buffer); assert.strictEqual(mimes.seemsBinary, true); }); test('detectEncodingFromBuffer (guess UTF-16 LE from content without BOM)', async function () { - const file = getPathFromAmdModule(require, './fixtures/utf16_le_nobom.txt'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/utf16_le_nobom.txt').fsPath; const buffer = await readExactlyByFile(file, 512); const mimes = encoding.detectEncodingFromBuffer(buffer); assert.strictEqual(mimes.encoding, encoding.UTF16le); @@ -186,7 +172,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (guess UTF-16 BE from content without BOM)', async function () { - const file = getPathFromAmdModule(require, './fixtures/utf16_be_nobom.txt'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/utf16_be_nobom.txt').fsPath; const buffer = await readExactlyByFile(file, 512); const mimes = encoding.detectEncodingFromBuffer(buffer); assert.strictEqual(mimes.encoding, encoding.UTF16be); @@ -194,28 +180,28 @@ suite('Encoding', () => { }); test('autoGuessEncoding (UTF8)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some_file.css'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_file.css').fsPath; const buffer = await readExactlyByFile(file, 512 * 8); const mimes = await encoding.detectEncodingFromBuffer(buffer, true); assert.strictEqual(mimes.encoding, 'utf8'); }); test('autoGuessEncoding (ASCII)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some_ansi.css'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_ansi.css').fsPath; const buffer = await readExactlyByFile(file, 512 * 8); const mimes = await encoding.detectEncodingFromBuffer(buffer, true); assert.strictEqual(mimes.encoding, null); }); test('autoGuessEncoding (ShiftJIS)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some.shiftjis.txt'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some.shiftjis.txt').fsPath; const buffer = await readExactlyByFile(file, 512 * 8); const mimes = await encoding.detectEncodingFromBuffer(buffer, true); assert.strictEqual(mimes.encoding, 'shiftjis'); }); test('autoGuessEncoding (CP1252)', async function () { - const file = getPathFromAmdModule(require, './fixtures/some.cp1252.txt'); + const file = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some.cp1252.txt').fsPath; const buffer = await readExactlyByFile(file, 512 * 8); const mimes = await encoding.detectEncodingFromBuffer(buffer, true); assert.strictEqual(mimes.encoding, 'windows1252'); @@ -298,7 +284,7 @@ suite('Encoding', () => { }); test('toDecodeStream - encoding, utf16be', async function () { - const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css'); + const path = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_utf16be.css').fsPath; const source = streamToBufferReadableStream(fs.createReadStream(path)); const { detected, stream } = await encoding.toDecodeStream(source, { acceptTextOnly: true, minBytesRequiredForDetection: 64, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 }); @@ -312,7 +298,7 @@ suite('Encoding', () => { }); test('toDecodeStream - empty file', async function () { - const path = getPathFromAmdModule(require, './fixtures/empty.txt'); + const path = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/empty.txt').fsPath; const source = streamToBufferReadableStream(fs.createReadStream(path)); const { detected, stream } = await encoding.toDecodeStream(source, { acceptTextOnly: true, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 }); @@ -340,7 +326,7 @@ suite('Encoding', () => { }); test('toDecodeStream - some stream (GBK issue #101856)', async function () { - const path = getPathFromAmdModule(require, './fixtures/some_gbk.txt'); + const path = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_gbk.txt').fsPath; const source = streamToBufferReadableStream(fs.createReadStream(path)); const { detected, stream } = await encoding.toDecodeStream(source, { acceptTextOnly: true, minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async () => 'gbk' }); @@ -352,7 +338,7 @@ suite('Encoding', () => { }); test('toDecodeStream - some stream (UTF-8 issue #102202)', async function () { - const path = getPathFromAmdModule(require, './fixtures/issue_102202.txt'); + const path = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/issue_102202.txt').fsPath; const source = streamToBufferReadableStream(fs.createReadStream(path)); const { detected, stream } = await encoding.toDecodeStream(source, { acceptTextOnly: true, minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async () => 'utf-8' }); @@ -396,7 +382,7 @@ suite('Encoding', () => { }); test('toEncodeReadable - encoding, utf16be', async function () { - const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css'); + const path = FileAccess.asFileUri('vs/workbench/services/textfile/test/node/encoding/fixtures/some_utf16be.css').fsPath; const source = await readAndDecodeFromDisk(path, encoding.UTF16be); const expected = VSBuffer.wrap( diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 991d62046e..1c7b2bb084 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -14,7 +14,7 @@ import { Schemas } from 'vs/base/common/network'; import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel, isResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IFileService } from 'vs/platform/files/common/files'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { ModelUndoRedoParticipant } from 'vs/editor/common/services/modelUndoRedoParticipant'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -33,7 +33,7 @@ class ResourceModelCollection extends ReferenceCollection { + protected createReferencedObject(key: string): Promise { return this.doCreateReferencedObject(key); } @@ -100,7 +100,7 @@ class ResourceModelCollection extends ReferenceCollection): void { + protected destroyReferencedObject(key: string, modelPromise: Promise): void { // untitled and inMemory are bound to a different lifecycle const resource = URI.parse(key); @@ -193,8 +193,23 @@ export class TextModelResolverService extends Disposable implements ITextModelSe declare readonly _serviceBrand: undefined; - private readonly resourceModelCollection: ResourceModelCollection & ReferenceCollection> /* TS Fail */ = this.instantiationService.createInstance(ResourceModelCollection); - private readonly asyncModelCollection = new AsyncReferenceCollection(this.resourceModelCollection); + private _resourceModelCollection: ResourceModelCollection & ReferenceCollection> /* TS Fail */ | undefined = undefined; + private get resourceModelCollection() { + if (!this._resourceModelCollection) { + this._resourceModelCollection = this.instantiationService.createInstance(ResourceModelCollection); + } + + return this._resourceModelCollection; + } + + private _asyncModelCollection: AsyncReferenceCollection | undefined = undefined; + private get asyncModelCollection() { + if (!this._asyncModelCollection) { + this._asyncModelCollection = new AsyncReferenceCollection(this.resourceModelCollection); + } + + return this._asyncModelCollection; + } constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -231,4 +246,4 @@ export class TextModelResolverService extends Disposable implements ITextModelSe } } -registerSingleton(ITextModelService, TextModelResolverService, true); +registerSingleton(ITextModelService, TextModelResolverService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts index 74cb9a3de2..d05d2da5bc 100644 --- a/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/browser/textModelResolverService.test.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { TextResourceEditorModel } from 'vs/workbench/common/editor/textResourceEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { workbenchInstantiationService, TestServiceAccessor, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, ITestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { snapshotToString } from 'vs/workbench/services/textfile/common/textfiles'; @@ -75,7 +75,7 @@ suite('Workbench - TextModelResolverService', () => { test('resolve file', async function () { const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(textModel.resource, textModel); + (accessor.textFileService.files).add(textModel.resource, textModel); await textModel.resolve(); @@ -99,7 +99,7 @@ suite('Workbench - TextModelResolverService', () => { test('resolved dirty file eventually disposes', async function () { const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(textModel.resource, textModel); + (accessor.textFileService.files).add(textModel.resource, textModel); await textModel.resolve(); @@ -124,7 +124,7 @@ suite('Workbench - TextModelResolverService', () => { test('resolved dirty file does not dispose when new reference created', async function () { const textModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_resolver.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(textModel.resource, textModel); + (accessor.textFileService.files).add(textModel.resource, textModel); await textModel.resolve(); diff --git a/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts b/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts index 471d3cc93a..0bf4f1737d 100644 --- a/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textresourceProperties/common/textResourcePropertiesService.ts @@ -10,7 +10,7 @@ import { OperatingSystem, OS } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -54,4 +54,4 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer } } -registerSingleton(ITextResourcePropertiesService, TextResourcePropertiesService, true); +registerSingleton(ITextResourcePropertiesService, TextResourcePropertiesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts b/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts index 7615aabf7f..73bee882f8 100644 --- a/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts +++ b/src/vs/workbench/services/themes/browser/browserHostColorSchemeService.ts @@ -5,7 +5,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { addMatchMediaChangeListener } from 'vs/base/browser/browser'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Disposable } from 'vs/base/common/lifecycle'; import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService'; @@ -54,4 +54,4 @@ export class BrowserHostColorSchemeService extends Disposable implements IHostCo } -registerSingleton(IHostColorSchemeService, BrowserHostColorSchemeService, true); +registerSingleton(IHostColorSchemeService, BrowserHostColorSchemeService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts index 5f76dd734e..5f8b70b71d 100644 --- a/src/vs/workbench/services/themes/browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/fileIconThemeData.ts @@ -12,7 +12,7 @@ import { ExtensionData, IThemeExtensionPoint, IWorkbenchFileIconTheme } from 'vs import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { asCSSUrl } from 'vs/base/browser/dom'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; import { ILanguageService } from 'vs/editor/common/languages/language'; export class FileIconThemeData implements IWorkbenchFileIconTheme { diff --git a/src/vs/workbench/services/themes/browser/productIconThemeData.ts b/src/vs/workbench/services/themes/browser/productIconThemeData.ts index 7e8d890488..10a9e4b89e 100644 --- a/src/vs/workbench/services/themes/browser/productIconThemeData.ts +++ b/src/vs/workbench/services/themes/browser/productIconThemeData.ts @@ -8,16 +8,15 @@ import * as nls from 'vs/nls'; import * as Paths from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import * as Json from 'vs/base/common/json'; -import { ExtensionData, IThemeExtensionPoint, IWorkbenchProductIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ExtensionData, IThemeExtensionPoint, IWorkbenchProductIconTheme, ThemeSettingDefaults } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE } from 'vs/workbench/services/themes/common/themeConfiguration'; import { fontIdRegex, fontWeightRegex, fontStyleRegex, fontFormatRegex } from 'vs/workbench/services/themes/common/productIconThemeSchema'; import { isObject, isString } from 'vs/base/common/types'; import { ILogService } from 'vs/platform/log/common/log'; import { IconDefinition, getIconRegistry, IconContribution, IconFontDefinition, IconFontSource } from 'vs/platform/theme/common/iconRegistry'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; export const DEFAULT_PRODUCT_ICON_THEME_ID = ''; // TODO @@ -98,7 +97,7 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme { static get defaultTheme(): ProductIconThemeData { let themeData = ProductIconThemeData._defaultProductIconTheme; if (!themeData) { - themeData = ProductIconThemeData._defaultProductIconTheme = new ProductIconThemeData(DEFAULT_PRODUCT_ICON_THEME_ID, nls.localize('defaultTheme', 'Default'), DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE); + themeData = ProductIconThemeData._defaultProductIconTheme = new ProductIconThemeData(DEFAULT_PRODUCT_ICON_THEME_ID, nls.localize('defaultTheme', 'Default'), ThemeSettingDefaults.PRODUCT_ICON_THEME); themeData.isLoaded = true; themeData.extensionData = undefined; themeData.watch = false; diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 25c72e5f59..77674d3739 100644 --- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchThemeService, IWorkbenchColorTheme, IWorkbenchFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, VS_HC_LIGHT_THEME, ThemeSettings, IWorkbenchProductIconTheme, ThemeSettingTarget } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWorkbenchThemeService, IWorkbenchColorTheme, IWorkbenchFileIconTheme, ExtensionData, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME, VS_HC_LIGHT_THEME, ThemeSettings, IWorkbenchProductIconTheme, ThemeSettingTarget, ThemeSettingDefaults, COLOR_THEME_DARK_INITIAL_COLORS, COLOR_THEME_LIGHT_INITIAL_COLORS } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -24,10 +24,10 @@ import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { registerColorThemeSchemas } from 'vs/workbench/services/themes/common/colorThemeSchema'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; -import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; import { ThemeRegistry, registerColorThemeExtensionPoint, registerFileIconThemeExtensionPoint, registerProductIconThemeExtensionPoint } from 'vs/workbench/services/themes/common/themeExtensionPoints'; import { updateColorThemeConfigurationSchemas, updateFileIconThemeConfigurationSchemas, ThemeConfiguration, updateProductIconThemeConfigurationSchemas } from 'vs/workbench/services/themes/common/themeConfiguration'; import { ProductIconThemeData, DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData'; @@ -44,8 +44,9 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; // implementation -const DEFAULT_COLOR_THEME_ID = 'vs sql-theme-carbon-themes-light_carbon-json'; // {{SQL CARBON EDIT}} -const DEFAULT_LIGHT_COLOR_THEME_ID = 'vs vscode-theme-defaults-themes-light_plus-json'; +// {{SQL CARBON TODO}} - default theme? +// const DEFAULT_COLOR_THEME_ID = 'vs sql-theme-carbon-themes-light_carbon-json'; // {{SQL CARBON EDIT}} +// const DEFAULT_LIGHT_COLOR_THEME_ID = 'vs vscode-theme-defaults-themes-light_plus-json'; const PERSISTED_OS_COLOR_SCHEME = 'osColorScheme'; const PERSISTED_OS_COLOR_SCHEME_SCOPE = StorageScope.APPLICATION; // the OS scheme depends on settings in the OS @@ -105,19 +106,21 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private themeSettingIdBeforeSchemeSwitch: string | undefined; + private hasDefaultUpdated: boolean = false; + constructor( @IExtensionService extensionService: IExtensionService, @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IBrowserWorkbenchEnvironmentService readonly environmentService: IBrowserWorkbenchEnvironmentService, + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, @IFileService fileService: IFileService, @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, - @IWorkbenchLayoutService readonly layoutService: IWorkbenchLayoutService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ILogService private readonly logService: ILogService, @IHostColorSchemeService private readonly hostColorService: IHostColorSchemeService, - @IUserDataInitializationService readonly userDataInitializationService: IUserDataInitializationService, - @ILanguageService readonly languageService: ILanguageService + @IUserDataInitializationService userDataInitializationService: IUserDataInitializationService, + @ILanguageService languageService: ILanguageService ) { this.container = layoutService.container; this.settings = new ThemeConfiguration(configurationService); @@ -145,25 +148,29 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // themes are loaded asynchronously, we need to initialize // a color theme document with good defaults until the theme is loaded let themeData: ColorThemeData | undefined = ColorThemeData.fromStorageData(this.storageService); - if (themeData && this.settings.colorTheme !== themeData.settingsId && this.settings.isDefaultColorTheme()) { + const colorThemeSetting = this.settings.colorTheme; + if (themeData && colorThemeSetting !== themeData.settingsId && this.settings.isDefaultColorTheme()) { + this.hasDefaultUpdated = themeData.settingsId === ThemeSettingDefaults.COLOR_THEME_DARK_OLD || themeData.settingsId === ThemeSettingDefaults.COLOR_THEME_LIGHT_OLD; + // the web has different defaults than the desktop, therefore do not restore when the setting is the default theme and the storage doesn't match that. themeData = undefined; } // the preferred color scheme (high contrast, light, dark) has changed since the last start const preferredColorScheme = this.getPreferredColorScheme(); + const defaultColorMap = colorThemeSetting === ThemeSettingDefaults.COLOR_THEME_LIGHT ? COLOR_THEME_LIGHT_INITIAL_COLORS : colorThemeSetting === ThemeSettingDefaults.COLOR_THEME_DARK ? COLOR_THEME_DARK_INITIAL_COLORS : undefined; if (preferredColorScheme && themeData?.type !== preferredColorScheme && this.storageService.get(PERSISTED_OS_COLOR_SCHEME, PERSISTED_OS_COLOR_SCHEME_SCOPE) !== preferredColorScheme) { - themeData = ColorThemeData.createUnloadedThemeForThemeType(preferredColorScheme); + themeData = ColorThemeData.createUnloadedThemeForThemeType(preferredColorScheme, undefined); } if (!themeData) { const initialColorTheme = environmentService.options?.initialColorTheme; if (initialColorTheme) { - themeData = ColorThemeData.createUnloadedThemeForThemeType(initialColorTheme.themeType, initialColorTheme.colors); + themeData = ColorThemeData.createUnloadedThemeForThemeType(initialColorTheme.themeType, initialColorTheme.colors ?? defaultColorMap); } } if (!themeData) { - themeData = ColorThemeData.createUnloadedThemeForThemeType(isWeb ? ColorScheme.LIGHT : ColorScheme.DARK); + themeData = ColorThemeData.createUnloadedThemeForThemeType(isWeb ? ColorScheme.LIGHT : ColorScheme.DARK, defaultColorMap); } themeData.setCustomizations(this.settings); this.applyTheme(themeData, undefined, true); @@ -213,7 +220,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { if (devThemes.length) { return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); } - const fallbackTheme = this.currentColorTheme.type === ColorScheme.LIGHT ? DEFAULT_LIGHT_COLOR_THEME_ID : DEFAULT_COLOR_THEME_ID; + const fallbackTheme = this.currentColorTheme.type === ColorScheme.LIGHT ? ThemeSettingDefaults.COLOR_THEME_LIGHT : ThemeSettingDefaults.COLOR_THEME_DARK; const theme = this.colorThemeRegistry.findThemeBySettingsId(this.settings.colorTheme, fallbackTheme); const preferredColorScheme = this.getPreferredColorScheme(); @@ -314,7 +321,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { updateColorThemeConfigurationSchemas(event.themes); if (await this.restoreColorTheme()) { // checks if theme from settings exists and is set // restore theme - if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) { + if (this.currentColorTheme.settingsId === ThemeSettingDefaults.COLOR_THEME_DARK && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) { await this.setColorTheme(prevColorId, 'auto'); prevColorId = undefined; } else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) { @@ -323,7 +330,8 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } else if (event.removed.some(t => t.settingsId === this.currentColorTheme.settingsId)) { // current theme is no longer available prevColorId = this.currentColorTheme.id; - await this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto'); + const defaultTheme = this.colorThemeRegistry.findThemeBySettingsId(ThemeSettingDefaults.COLOR_THEME_DARK); + await this.setColorTheme(defaultTheme, 'auto'); } }); @@ -430,6 +438,10 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return null; } + public hasUpdatedDefaultThemes(): boolean { + return this.hasDefaultUpdated; + } + public getColorTheme(): IWorkbenchColorTheme { return this.currentColorTheme; } @@ -870,4 +882,7 @@ registerColorThemeSchemas(); registerFileIconThemeSchemas(); registerProductIconThemeSchemas(); -registerSingleton(IWorkbenchThemeService, WorkbenchThemeService); +// The WorkbenchThemeService should stay eager as the constructor restores the +// last used colors / icons from storage. This needs to happen as quickly as possible +// for a flicker-free startup experience. +registerSingleton(IWorkbenchThemeService, WorkbenchThemeService, InstantiationType.Eager); diff --git a/src/vs/workbench/services/themes/common/colorThemeData.ts b/src/vs/workbench/services/themes/common/colorThemeData.ts index 7952800491..b54c98d9cd 100644 --- a/src/vs/workbench/services/themes/common/colorThemeData.ts +++ b/src/vs/workbench/services/themes/common/colorThemeData.ts @@ -19,7 +19,7 @@ import { URI } from 'vs/base/common/uri'; import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; import { TokenStyle, SemanticTokenRule, ProbeScope, getTokenClassificationRegistry, TokenStyleValue, TokenStyleData, parseClassifierString } from 'vs/platform/theme/common/tokenClassificationRegistry'; import { MatcherWithPriority, Matcher, createMatchers } from 'vs/workbench/services/themes/common/textMateScopeMatcher'; -import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; +import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; import { CharCode } from 'vs/base/common/charCode'; import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage'; import { ThemeConfiguration } from 'vs/workbench/services/themes/common/themeConfiguration'; @@ -440,7 +440,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { let themeSpecificColors; for (const key in colors) { const scopedColors = colors[key]; - if (this.isThemeScope(key) && scopedColors instanceof Object && !types.isArray(scopedColors)) { + if (this.isThemeScope(key) && scopedColors instanceof Object && !Array.isArray(scopedColors)) { const themeScopeList = key.match(themeScopeRegex) || []; for (const themeScope of themeScopeList) { const themeId = themeScope.substring(1, themeScope.length - 1); @@ -452,7 +452,7 @@ export class ColorThemeData implements IWorkbenchColorTheme { for (const subkey in scopedThemeSpecificColors) { const originalColors = themeSpecificColors[subkey]; const overrideColors = scopedThemeSpecificColors[subkey]; - if (types.isArray(originalColors) && types.isArray(overrideColors)) { + if (Array.isArray(originalColors) && Array.isArray(overrideColors)) { themeSpecificColors[subkey] = originalColors.concat(overrideColors); } else if (overrideColors) { themeSpecificColors[subkey] = overrideColors; diff --git a/src/vs/workbench/services/themes/common/colorThemeSchema.ts b/src/vs/workbench/services/themes/common/colorThemeSchema.ts index ac72a64be9..dbccbf7f44 100644 --- a/src/vs/workbench/services/themes/common/colorThemeSchema.ts +++ b/src/vs/workbench/services/themes/common/colorThemeSchema.ts @@ -115,7 +115,6 @@ const textMateScopes = [ ]; export const textmateColorsSchemaId = 'vscode://schemas/textmate-colors'; -export const textmateColorSettingsSchemaId = `${textmateColorsSchemaId}#/definitions/settings`; export const textmateColorGroupSchemaId = `${textmateColorsSchemaId}#/definitions/colorGroup`; const textmateColorSchema: IJSONSchema = { diff --git a/src/vs/workbench/services/themes/common/iconExtensionPoint.ts b/src/vs/workbench/services/themes/common/iconExtensionPoint.ts index 7b13f23e68..6c7f84d0d9 100644 --- a/src/vs/workbench/services/themes/common/iconExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/iconExtensionPoint.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IIconRegistry, Extensions as IconRegistryExtensions } from 'vs/platform/theme/common/iconRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { CSSIcon } from 'vs/base/common/codicons'; +import { ThemeIcon } from 'vs/base/common/themables'; import * as resources from 'vs/base/common/resources'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { extname, posix } from 'vs/base/common/path'; @@ -22,7 +22,7 @@ interface IIconExtensionPoint { const iconRegistry: IIconRegistry = Registry.as(IconRegistryExtensions.IconContribution); const iconReferenceSchema = iconRegistry.getIconReferenceSchema(); -const iconIdPattern = `^${CSSIcon.iconNameSegment}(-${CSSIcon.iconNameSegment})+$`; +const iconIdPattern = `^${ThemeIcon.iconNameSegment}(-${ThemeIcon.iconNameSegment})+$`; const iconConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'icons', diff --git a/src/vs/workbench/services/themes/common/plistParser.ts b/src/vs/workbench/services/themes/common/plistParser.ts index 3c382a1a31..a84de525bc 100644 --- a/src/vs/workbench/services/themes/common/plistParser.ts +++ b/src/vs/workbench/services/themes/common/plistParser.ts @@ -23,11 +23,6 @@ const enum State { DICT_STATE = 1, ARR_STATE = 2 } - -export function parseWithLocation(content: string, filename: string, locationKeyName: string): any { - return _parse(content, filename, locationKeyName); -} - /** * A very fast plist parser */ @@ -331,9 +326,9 @@ function _parse(content: string, filename: string | null, locationKeyName: strin function escapeVal(str: string): string { return str.replace(/&#([0-9]+);/g, function (_: string, m0: string) { - return (String).fromCodePoint(parseInt(m0, 10)); + return String.fromCodePoint(parseInt(m0, 10)); }).replace(/&#x([0-9a-f]+);/g, function (_: string, m0: string) { - return (String).fromCodePoint(parseInt(m0, 16)); + return String.fromCodePoint(parseInt(m0, 16)); }).replace(/&|<|>|"|'/g, function (_: string) { switch (_) { case '&': return '&'; diff --git a/src/vs/workbench/services/themes/common/themeConfiguration.ts b/src/vs/workbench/services/themes/common/themeConfiguration.ts index cc4d8533d3..6edee9f810 100644 --- a/src/vs/workbench/services/themes/common/themeConfiguration.ts +++ b/src/vs/workbench/services/themes/common/themeConfiguration.ts @@ -12,16 +12,17 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, textmateColorGroupSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; import { tokenStylingSchemaId } from 'vs/platform/theme/common/tokenClassificationRegistry'; -import { ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IColorCustomizations, ITokenColorCustomizations, IWorkbenchProductIconTheme, ISemanticTokenColorCustomizations, ThemeSettingTarget } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ThemeSettings, IWorkbenchColorTheme, IWorkbenchFileIconTheme, IColorCustomizations, ITokenColorCustomizations, IWorkbenchProductIconTheme, ISemanticTokenColorCustomizations, ThemeSettingTarget, ThemeSettingDefaults } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { isWeb } from 'vs/base/common/platform'; -const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme -const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme -const DEFAULT_THEME_HC_DARK_SETTING_VALUE = 'Default High Contrast'; -const DEFAULT_THEME_HC_LIGHT_SETTING_VALUE = 'Default High Contrast Light'; +// {{SQL CARBON TODO}} - set default themes +// const DEFAULT_THEME_DARK_SETTING_VALUE = 'Default Dark Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme +// const DEFAULT_THEME_LIGHT_SETTING_VALUE = 'Default Light Azure Data Studio'; // {{SQL CARBON EDIT}} replace default theme +// const DEFAULT_THEME_HC_DARK_SETTING_VALUE = 'Default High Contrast'; +// const DEFAULT_THEME_HC_LIGHT_SETTING_VALUE = 'Default High Contrast Light'; -const DEFAULT_FILE_ICON_THEME_SETTING_VALUE = 'vs-seti'; +// const DEFAULT_FILE_ICON_THEME_SETTING_VALUE = 'vs-seti'; export const DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE = 'Default'; @@ -32,10 +33,14 @@ const colorThemeSettingEnum: string[] = []; const colorThemeSettingEnumItemLabels: string[] = []; const colorThemeSettingEnumDescriptions: string[] = []; +function formatSettingAsLink(str: string) { + return `\`#${str}#\``; +} + const colorThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', description: nls.localize('colorTheme', "Specifies the color theme used in the workbench."), - default: isWeb ? DEFAULT_THEME_LIGHT_SETTING_VALUE : DEFAULT_THEME_LIGHT_SETTING_VALUE, // {{SQL CARBON EDIT}} + default: isWeb ? ThemeSettingDefaults.COLOR_THEME_LIGHT : ThemeSettingDefaults.COLOR_THEME_DARK, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -43,8 +48,8 @@ const colorThemeSettingSchema: IConfigurationPropertySchema = { }; const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', // - markdownDescription: nls.localize({ key: 'preferredDarkColorTheme', comment: ['`#{0}#` will become a link to an other setting. Do not remove backtick or #'] }, 'Specifies the preferred color theme for dark OS appearance when `#{0}#` is enabled.', ThemeSettings.DETECT_COLOR_SCHEME), - default: DEFAULT_THEME_DARK_SETTING_VALUE, + markdownDescription: nls.localize({ key: 'preferredDarkColorTheme', comment: ['{0} will become a link to another setting.'] }, 'Specifies the preferred color theme for dark OS appearance when {0} is enabled.', formatSettingAsLink(ThemeSettings.DETECT_COLOR_SCHEME)), + default: ThemeSettingDefaults.COLOR_THEME_DARK, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -52,8 +57,8 @@ const preferredDarkThemeSettingSchema: IConfigurationPropertySchema = { }; const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', - markdownDescription: nls.localize({ key: 'preferredLightColorTheme', comment: ['`#{0}#` will become a link to an other setting. Do not remove backtick or #'] }, 'Specifies the preferred color theme for light OS appearance when `#{0}#` is enabled.', ThemeSettings.DETECT_COLOR_SCHEME), - default: DEFAULT_THEME_LIGHT_SETTING_VALUE, + markdownDescription: nls.localize({ key: 'preferredLightColorTheme', comment: ['{0} will become a link to another setting.'] }, 'Specifies the preferred color theme for light OS appearance when {0} is enabled.', formatSettingAsLink(ThemeSettings.DETECT_COLOR_SCHEME)), + default: ThemeSettingDefaults.COLOR_THEME_LIGHT, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -61,8 +66,8 @@ const preferredLightThemeSettingSchema: IConfigurationPropertySchema = { }; const preferredHCDarkThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', - markdownDescription: nls.localize({ key: 'preferredHCDarkColorTheme', comment: ['`#{0}#` will become a link to an other setting. Do not remove backtick or #'] }, 'Specifies the preferred color theme used in high contrast dark mode when `#{0}#` is enabled.', ThemeSettings.DETECT_HC), - default: DEFAULT_THEME_HC_DARK_SETTING_VALUE, + markdownDescription: nls.localize({ key: 'preferredHCDarkColorTheme', comment: ['{0} will become a link to another setting.'] }, 'Specifies the preferred color theme used in high contrast dark mode when {0} is enabled.', formatSettingAsLink(ThemeSettings.DETECT_HC)), + default: ThemeSettingDefaults.COLOR_THEME_HC_DARK, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -70,8 +75,8 @@ const preferredHCDarkThemeSettingSchema: IConfigurationPropertySchema = { }; const preferredHCLightThemeSettingSchema: IConfigurationPropertySchema = { type: 'string', - markdownDescription: nls.localize({ key: 'preferredHCLightColorTheme', comment: ['`#{0}#` will become a link to an other setting. Do not remove backtick or #'] }, 'Specifies the preferred color theme used in high contrast light mode when `#{0}#` is enabled.', ThemeSettings.DETECT_HC), - default: DEFAULT_THEME_HC_LIGHT_SETTING_VALUE, + markdownDescription: nls.localize({ key: 'preferredHCLightColorTheme', comment: ['{0} will become a link to another setting.'] }, 'Specifies the preferred color theme used in high contrast light mode when {0} is enabled.', formatSettingAsLink(ThemeSettings.DETECT_HC)), + default: ThemeSettingDefaults.COLOR_THEME_HC_LIGHT, enum: colorThemeSettingEnum, enumDescriptions: colorThemeSettingEnumDescriptions, enumItemLabels: colorThemeSettingEnumItemLabels, @@ -79,7 +84,7 @@ const preferredHCLightThemeSettingSchema: IConfigurationPropertySchema = { }; const detectColorSchemeSettingSchema: IConfigurationPropertySchema = { type: 'boolean', - markdownDescription: nls.localize('detectColorScheme', 'If set, automatically switch to the preferred color theme based on the OS appearance. If the OS appearance is dark, the theme specified at `#{0}#` is used, for light `#{1}#`.', ThemeSettings.PREFERRED_DARK_THEME, ThemeSettings.PREFERRED_LIGHT_THEME), + markdownDescription: nls.localize({ key: 'detectColorScheme', comment: ['{0} and {1} will become links to other settings.'] }, 'If set, automatically switch to the preferred color theme based on the OS appearance. If the OS appearance is dark, the theme specified at {0} is used, for light {1}.', formatSettingAsLink(ThemeSettings.PREFERRED_DARK_THEME), formatSettingAsLink(ThemeSettings.PREFERRED_LIGHT_THEME)), default: false }; @@ -95,7 +100,7 @@ const colorCustomizationsSchema: IConfigurationPropertySchema = { }; const fileIconThemeSettingSchema: IConfigurationPropertySchema = { type: ['string', 'null'], - default: DEFAULT_FILE_ICON_THEME_SETTING_VALUE, + default: ThemeSettingDefaults.FILE_ICON_THEME, description: nls.localize('iconTheme', "Specifies the file icon theme used in the workbench or 'null' to not show any file icons."), enum: [null], enumItemLabels: [nls.localize('noIconThemeLabel', 'None')], @@ -104,9 +109,9 @@ const fileIconThemeSettingSchema: IConfigurationPropertySchema = { }; const productIconThemeSettingSchema: IConfigurationPropertySchema = { type: ['string', 'null'], - default: DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE, + default: ThemeSettingDefaults.PRODUCT_ICON_THEME, description: nls.localize('productIconTheme', "Specifies the product icon theme used."), - enum: [DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE], + enum: [ThemeSettingDefaults.PRODUCT_ICON_THEME], enumItemLabels: [nls.localize('defaultProductIconThemeLabel', 'Default')], enumDescriptions: [nls.localize('defaultProductIconThemeDesc', 'Default')], errorMessage: nls.localize('productIconThemeError', "Product icon theme is unknown or not installed.") @@ -115,7 +120,7 @@ const productIconThemeSettingSchema: IConfigurationPropertySchema = { const detectHCSchemeSettingSchema: IConfigurationPropertySchema = { type: 'boolean', default: true, - markdownDescription: nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme. The high contrast theme to use is specified by `#{0}#` and `#{1}#`", ThemeSettings.PREFERRED_HC_DARK_THEME, ThemeSettings.PREFERRED_HC_LIGHT_THEME), + markdownDescription: nls.localize({ key: 'autoDetectHighContrast', comment: ['{0} and {1} will become links to other settings.'] }, "If enabled, will automatically change to high contrast theme if the OS is using a high contrast theme. The high contrast theme to use is specified by {0} and {1}", formatSettingAsLink(ThemeSettings.PREFERRED_HC_DARK_THEME), formatSettingAsLink(ThemeSettings.PREFERRED_HC_LIGHT_THEME)), scope: ConfigurationScope.APPLICATION }; @@ -173,7 +178,7 @@ const tokenColorSchema: IJSONSchema = { semanticHighlighting: { description: nls.localize('editorColors.semanticHighlighting', 'Whether semantic highlighting should be enabled for this theme.'), deprecationMessage: nls.localize('editorColors.semanticHighlighting.deprecationMessage', 'Use `enabled` in `editor.semanticTokenColorCustomizations` setting instead.'), - markdownDeprecationMessage: nls.localize('editorColors.semanticHighlighting.deprecationMessageMarkdown', 'Use `enabled` in `#editor.semanticTokenColorCustomizations#` setting instead.'), + markdownDeprecationMessage: nls.localize({ key: 'editorColors.semanticHighlighting.deprecationMessageMarkdown', comment: ['{0} will become a link to another setting.'] }, 'Use `enabled` in {0} setting instead.', formatSettingAsLink('editor.semanticTokenColorCustomizations')), type: 'boolean' } }, @@ -223,6 +228,7 @@ configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration export function updateColorThemeConfigurationSchemas(themes: IWorkbenchColorTheme[]) { // updates enum for the 'workbench.colorTheme` setting + themes.sort((a, b) => a.label.localeCompare(b.label)); colorThemeSettingEnum.splice(0, colorThemeSettingEnum.length, ...themes.map(t => t.settingsId)); colorThemeSettingEnumDescriptions.splice(0, colorThemeSettingEnumDescriptions.length, ...themes.map(t => t.description || '')); colorThemeSettingEnumItemLabels.splice(0, colorThemeSettingEnumItemLabels.length, ...themes.map(t => t.label || '')); diff --git a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts index a0e82871c8..1ef57909ba 100644 --- a/src/vs/workbench/services/themes/common/themeExtensionPoints.ts +++ b/src/vs/workbench/services/themes/common/themeExtensionPoints.ts @@ -197,24 +197,20 @@ export class ThemeRegistry { return resultingThemes; } - public findThemeById(themeId: string, defaultId?: string): T | undefined { + public findThemeById(themeId: string): T | undefined { if (this.builtInTheme && this.builtInTheme.id === themeId) { return this.builtInTheme; } const allThemes = this.getThemes(); - let defaultTheme: T | undefined = undefined; for (const t of allThemes) { if (t.id === themeId) { return t; } - if (t.id === defaultId) { - defaultTheme = t; - } } - return defaultTheme; + return undefined; } - public findThemeBySettingsId(settingsId: string | null, defaultId?: string): T | undefined { + public findThemeBySettingsId(settingsId: string | null, defaultSettingsId?: string): T | undefined { if (this.builtInTheme && this.builtInTheme.settingsId === settingsId) { return this.builtInTheme; } @@ -224,7 +220,7 @@ export class ThemeRegistry { if (t.settingsId === settingsId) { return t; } - if (t.id === defaultId) { + if (t.settingsId === defaultSettingsId) { defaultTheme = t; } } diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 3d80a01792..1789a220e9 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -40,6 +40,31 @@ export enum ThemeSettings { DETECT_HC = 'window.autoDetectHighContrast' } +export enum ThemeSettingDefaults { + COLOR_THEME_DARK = 'Default Dark Modern', + COLOR_THEME_LIGHT = 'Default Light Modern', + COLOR_THEME_HC_DARK = 'Default High Contrast', + COLOR_THEME_HC_LIGHT = 'Default High Contrast Light', + + COLOR_THEME_DARK_OLD = 'Default Dark+', + COLOR_THEME_LIGHT_OLD = 'Default Light+', + + FILE_ICON_THEME = 'vs-seti', + PRODUCT_ICON_THEME = 'Default', +} + +export const COLOR_THEME_DARK_INITIAL_COLORS = { + 'activityBar.background': '#181818', + 'statusBar.background': '#181818', + 'statusBar.noFolderBackground': '#1f1f1f', +}; + +export const COLOR_THEME_LIGHT_INITIAL_COLORS = { + 'activityBar.background': '#f8f8f8', + 'statusBar.background': '#f8f8f8', + 'statusBar.noFolderBackground': '#f8f8f8' +}; + export interface IWorkbenchTheme { readonly id: string; readonly label: string; @@ -77,6 +102,8 @@ export interface IWorkbenchThemeService extends IThemeService { getMarketplaceColorThemes(publisher: string, name: string, version: string): Promise; onDidColorThemeChange: Event; + hasUpdatedDefaultThemes(): boolean; + setFileIconTheme(iconThemeId: string | undefined | IWorkbenchFileIconTheme, settingsTarget: ThemeSettingTarget): Promise; getFileIconTheme(): IWorkbenchFileIconTheme; getFileIconThemes(): Promise; @@ -181,12 +208,6 @@ export interface ISemanticTokenColorizationSetting { italic?: boolean; } -export interface ExtensionVersion { - publisher: string; - name: string; - version: string; -} - export interface ExtensionData { extensionId: string; extensionPublisher: string; diff --git a/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts b/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts index c9f4e80b68..e4cba84c3f 100644 --- a/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts +++ b/src/vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter } from 'vs/base/common/event'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { INativeHostService } from 'vs/platform/native/common/native'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Disposable } from 'vs/base/common/lifecycle'; import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; @@ -70,4 +70,4 @@ export class NativeHostColorSchemeService extends Disposable implements IHostCol } -registerSingleton(IHostColorSchemeService, NativeHostColorSchemeService, true); +registerSingleton(IHostColorSchemeService, NativeHostColorSchemeService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/themes/test/electron-browser/color-theme.json b/src/vs/workbench/services/themes/test/node/color-theme.json similarity index 100% rename from src/vs/workbench/services/themes/test/electron-browser/color-theme.json rename to src/vs/workbench/services/themes/test/node/color-theme.json diff --git a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts b/src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts similarity index 98% rename from src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts rename to src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts index 20d45d8cc4..5a62d88e53 100644 --- a/src/vs/workbench/services/themes/test/electron-browser/tokenStyleResolving.test.ts +++ b/src/vs/workbench/services/themes/test/node/tokenStyleResolving.test.ts @@ -13,7 +13,7 @@ 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 { FileAccess, Schemas } from 'vs/base/common/network'; -import { ExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService'; +import { ExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoaderService'; import { ITokenStyle } from 'vs/platform/theme/common/themeService'; import { mock, TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; import { IRequestService } from 'vs/platform/request/common/request'; @@ -99,7 +99,7 @@ suite('Themes - TokenStyleResolving', () => { test('color defaults', async () => { const themeData = ColorThemeData.createUnloadedTheme('foo'); - themeData.location = FileAccess.asFileUri('./color-theme.json', require); + themeData.location = FileAccess.asFileUri('vs/workbench/services/themes/test/node/color-theme.json'); await themeData.ensureLoaded(extensionResourceLoaderService); assert.strictEqual(themeData.isLoaded, true); diff --git a/src/vs/workbench/services/timer/browser/timerService.ts b/src/vs/workbench/services/timer/browser/timerService.ts index 8b01e9a271..149690c941 100644 --- a/src/vs/workbench/services/timer/browser/timerService.ts +++ b/src/vs/workbench/services/timer/browser/timerService.ts @@ -12,10 +12,13 @@ import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecyc import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Barrier } from 'vs/base/common/async'; +import { Barrier, timeout } from 'vs/base/common/async'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; +import { isWeb } from 'vs/base/common/platform'; +import { createBlobWorker } from 'vs/base/browser/defaultWorkerFactory'; /* __GDPR__FRAGMENT__ "IMemoryInfo" : { @@ -397,6 +400,14 @@ export interface ITimerService { */ whenReady(): Promise; + /** + * A baseline performance indicator for this machine. The value will only available + * late after startup because computing it takes away CPU resources + * + * NOTE that this returns -1 if the machine is hopelessly slow... + */ + perfBaseline: Promise; + /** * Startup metrics. Can ONLY be accessed after `whenReady` has resolved. */ @@ -413,6 +424,13 @@ export interface ITimerService { * returned tuples but the marks of a tuple are guaranteed to be sorted by start times. */ getPerformanceMarks(): [source: string, marks: readonly perf.PerformanceMark[]][]; + + /** + * Return the duration between two marks. + * @param from from mark name + * @param to to mark name + */ + getDuration(from: string, to: string): number; } export const ITimerService = createDecorator('timerService'); @@ -461,8 +479,12 @@ export abstract class AbstractTimerService implements ITimerService { private readonly _barrier = new Barrier(); private readonly _marks = new PerfMarks(); + private readonly _rndValueShouldSendTelemetry = Math.random() < .05; // 5% of users + private _startupMetrics?: IStartupMetrics; + readonly perfBaseline: Promise; + constructor( @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @@ -487,6 +509,52 @@ export abstract class AbstractTimerService implements ITimerService { this._reportStartupTimes(metrics); this._barrier.open(); }); + + + this.perfBaseline = this._barrier.wait() + .then(() => this._lifecycleService.when(LifecyclePhase.Eventually)) + .then(() => timeout(this._startupMetrics!.timers.ellapsedRequire)) + .then(() => { + + // we use fibonacci numbers to have a performance baseline that indicates + // how slow/fast THIS machine actually is. + + const jsSrc = (function (this: WindowOrWorkerGlobalScope) { + // the following operation took ~16ms (one frame at 64FPS) to complete on my machine. We derive performance observations + // from that. We also bail if that took too long (>1s) + let tooSlow = false; + function fib(n: number): number { + if (tooSlow) { + return 0; + } + if (performance.now() - t1 >= 1000) { + tooSlow = true; + } + if (n <= 2) { + return n; + } + return fib(n - 1) + fib(n - 2); + } + + const t1 = performance.now(); + fib(24); + const value = Math.round(performance.now() - t1); + postMessage({ value: tooSlow ? -1 : value }); + + }).toString(); + + const blob = new Blob([`(${jsSrc})();`], { type: 'application/javascript' }); + const blobUrl = URL.createObjectURL(blob); + + const worker = createBlobWorker(blobUrl, { name: 'perfBaseline' }); + return new Promise(resolve => { + worker.onmessage = e => resolve(e.data.value); + + }).finally(() => { + worker.terminate(); + URL.revokeObjectURL(blobUrl); + }); + }); } whenReady(): Promise { @@ -503,13 +571,19 @@ export abstract class AbstractTimerService implements ITimerService { setPerformanceMarks(source: string, marks: perf.PerformanceMark[]): void { // Perf marks are a shared resource because anyone can generate them // and because of that we only accept marks that start with 'code/' - this._marks.setMarks(source, marks.filter(mark => mark.name.startsWith('code/'))); + const codeMarks = marks.filter(mark => mark.name.startsWith('code/')); + this._marks.setMarks(source, codeMarks); + this._reportPerformanceMarks(source, codeMarks); } getPerformanceMarks(): [source: string, marks: readonly perf.PerformanceMark[]][] { return this._marks.getEntries(); } + getDuration(from: string, to: string): number { + return this._marks.getDuration(from, to); + } + private _reportStartupTimes(metrics: IStartupMetrics): void { // report IStartupMetrics as telemetry /* __GDPR__ @@ -521,39 +595,51 @@ export abstract class AbstractTimerService implements ITimerService { } */ this._telemetryService.publicLog('startupTimeVaried', metrics); + } + protected _shouldReportPerfMarks(): boolean { + return this._rndValueShouldSendTelemetry; + } + + private _reportPerformanceMarks(source: string, marks: perf.PerformanceMark[]) { + + if (!this._shouldReportPerfMarks()) { + // the `startup.timer.mark` event is send very often. In order to save resources + // we let some of our instances/sessions send this event + return; + } // report raw timers as telemetry. each mark is send a separate telemetry // event and it is "normalized" to a relative timestamp where the first mark // defines the start - for (const [source, marks] of this.getPerformanceMarks()) { - type Mark = { source: string; name: string; relativeStartTime: number; startTime: number }; - type MarkClassification = { - owner: 'jrieken'; - comment: 'Information about a performance marker'; - source: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Where this marker was generated, e.g main, renderer, extension host' }; - name: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of this marker (as defined in source code)' }; - relativeStartTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The duration between the previous and this marker' }; - startTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comments: 'The absolute timestamp (unix time)' }; - }; - let lastMark: perf.PerformanceMark = marks[0]; - for (const mark of marks) { - const delta = mark.startTime - lastMark.startTime; - this._telemetryService.publicLog2('startup.timer.mark', { - source, - name: mark.name, - relativeStartTime: delta, - startTime: mark.startTime - }); - lastMark = mark; - } + type Mark = { source: string; name: TelemetryTrustedValue; startTime: number }; + type MarkClassification = { + owner: 'jrieken'; + comment: 'Information about a performance marker'; + source: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Where this marker was generated, e.g main, renderer, extension host' }; + name: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of this marker (as defined in source code)' }; + startTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'The absolute timestamp (unix time)' }; + }; + + for (const mark of marks) { + this._telemetryService.publicLog2('startup.timer.mark', { + source, + name: new TelemetryTrustedValue(mark.name), + startTime: mark.startTime + }); } + } private async _computeStartupMetrics(): Promise { const initialStartup = this._isInitialStartup(); - const startMark = initialStartup ? 'code/didStartMain' : 'code/willOpenNewWindow'; + let startMark: string; + if (isWeb) { + startMark = 'code/timeOrigin'; + } else { + startMark = initialStartup ? 'code/didStartMain' : 'code/willOpenNewWindow'; + } const activeViewlet = this._paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar); const activePanel = this._paneCompositeService.getActivePaneComposite(ViewContainerLocation.Panel); diff --git a/src/vs/workbench/services/timer/electron-sandbox/timerService.ts b/src/vs/workbench/services/timer/electron-sandbox/timerService.ts index efc7137a77..97831919ad 100644 --- a/src/vs/workbench/services/timer/electron-sandbox/timerService.ts +++ b/src/vs/workbench/services/timer/electron-sandbox/timerService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { INativeHostService } from 'vs/platform/native/common/native'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -14,7 +14,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { IStartupMetrics, AbstractTimerService, Writeable, ITimerService } from 'vs/workbench/services/timer/browser/timerService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -83,9 +83,14 @@ export class TimerService extends AbstractTimerService { // ignore, be on the safe side with these hardware method calls } } + + protected override _shouldReportPerfMarks(): boolean { + // always send when running with the prof-append-timers flag + return super._shouldReportPerfMarks() || Boolean(this._environmentService.args['prof-append-timers']); + } } -registerSingleton(ITimerService, TimerService); +registerSingleton(ITimerService, TimerService, InstantiationType.Delayed); //#region cached data logic diff --git a/src/vs/workbench/services/title/electron-sandbox/titleService.ts b/src/vs/workbench/services/title/electron-sandbox/titleService.ts index 5d23809d2c..53ab456f8f 100644 --- a/src/vs/workbench/services/title/electron-sandbox/titleService.ts +++ b/src/vs/workbench/services/title/electron-sandbox/titleService.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { TitlebarPart } from 'vs/workbench/electron-sandbox/parts/titlebar/titlebarPart'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; -registerSingleton(ITitleService, TitlebarPart); +registerSingleton(ITitleService, TitlebarPart, InstantiationType.Eager); diff --git a/src/vs/workbench/services/tunnel/browser/tunnelService.ts b/src/vs/workbench/services/tunnel/browser/tunnelService.ts index 77be8933da..b0ffd5a952 100644 --- a/src/vs/workbench/services/tunnel/browser/tunnelService.ts +++ b/src/vs/workbench/services/tunnel/browser/tunnelService.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; import { AbstractTunnelService, ITunnelService, RemoteTunnel } from 'vs/platform/tunnel/common/tunnel'; @@ -13,12 +14,17 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ export class TunnelService extends AbstractTunnelService { constructor( @ILogService logService: ILogService, - @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService + @IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService, + @IConfigurationService configurationService: IConfigurationService ) { - super(logService); + super(logService, configurationService); } - protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + public isPortPrivileged(_port: number): boolean { + return false; + } + + protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, _localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; @@ -36,4 +42,4 @@ export class TunnelService extends AbstractTunnelService { } } -registerSingleton(ITunnelService, TunnelService, true); +registerSingleton(ITunnelService, TunnelService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts b/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts index 34768ba4d4..7fe576155e 100644 --- a/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts +++ b/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts @@ -6,14 +6,17 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { URI } from 'vs/base/common/uri'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ITunnelService, AbstractTunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ITunnelService, AbstractTunnelService, RemoteTunnel, TunnelPrivacyId, isPortPrivileged } from 'vs/platform/tunnel/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; import { ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { OS } from 'vs/base/common/platform'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; class SharedProcessTunnel extends Disposable implements RemoteTunnel { @@ -59,8 +62,10 @@ export class TunnelService extends AbstractTunnelService { @ISharedProcessTunnelService private readonly _sharedProcessTunnelService: ISharedProcessTunnelService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILifecycleService lifecycleService: ILifecycleService, + @INativeWorkbenchEnvironmentService private readonly _nativeWorkbenchEnvironmentService: INativeWorkbenchEnvironmentService, + @IConfigurationService configurationService: IConfigurationService ) { - super(logService); + super(logService, configurationService); // Destroy any shared process tunnels that might still be active lifecycleService.onDidShutdown(() => { @@ -70,7 +75,11 @@ export class TunnelService extends AbstractTunnelService { }); } - protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + public isPortPrivileged(port: number): boolean { + return isPortPrivileged(port, this.defaultTunnelHost, OS, this._nativeWorkbenchEnvironmentService.os.release); + } + + protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; @@ -82,18 +91,18 @@ export class TunnelService extends AbstractTunnelService { } else { this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`); - const tunnel = this._createSharedProcessTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded); + const tunnel = this._createSharedProcessTunnel(addressProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded); this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.'); this.addTunnelToMap(remoteHost, remotePort, tunnel); return tunnel; } } - private async _createSharedProcessTunnel(addressProvider: IAddressProvider, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalPort: number | undefined, elevateIfNeeded: boolean | undefined): Promise { + private async _createSharedProcessTunnel(addressProvider: IAddressProvider, tunnelRemoteHost: string, tunnelRemotePort: number, tunnelLocalHost: string, tunnelLocalPort: number | undefined, elevateIfNeeded: boolean | undefined): Promise { const { id } = await this._sharedProcessTunnelService.createTunnel(); this._activeSharedProcessTunnels.add(id); const authority = this._environmentService.remoteAuthority!; - const result = await this._sharedProcessTunnelService.startTunnel(authority, id, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort, elevateIfNeeded); + const result = await this._sharedProcessTunnelService.startTunnel(authority, id, tunnelRemoteHost, tunnelRemotePort, tunnelLocalHost, tunnelLocalPort, elevateIfNeeded); const tunnel = this._instantiationService.createInstance(SharedProcessTunnel, id, addressProvider, tunnelRemoteHost, tunnelRemotePort, result.tunnelLocalPort, result.localAddress, () => { this._activeSharedProcessTunnels.delete(id); }); @@ -105,4 +114,4 @@ export class TunnelService extends AbstractTunnelService { } } -registerSingleton(ITunnelService, TunnelService); +registerSingleton(ITunnelService, TunnelService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts index 9070beffdf..10da62beab 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorHandler.ts @@ -17,14 +17,16 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy'; -import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { IWorkingCopyIdentifier, NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { IWorkingCopyEditorHandler, IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; +import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; + import { UNTITLED_NOTEBOOK_TYPEID, UNTITLED_QUERY_EDITOR_TYPEID } from 'sql/workbench/common/constants'; // {{SQL CARBON EDIT}} Handle our untitled inputs as well interface ISerializedUntitledTextEditorInput { - resourceJSON: UriComponents; - modeId: string | undefined; // should be `languageId` but is kept for backwards compatibility - encoding: string | undefined; + readonly resourceJSON: UriComponents; + readonly modeId: string | undefined; // should be `languageId` but is kept for backwards compatibility + readonly encoding: string | undefined; } export class UntitledTextEditorInputSerializer implements IEditorSerializer { @@ -84,39 +86,43 @@ export class UntitledTextEditorInputSerializer implements IEditorSerializer { } } -export class UntitledTextEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution { - - private static readonly UNTITLED_REGEX = /Untitled-\d+/; +export class UntitledTextEditorWorkingCopyEditorHandler extends Disposable implements IWorkbenchContribution, IWorkingCopyEditorHandler { constructor( - @IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService, + @IWorkingCopyEditorService workingCopyEditorService: IWorkingCopyEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IPathService private readonly pathService: IPathService, - @ITextEditorService private readonly textEditorService: ITextEditorService + @ITextEditorService private readonly textEditorService: ITextEditorService, + @IUntitledTextEditorService private readonly untitledTextEditorService: IUntitledTextEditorService ) { super(); - this.installHandler(); + this._register(workingCopyEditorService.registerHandler(this)); } - private installHandler(): void { - this._register(this.workingCopyEditorService.registerHandler({ - handles: workingCopy => workingCopy.resource.scheme === Schemas.untitled && workingCopy.typeId === NO_TYPE_ID, - isOpen: (workingCopy, editor) => (editor instanceof UntitledTextEditorInput || editor.typeId === UNTITLED_QUERY_EDITOR_TYPEID || editor.typeId === UNTITLED_NOTEBOOK_TYPEID) && isEqual(workingCopy.resource, editor.resource), // {{SQL CARBON EDIT}} Handle our untitled inputs as well. Notebook input can't be imported due to layering currently so just use the typeID for that - createEditor: workingCopy => { - let editorInputResource: URI; + handles(workingCopy: IWorkingCopyIdentifier): boolean { + return workingCopy.resource.scheme === Schemas.untitled && workingCopy.typeId === NO_TYPE_ID; + } - // This is a (weak) strategy to find out if the untitled input had - // an associated file path or not by just looking at the path. and - // if so, we must ensure to restore the local resource it had. - if (!UntitledTextEditorWorkingCopyEditorHandler.UNTITLED_REGEX.test(workingCopy.resource.path)) { - editorInputResource = toLocalResource(workingCopy.resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme); - } else { - editorInputResource = workingCopy.resource; - } + isOpen(workingCopy: IWorkingCopyIdentifier, editor: EditorInput): boolean { + if (!this.handles(workingCopy)) { + return false; + } - return this.textEditorService.createTextEditor({ resource: editorInputResource, forceUntitled: true }); - } - })); + return (editor instanceof UntitledTextEditorInput || editor.typeId === UNTITLED_QUERY_EDITOR_TYPEID || editor.typeId === UNTITLED_NOTEBOOK_TYPEID) && isEqual(workingCopy.resource, editor.resource); // {{SQL CARBON EDIT}} Handle our untitled inputs as well. Notebook input can't be imported due to layering currently so just use the typeID for that + } + + createEditor(workingCopy: IWorkingCopyIdentifier): EditorInput { + let editorInputResource: URI; + + // If the untitled has an associated resource, + // ensure to restore the local resource it had + if (this.untitledTextEditorService.isUntitledWithAssociatedResource(workingCopy.resource)) { + editorInputResource = toLocalResource(workingCopy.resource, this.environmentService.remoteAuthority, this.pathService.defaultUriScheme); + } else { + editorInputResource = workingCopy.resource; + } + + return this.textEditorService.createTextEditor({ resource: editorInputResource, forceUntitled: true }); } } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts index 3e8a9b81f1..fbebfbd090 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { DEFAULT_EDITOR_ASSOCIATION, findViewStateForEditor, GroupIdentifier, IUntitledTextResourceEditorInput, IUntypedEditorInput, Verbosity } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, findViewStateForEditor, GroupIdentifier, isUntitledResourceEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { IUntitledTextEditorModel } from 'vs/workbench/services/untitled/common/untitledTextEditorModel'; @@ -16,6 +16,7 @@ import { isEqual, toLocalResource } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; /** * An editor input to be used for untitled text buffers. @@ -41,9 +42,10 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp @IEditorService editorService: IEditorService, @IFileService fileService: IFileService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IPathService private readonly pathService: IPathService + @IPathService private readonly pathService: IPathService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService ) { - super(model.resource, undefined, editorService, textFileService, labelService, fileService); + super(model.resource, undefined, editorService, textFileService, labelService, fileService, filesConfigurationService); this.registerModelListeners(model); } @@ -108,8 +110,8 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return this.model.setEncoding(encoding); } - setLanguageId(languageId: string): void { - this.model.setLanguageId(languageId); + setLanguageId(languageId: string, source?: string): void { + this.model.setLanguageId(languageId, source); } getLanguageId(): string | undefined { @@ -138,7 +140,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp if (typeof options?.preserveViewState === 'number') { untypedInput.encoding = this.getEncoding(); untypedInput.languageId = this.getLanguageId(); - untypedInput.contents = this.model.isDirty() ? this.model.textEditorModel?.getValue() : undefined; + untypedInput.contents = this.model.isModified() ? this.model.textEditorModel?.getValue() : undefined; untypedInput.options.viewState = findViewStateForEditor(this, options.preserveViewState, this.editorService); if (typeof untypedInput.contents === 'string' && !this.model.hasAssociatedFilePath) { @@ -158,7 +160,7 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp } override matches(otherInput: EditorInput | IUntypedEditorInput): boolean { - if (super.matches(otherInput)) { + if (this === otherInput) { return true; } @@ -166,6 +168,10 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp return isEqual(otherInput.resource, this.resource); } + if (isUntitledResourceEditorInput(otherInput)) { + return super.matches(otherInput); + } + return false; } diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts index b24640344b..96d45486b2 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts @@ -10,7 +10,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { IModelService } from 'vs/editor/common/services/model'; import { Event, Emitter } from 'vs/base/common/event'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; +import { ITextResourceConfigurationChangeEvent, ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { ITextModel } from 'vs/editor/common/model'; import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; @@ -161,7 +161,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } // Fetch config - this.onConfigurationChange(false); + this.onConfigurationChange(undefined, false); this.registerListeners(); } @@ -169,42 +169,46 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt private registerListeners(): void { // Config Changes - this._register(this.textResourceConfigurationService.onDidChangeConfiguration(() => this.onConfigurationChange(true))); + this._register(this.textResourceConfigurationService.onDidChangeConfiguration(e => this.onConfigurationChange(e, true))); } - private onConfigurationChange(fromEvent: boolean): void { + private onConfigurationChange(e: ITextResourceConfigurationChangeEvent | undefined, fromEvent: boolean): void { // Encoding - const configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); - if (this.configuredEncoding !== configuredEncoding && typeof configuredEncoding === 'string') { - this.configuredEncoding = configuredEncoding; + if (!e || e.affectsConfiguration(this.resource, 'files.encoding')) { + const configuredEncoding = this.textResourceConfigurationService.getValue(this.resource, 'files.encoding'); + if (this.configuredEncoding !== configuredEncoding && typeof configuredEncoding === 'string') { + this.configuredEncoding = configuredEncoding; - if (fromEvent && !this.preferredEncoding) { - this._onDidChangeEncoding.fire(); // do not fire event if we have a preferred encoding set + if (fromEvent && !this.preferredEncoding) { + this._onDidChangeEncoding.fire(); // do not fire event if we have a preferred encoding set + } } } // Label Format - const configuredLabelFormat = this.textResourceConfigurationService.getValue(this.resource, 'workbench.editor.untitled.labelFormat'); - if (this.configuredLabelFormat !== configuredLabelFormat && (configuredLabelFormat === 'content' || configuredLabelFormat === 'name')) { - this.configuredLabelFormat = configuredLabelFormat; + if (!e || e.affectsConfiguration(this.resource, 'workbench.editor.untitled.labelFormat')) { + const configuredLabelFormat = this.textResourceConfigurationService.getValue(this.resource, 'workbench.editor.untitled.labelFormat'); + if (this.configuredLabelFormat !== configuredLabelFormat && (configuredLabelFormat === 'content' || configuredLabelFormat === 'name')) { + this.configuredLabelFormat = configuredLabelFormat; - if (fromEvent) { - this._onDidChangeName.fire(); + if (fromEvent) { + this._onDidChangeName.fire(); + } } } } //#region Language - override setLanguageId(languageId: string): void { + override setLanguageId(languageId: string, source?: string): void { const actualLanguage: string | undefined = languageId === UntitledTextEditorModel.ACTIVE_EDITOR_LANGUAGE_ID ? this.editorService.activeTextEditorLanguageId : languageId; this.preferredLanguageId = actualLanguage; if (actualLanguage) { - super.setLanguageId(actualLanguage); + super.setLanguageId(actualLanguage, source); } } @@ -246,9 +250,11 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return this.dirty; } - // {{SQL CARBON EDIT}} - // make property public - public setDirty(dirty: boolean): void { + isModified(): boolean { + return this.isDirty(); + } + + public setDirty(dirty: boolean): void { // {{SQL CARBON EDIT}} - make method public if (this.dirty === dirty) { return; } @@ -273,6 +279,8 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt } async revert(): Promise { + + // No longer dirty this.setDirty(false); // Emit as event @@ -342,8 +350,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt // Listen to text model events const textEditorModel = assertIsDefined(this.textEditorModel); - this._register(textEditorModel.onDidChangeContent(e => this.onModelContentChanged(textEditorModel, e))); - this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange(true))); // language change can have impact on config + this.installModelListeners(textEditorModel); // Only adjust name and dirty state etc. if we // actually created the untitled model @@ -367,6 +374,13 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt return super.resolve(); } + protected override installModelListeners(model: ITextModel): void { + this._register(model.onDidChangeContent(e => this.onModelContentChanged(model, e))); + this._register(model.onDidChangeLanguage(() => this.onConfigurationChange(undefined, true))); // language change can have impact on config + + super.installModelListeners(model); + } + private onModelContentChanged(textEditorModel: ITextModel, e: IModelContentChangedEvent): void { // mark the untitled text editor as non-dirty once its content becomes empty and we do diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts index 44644f69c8..95932d4886 100644 --- a/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledTextEditorService.ts @@ -12,7 +12,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; export const IUntitledTextEditorService = createDecorator('untitledTextEditorService'); @@ -112,6 +112,11 @@ export interface IUntitledTextEditorModelManager { resolve(options?: INewUntitledTextEditorOptions): Promise; resolve(options?: INewUntitledTextEditorWithAssociatedResourceOptions): Promise; resolve(options?: IExistingUntitledTextEditorOptions): Promise; + + /** + * Figures out if the given resource has an associated resource or not. + */ + isUntitledWithAssociatedResource(resource: URI): boolean; } export interface IUntitledTextEditorService extends IUntitledTextEditorModelManager { @@ -123,6 +128,8 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe declare readonly _serviceBrand: undefined; + private static readonly UNTITLED_WITHOUT_ASSOCIATED_RESOURCE_REGEX = /Untitled-\d+/; + private readonly _onDidChangeDirty = this._register(new Emitter()); readonly onDidChangeDirty = this._onDidChangeDirty.event; @@ -259,6 +266,10 @@ export class UntitledTextEditorService extends Disposable implements IUntitledTe this._onDidChangeDirty.fire(model); } } + + isUntitledWithAssociatedResource(resource: URI): boolean { + return resource.scheme === Schemas.untitled && resource.path.length > 1 && !UntitledTextEditorService.UNTITLED_WITHOUT_ASSOCIATED_RESOURCE_REGEX.test(resource.path); + } } -registerSingleton(IUntitledTextEditorService, UntitledTextEditorService, true); +registerSingleton(IUntitledTextEditorService, UntitledTextEditorService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.integrationTest.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.integrationTest.ts new file mode 100644 index 0000000000..0a91f18839 --- /dev/null +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.integrationTest.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { UntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; +import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/browser/workbenchTestServices'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; + +suite('Untitled text editors', () => { + + let disposables: DisposableStore; + let instantiationService: IInstantiationService; + let accessor: TestServiceAccessor; + + setup(() => { + disposables = new DisposableStore(); + instantiationService = workbenchInstantiationService(undefined, disposables); + accessor = instantiationService.createInstance(TestServiceAccessor); + }); + + teardown(() => { + (accessor.untitledTextEditorService as UntitledTextEditorService).dispose(); + disposables.dispose(); + }); + + test('backup and restore (simple)', async function () { + return testBackupAndRestore('Some very small file text content.'); + }); + + test('backup and restore (large, #121347)', async function () { + const largeContent = '국어한\n'.repeat(100000); + return testBackupAndRestore(largeContent); + }); + + async function testBackupAndRestore(content: string) { + const service = accessor.untitledTextEditorService; + const originalInput = instantiationService.createInstance(UntitledTextEditorInput, service.create()); + const restoredInput = instantiationService.createInstance(UntitledTextEditorInput, service.create()); + + const originalModel = await originalInput.resolve(); + originalModel.textEditorModel?.setValue(content); + + const backup = await originalModel.backup(CancellationToken.None); + const modelRestoredIdentifier = { typeId: originalModel.typeId, resource: restoredInput.resource }; + await accessor.workingCopyBackupService.backup(modelRestoredIdentifier, backup.content); + + const restoredModel = await restoredInput.resolve(); + + assert.strictEqual(restoredModel.textEditorModel?.getValue(), content); + assert.strictEqual(restoredModel.isDirty(), true); + + originalInput.dispose(); + originalModel.dispose(); + restoredInput.dispose(); + restoredModel.dispose(); + } +}); diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts index 6d2a1346c7..fb21b98d05 100644 --- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts +++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts @@ -20,6 +20,7 @@ import { EditorInputCapabilities } from 'vs/workbench/common/editor'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { isReadable, isReadableStream } from 'vs/base/common/stream'; import { readableToBuffer, streamToBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { LanguageDetectionLanguageEventSource } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; suite('Untitled text editors', () => { @@ -45,6 +46,7 @@ suite('Untitled text editors', () => { const input1 = instantiationService.createInstance(UntitledTextEditorInput, service.create()); await input1.resolve(); assert.strictEqual(service.get(input1.resource), input1.model); + assert.ok(!accessor.untitledTextEditorService.isUntitledWithAssociatedResource(input1.resource)); assert.ok(service.get(input1.resource)); assert.ok(!service.get(URI.file('testing'))); @@ -53,6 +55,7 @@ suite('Untitled text editors', () => { assert.ok(!input1.hasCapability(EditorInputCapabilities.Readonly)); assert.ok(!input1.hasCapability(EditorInputCapabilities.Singleton)); assert.ok(!input1.hasCapability(EditorInputCapabilities.RequiresTrust)); + assert.ok(!input1.hasCapability(EditorInputCapabilities.Scratchpad)); const input2 = instantiationService.createInstance(UntitledTextEditorInput, service.create()); assert.strictEqual(service.get(input2.resource), input2.model); @@ -136,6 +139,7 @@ suite('Untitled text editors', () => { }); const model = service.create({ associatedResource: file }); + assert.ok(accessor.untitledTextEditorService.isUntitledWithAssociatedResource(model.resource)); const untitled = instantiationService.createInstance(UntitledTextEditorInput, model); assert.ok(untitled.isDirty()); assert.strictEqual(model, onDidChangeDirtyModel); @@ -334,6 +338,54 @@ suite('Untitled text editors', () => { registration.dispose(); }); + // Issue #159202 + test('remembers that language was set explicitly if set by another source (i.e. ModelService)', async () => { + const language = 'untitled-input-test'; + + const registration = accessor.languageService.registerLanguage({ + id: language, + }); + + const service = accessor.untitledTextEditorService; + const model = service.create(); + const input = instantiationService.createInstance(UntitledTextEditorInput, model); + await input.resolve(); + + assert.ok(!input.model.hasLanguageSetExplicitly); + model.textEditorModel!.setLanguage(accessor.languageService.createById(language)); + assert.ok(input.model.hasLanguageSetExplicitly); + + assert.strictEqual(model.getLanguageId(), language); + + model.dispose(); + registration.dispose(); + }); + + test('Language is not set explicitly if set by language detection source', async () => { + const language = 'untitled-input-test'; + + const registration = accessor.languageService.registerLanguage({ + id: language, + }); + + const service = accessor.untitledTextEditorService; + const model = service.create(); + const input = instantiationService.createInstance(UntitledTextEditorInput, model); + await input.resolve(); + + assert.ok(!input.model.hasLanguageSetExplicitly); + model.textEditorModel!.setLanguage( + accessor.languageService.createById(language), + // This is really what this is testing + LanguageDetectionLanguageEventSource); + assert.ok(!input.model.hasLanguageSetExplicitly); + + assert.strictEqual(model.getLanguageId(), language); + + model.dispose(); + registration.dispose(); + }); + test('service#onDidChangeEncoding', async () => { const service = accessor.untitledTextEditorService; const input = instantiationService.createInstance(UntitledTextEditorInput, service.create()); @@ -563,36 +615,4 @@ suite('Untitled text editors', () => { input.dispose(); model.dispose(); }); - - test('backup and restore (simple)', async function () { - return testBackupAndRestore('Some very small file text content.'); - }); - - test('backup and restore (large, #121347)', async function () { - const largeContent = '국어한\n'.repeat(100000); - return testBackupAndRestore(largeContent); - }); - - async function testBackupAndRestore(content: string) { - const service = accessor.untitledTextEditorService; - const originalInput = instantiationService.createInstance(UntitledTextEditorInput, service.create()); - const restoredInput = instantiationService.createInstance(UntitledTextEditorInput, service.create()); - - const originalModel = await originalInput.resolve(); - originalModel.textEditorModel?.setValue(content); - - const backup = await originalModel.backup(CancellationToken.None); - const modelRestoredIdentifier = { typeId: originalModel.typeId, resource: restoredInput.resource }; - await accessor.workingCopyBackupService.backup(modelRestoredIdentifier, backup.content); - - const restoredModel = await restoredInput.resolve(); - - assert.strictEqual(restoredModel.textEditorModel?.getValue(), content); - assert.strictEqual(restoredModel.isDirty(), true); - - originalInput.dispose(); - originalModel.dispose(); - restoredInput.dispose(); - restoredModel.dispose(); - } }); diff --git a/src/vs/workbench/services/update/browser/updateService.ts b/src/vs/workbench/services/update/browser/updateService.ts index dad6214db5..f1d6e4a555 100644 --- a/src/vs/workbench/services/update/browser/updateService.ts +++ b/src/vs/workbench/services/update/browser/updateService.ts @@ -5,7 +5,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IUpdateService, State, UpdateType } from 'vs/platform/update/common/update'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -47,8 +47,11 @@ export class BrowserUpdateService extends Disposable implements IUpdateService { this.checkForUpdates(false); } - async isLatestVersion(): Promise { + async isLatestVersion(): Promise { const update = await this.doCheckForUpdates(false); + if (update === undefined) { + return undefined; // no update provider + } return !!update; } @@ -57,7 +60,7 @@ export class BrowserUpdateService extends Disposable implements IUpdateService { await this.doCheckForUpdates(explicit); } - private async doCheckForUpdates(explicit: boolean): Promise { + private async doCheckForUpdates(explicit: boolean): Promise { if (this.environmentService.options && this.environmentService.options.updateProvider) { const updateProvider = this.environmentService.options.updateProvider; @@ -76,7 +79,7 @@ export class BrowserUpdateService extends Disposable implements IUpdateService { return update; } - return null; // no update provider to ask + return undefined; // no update provider to ask } async downloadUpdate(): Promise { @@ -96,4 +99,4 @@ export class BrowserUpdateService extends Disposable implements IUpdateService { } } -registerSingleton(IUpdateService, BrowserUpdateService); +registerSingleton(IUpdateService, BrowserUpdateService, InstantiationType.Eager); diff --git a/src/vs/workbench/services/url/browser/urlService.ts b/src/vs/workbench/services/url/browser/urlService.ts index ed94b34172..ab523d2d66 100644 --- a/src/vs/workbench/services/url/browser/urlService.ts +++ b/src/vs/workbench/services/url/browser/urlService.ts @@ -5,7 +5,7 @@ import { IURLService } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractURLService } from 'vs/platform/url/common/urlService'; import { Event } from 'vs/base/common/event'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; @@ -91,4 +91,4 @@ export class BrowserURLService extends AbstractURLService { } } -registerSingleton(IURLService, BrowserURLService, true); +registerSingleton(IURLService, BrowserURLService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/url/electron-sandbox/urlService.ts b/src/vs/workbench/services/url/electron-sandbox/urlService.ts index 50cdee0271..6e7eb6a623 100644 --- a/src/vs/workbench/services/url/electron-sandbox/urlService.ts +++ b/src/vs/workbench/services/url/electron-sandbox/urlService.ts @@ -5,14 +5,15 @@ import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; import { URLHandlerChannel } from 'vs/platform/url/common/urlIpc'; import { IOpenerService, IOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { INativeHostService } from 'vs/platform/native/common/native'; import { NativeURLService } from 'vs/platform/url/common/urlService'; +import { ILogService } from 'vs/platform/log/common/log'; export interface IRelayOpenURLOptions extends IOpenURLOptions { openToSide?: boolean; @@ -27,7 +28,8 @@ export class RelayURLService extends NativeURLService implements IURLHandler, IO @IMainProcessService mainProcessService: IMainProcessService, @IOpenerService openerService: IOpenerService, @INativeHostService private readonly nativeHostService: INativeHostService, - @IProductService productService: IProductService + @IProductService productService: IProductService, + @ILogService private readonly logService: ILogService ) { super(productService); @@ -66,11 +68,15 @@ export class RelayURLService extends NativeURLService implements IURLHandler, IO const result = await super.open(uri, options); if (result) { + this.logService.trace('URLService#handleURL(): handled', uri.toString(true)); + await this.nativeHostService.focusWindow({ force: true /* Application may not be active */ }); + } else { + this.logService.trace('URLService#handleURL(): not handled', uri.toString(true)); } return result; } } -registerSingleton(IURLService, RelayURLService); +registerSingleton(IURLService, RelayURLService, InstantiationType.Eager); diff --git a/src/vs/workbench/services/userActivity/browser/domActivityTracker.ts b/src/vs/workbench/services/userActivity/browser/domActivityTracker.ts new file mode 100644 index 0000000000..6bca1c7a2f --- /dev/null +++ b/src/vs/workbench/services/userActivity/browser/domActivityTracker.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as dom from 'vs/base/browser/dom'; +import { IntervalTimer } from 'vs/base/common/async'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { IUserActivityService } from 'vs/workbench/services/userActivity/common/userActivityService'; + +/** + * This uses a time interval and checks whether there's any activity in that + * interval. A naive approach might be to use a debounce whenever an event + * happens, but this has some scheduling overhead. Instead, the tracker counts + * how many intervals have elapsed since any activity happened. + * + * If there's more than `MIN_INTERVALS_WITHOUT_ACTIVITY`, then say the user is + * inactive. Therefore the maximum time before an inactive user is detected + * is `CHECK_INTERVAL * (MIN_INTERVALS_WITHOUT_ACTIVITY + 1)`. + */ +const CHECK_INTERVAL = 30_000; + +/** See {@link CHECK_INTERVAL} */ +const MIN_INTERVALS_WITHOUT_ACTIVITY = 2; + +const eventListenerOptions: AddEventListenerOptions = { + passive: true, /** does not preventDefault() */ + capture: true, /** should dispatch first (before anyone stopPropagation()) */ +}; + +export class DomActivityTracker extends Disposable { + constructor(userActivityService: IUserActivityService) { + super(); + + let intervalsWithoutActivity = MIN_INTERVALS_WITHOUT_ACTIVITY; + const intervalTimer = this._register(new IntervalTimer()); + const activeMutex = this._register(new MutableDisposable()); + activeMutex.value = userActivityService.markActive(); + + const onInterval = () => { + if (++intervalsWithoutActivity === MIN_INTERVALS_WITHOUT_ACTIVITY) { + activeMutex.clear(); + intervalTimer.cancel(); + } + }; + + const onActivity = () => { + // if was inactive, they've now returned + if (intervalsWithoutActivity === MIN_INTERVALS_WITHOUT_ACTIVITY) { + activeMutex.value = userActivityService.markActive(); + intervalTimer.cancelAndSet(onInterval, CHECK_INTERVAL); + } + + intervalsWithoutActivity = 0; + }; + + this._register(dom.addDisposableListener(document, 'touchstart', onActivity, eventListenerOptions)); + this._register(dom.addDisposableListener(document, 'mousedown', onActivity, eventListenerOptions)); + this._register(dom.addDisposableListener(document, 'keydown', onActivity, eventListenerOptions)); + + onActivity(); + } +} diff --git a/src/vs/workbench/services/userActivity/browser/userActivityBrowser.ts b/src/vs/workbench/services/userActivity/browser/userActivityBrowser.ts new file mode 100644 index 0000000000..702a859edd --- /dev/null +++ b/src/vs/workbench/services/userActivity/browser/userActivityBrowser.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { DomActivityTracker } from 'vs/workbench/services/userActivity/browser/domActivityTracker'; +import { userActivityRegistry } from 'vs/workbench/services/userActivity/common/userActivityRegistry'; + +userActivityRegistry.add(DomActivityTracker); diff --git a/src/vs/workbench/services/userActivity/common/userActivityRegistry.ts b/src/vs/workbench/services/userActivity/common/userActivityRegistry.ts new file mode 100644 index 0000000000..b3f7f67567 --- /dev/null +++ b/src/vs/workbench/services/userActivity/common/userActivityRegistry.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IUserActivityService } from 'vs/workbench/services/userActivity/common/userActivityService'; + +class UserActivityRegistry { + private todo: { new(s: IUserActivityService, ...args: any[]): any }[] = []; + + public add = (ctor: { new(s: IUserActivityService, ...args: any[]): any }) => { + this.todo!.push(ctor); + }; + + public take(userActivityService: IUserActivityService, instantiation: IInstantiationService) { + this.add = ctor => instantiation.createInstance(ctor, userActivityService); + this.todo.forEach(this.add); + this.todo = []; + } +} + +export const userActivityRegistry = new UserActivityRegistry(); diff --git a/src/vs/workbench/services/userActivity/common/userActivityService.ts b/src/vs/workbench/services/userActivity/common/userActivityService.ts new file mode 100644 index 0000000000..30196f584e --- /dev/null +++ b/src/vs/workbench/services/userActivity/common/userActivityService.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { userActivityRegistry } from 'vs/workbench/services/userActivity/common/userActivityRegistry'; + +/** + * Service that observes user activity in the window. + */ +export interface IUserActivityService { + _serviceBrand: undefined; + + /** + * Whether the user is currently active. + */ + readonly isActive: boolean; + + /** + * Fires when the activity state changes. + */ + readonly onDidChangeIsActive: Event; + + /** + * Marks the user as being active until the Disposable is disposed of. + * Multiple consumers call this method; the user will only be considered + * inactive once all consumers have disposed of their Disposables. + */ + markActive(): IDisposable; +} + +export const IUserActivityService = createDecorator('IUserActivityService'); + +export class UserActivityService extends Disposable implements IUserActivityService { + declare readonly _serviceBrand: undefined; + private readonly markInactive = this._register(new RunOnceScheduler(() => { + this.isActive = false; + this.changeEmitter.fire(false); + }, 10_000)); + + private readonly changeEmitter = this._register(new Emitter); + private active = 0; + + /** + * @inheritdoc + * + * Note: initialized to true, since the user just did something to open the + * window. The bundled DomActivityTracker will initially assume activity + * as well in order to unset this if the window gets abandoned. + */ + public isActive = true; + + /** @inheritdoc */ + onDidChangeIsActive: Event = this.changeEmitter.event; + + constructor(@IInstantiationService instantiationService: IInstantiationService) { + super(); + this._register(runWhenIdle(() => userActivityRegistry.take(this, instantiationService))); + } + + /** @inheritdoc */ + markActive(): IDisposable { + if (++this.active === 1) { + this.isActive = true; + this.changeEmitter.fire(true); + this.markInactive.cancel(); + } + + return toDisposable(() => { + if (--this.active === 0) { + this.markInactive.schedule(); + } + }); + } +} + +registerSingleton(IUserActivityService, UserActivityService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userActivity/test/browser/domActivityTracker.test.ts b/src/vs/workbench/services/userActivity/test/browser/domActivityTracker.test.ts new file mode 100644 index 0000000000..8a8531e8b4 --- /dev/null +++ b/src/vs/workbench/services/userActivity/test/browser/domActivityTracker.test.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { DomActivityTracker } from 'vs/workbench/services/userActivity/browser/domActivityTracker'; +import { UserActivityService } from 'vs/workbench/services/userActivity/common/userActivityService'; +import * as sinon from 'sinon'; +import * as assert from 'assert'; + +suite('DomActivityTracker', () => { + let uas: UserActivityService; + let dom: DomActivityTracker; + let clock: sinon.SinonFakeTimers; + const maxTimeToBecomeIdle = 3 * 30_000; // (MIN_INTERVALS_WITHOUT_ACTIVITY + 1) * CHECK_INTERVAL; + + setup(() => { + clock = sinon.useFakeTimers(); + uas = new UserActivityService(new TestInstantiationService()); + dom = new DomActivityTracker(uas); + }); + + teardown(() => { + dom.dispose(); + uas.dispose(); + clock.restore(); + }); + + + test('marks inactive on no input', () => { + assert.equal(uas.isActive, true); + clock.tick(maxTimeToBecomeIdle); + assert.equal(uas.isActive, false); + }); + + test('preserves activity state when active', () => { + assert.equal(uas.isActive, true); + + const div = 10; + for (let i = 0; i < div; i++) { + document.dispatchEvent(new MouseEvent('keydown')); + clock.tick(maxTimeToBecomeIdle / div); + } + + assert.equal(uas.isActive, true); + }); + + test('restores active state', () => { + assert.equal(uas.isActive, true); + clock.tick(maxTimeToBecomeIdle); + assert.equal(uas.isActive, false); + + document.dispatchEvent(new MouseEvent('keydown')); + assert.equal(uas.isActive, true); + + clock.tick(maxTimeToBecomeIdle); + assert.equal(uas.isActive, false); + }); +}); diff --git a/src/vs/workbench/services/userData/browser/userDataInit.ts b/src/vs/workbench/services/userData/browser/userDataInit.ts index 7b6746cb74..66d584b1ed 100644 --- a/src/vs/workbench/services/userData/browser/userDataInit.ts +++ b/src/vs/workbench/services/userData/browser/userDataInit.ts @@ -3,46 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { AbstractExtensionsInitializer, IExtensionsInitializerPreviewResult } from 'vs/platform/userDataSync/common/extensionsSync'; -import { GlobalStateInitializer, UserDataSyncStoreTypeSynchronizer } from 'vs/platform/userDataSync/common/globalStateSync'; -import { KeybindingsInitializer } from 'vs/platform/userDataSync/common/keybindingsSync'; -import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSync'; -import { SnippetsInitializer } from 'vs/platform/userDataSync/common/snippetsSync'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILogService } from 'vs/platform/log/common/log'; -import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IRequestService } from 'vs/platform/request/common/request'; -import { IRemoteUserData, IUserData, IUserDataInitializer, IUserDataSyncLogService, IUserDataSyncStoreClient, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; -import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; -import { getSyncAreaLabel } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { Barrier, Promises } from 'vs/base/common/async'; -import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { mark } from 'vs/base/common/performance'; -import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { isEqual } from 'vs/base/common/resources'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; -import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; -import { TasksInitializer } from 'vs/platform/userDataSync/common/tasksSync'; -import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; - -export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); -export interface IUserDataInitializationService { - _serviceBrand: any; +export interface IUserDataInitializer { requiresInitialization(): Promise; whenInitializationFinished(): Promise; initializeRequiredResources(): Promise; @@ -50,378 +19,46 @@ export interface IUserDataInitializationService { initializeOtherResources(instantiationService: IInstantiationService): Promise; } +export const IUserDataInitializationService = createDecorator('IUserDataInitializationService'); +export interface IUserDataInitializationService extends IUserDataInitializer { + _serviceBrand: any; +} + export class UserDataInitializationService implements IUserDataInitializationService { _serviceBrand: any; - private readonly initialized: SyncResource[] = []; - private readonly initializationFinished = new Barrier(); - private globalStateUserData: IUserData | null = null; - - constructor( - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @ICredentialsService private readonly credentialsService: ICredentialsService, - @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, - @IFileService private readonly fileService: IFileService, - @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, - @IStorageService private readonly storageService: IStorageService, - @IProductService private readonly productService: IProductService, - @IRequestService private readonly requestService: IRequestService, - @ILogService private readonly logService: ILogService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, - ) { - this.createUserDataSyncStoreClient().then(userDataSyncStoreClient => { - if (!userDataSyncStoreClient) { - this.initializationFinished.open(); - } - }); - } - - private _userDataSyncStoreClientPromise: Promise | undefined; - private createUserDataSyncStoreClient(): Promise { - if (!this._userDataSyncStoreClientPromise) { - this._userDataSyncStoreClientPromise = (async (): Promise => { - try { - if (!isWeb) { - this.logService.trace(`Skipping initializing user data in desktop`); - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - if (!this.storageService.isNew(StorageScope.APPLICATION)) { - this.logService.trace(`Skipping initializing user data as application was opened before`); - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - if (!this.storageService.isNew(StorageScope.WORKSPACE)) { - this.logService.trace(`Skipping initializing user data as workspace was opened before`); - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - let authenticationSession; - try { - authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService); - } catch (error) { - this.logService.error(error); - } - if (!authenticationSession) { - this.logService.trace(`Skipping initializing user data as authentication session is not set`); - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - await this.initializeUserDataSyncStore(authenticationSession); - - const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; - if (!userDataSyncStore) { - this.logService.trace(`Skipping initializing user data as sync service is not provided`); - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - const userDataSyncStoreClient = new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); - userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); - - const manifest = await userDataSyncStoreClient.manifest(null); - if (manifest === null) { - userDataSyncStoreClient.dispose(); - this.logService.trace(`Skipping initializing user data as there is no data`); - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - - this.logService.info(`Using settings sync service ${userDataSyncStore.url.toString()} for initialization`); - return userDataSyncStoreClient; - - } catch (error) { - this.logService.error(error); - return undefined; // {{SQL CARBON EDIT}} strict-null-check - } - })(); - } - - return this._userDataSyncStoreClientPromise; - } - - private async initializeUserDataSyncStore(authenticationSession: AuthenticationSessionInfo): Promise { - const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; - if (!userDataSyncStore?.canSwitch) { - return; - } - - const disposables = new DisposableStore(); - try { - const userDataSyncStoreClient = disposables.add(new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService)); - userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); - - // Cache global state data for global state initialization - this.globalStateUserData = await userDataSyncStoreClient.read(SyncResource.GlobalState, null); - - if (this.globalStateUserData) { - const userDataSyncStoreType = new UserDataSyncStoreTypeSynchronizer(userDataSyncStoreClient, this.storageService, this.environmentService, this.fileService, this.logService).getSyncStoreType(this.globalStateUserData); - if (userDataSyncStoreType) { - await this.userDataSyncStoreManagementService.switch(userDataSyncStoreType); - - // Unset cached global state data if urls are changed - if (!isEqual(userDataSyncStore.url, this.userDataSyncStoreManagementService.userDataSyncStore?.url)) { - this.logService.info('Switched settings sync store'); - this.globalStateUserData = null; - } - } - } - } finally { - disposables.dispose(); - } + constructor(private readonly initializers: IUserDataInitializer[] = []) { } async whenInitializationFinished(): Promise { - await this.initializationFinished.wait(); + if (await this.requiresInitialization()) { + await Promise.all(this.initializers.map(initializer => initializer.whenInitializationFinished())); + } } async requiresInitialization(): Promise { - this.logService.trace(`UserDataInitializationService#requiresInitialization`); - const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); - return !!userDataSyncStoreClient; + return (await Promise.all(this.initializers.map(initializer => initializer.requiresInitialization()))).some(result => result); } async initializeRequiredResources(): Promise { - this.logService.trace(`UserDataInitializationService#initializeRequiredResources`); - return this.initialize([SyncResource.Settings, SyncResource.GlobalState]); + if (await this.requiresInitialization()) { + await Promise.all(this.initializers.map(initializer => initializer.initializeRequiredResources())); + } } async initializeOtherResources(instantiationService: IInstantiationService): Promise { - try { - this.logService.trace(`UserDataInitializationService#initializeOtherResources`); - await Promise.allSettled([this.initialize([SyncResource.Keybindings, SyncResource.Snippets, SyncResource.Tasks]), this.initializeExtensions(instantiationService)]); - } finally { - this.initializationFinished.open(); + if (await this.requiresInitialization()) { + await Promise.all(this.initializers.map(initializer => initializer.initializeOtherResources(instantiationService))); } } - private async initializeExtensions(instantiationService: IInstantiationService): Promise { - try { - await Promise.all([this.initializeInstalledExtensions(instantiationService), this.initializeNewExtensions(instantiationService)]); - } finally { - this.initialized.push(SyncResource.Extensions); - } - } - - private initializeInstalledExtensionsPromise: Promise | undefined; async initializeInstalledExtensions(instantiationService: IInstantiationService): Promise { - if (!this.initializeInstalledExtensionsPromise) { - this.initializeInstalledExtensionsPromise = (async () => { - this.logService.trace(`UserDataInitializationService#initializeInstalledExtensions`); - const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); - if (extensionsPreviewInitializer) { - await instantiationService.createInstance(InstalledExtensionsInitializer, extensionsPreviewInitializer).initialize(); - } - })(); - } - return this.initializeInstalledExtensionsPromise; - } - - private initializeNewExtensionsPromise: Promise | undefined; - private async initializeNewExtensions(instantiationService: IInstantiationService): Promise { - if (!this.initializeNewExtensionsPromise) { - this.initializeNewExtensionsPromise = (async () => { - this.logService.trace(`UserDataInitializationService#initializeNewExtensions`); - const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); - if (extensionsPreviewInitializer) { - await instantiationService.createInstance(NewExtensionsInitializer, extensionsPreviewInitializer).initialize(); - } - })(); - } - return this.initializeNewExtensionsPromise; - } - - private extensionsPreviewInitializerPromise: Promise | undefined; - private getExtensionsPreviewInitializer(instantiationService: IInstantiationService): Promise { - if (!this.extensionsPreviewInitializerPromise) { - this.extensionsPreviewInitializerPromise = (async () => { - const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); - if (!userDataSyncStoreClient) { - return null; - } - const userData = await userDataSyncStoreClient.read(SyncResource.Extensions, null); - return instantiationService.createInstance(ExtensionsPreviewInitializer, userData); - })(); - } - return this.extensionsPreviewInitializerPromise; - } - - private async initialize(syncResources: SyncResource[]): Promise { - const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); - if (!userDataSyncStoreClient) { - return; - } - - await Promises.settled(syncResources.map(async syncResource => { - try { - if (this.initialized.includes(syncResource)) { - this.logService.info(`${getSyncAreaLabel(syncResource)} initialized already.`); - return; - } - this.initialized.push(syncResource); - this.logService.trace(`Initializing ${getSyncAreaLabel(syncResource)}`); - const initializer = this.createSyncResourceInitializer(syncResource); - const userData = await userDataSyncStoreClient.read(syncResource, syncResource === SyncResource.GlobalState ? this.globalStateUserData : null); - await initializer.initialize(userData); - this.logService.info(`Initialized ${getSyncAreaLabel(syncResource)}`); - } catch (error) { - this.logService.info(`Error while initializing ${getSyncAreaLabel(syncResource)}`); - this.logService.error(error); - } - })); - } - - private createSyncResourceInitializer(syncResource: SyncResource): IUserDataInitializer { - switch (syncResource) { - case SyncResource.Settings: return new SettingsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.uriIdentityService); - case SyncResource.Keybindings: return new KeybindingsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.uriIdentityService); - case SyncResource.Tasks: return new TasksInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.uriIdentityService); - case SyncResource.Snippets: return new SnippetsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.uriIdentityService); - case SyncResource.GlobalState: return new GlobalStateInitializer(this.storageService, this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.uriIdentityService); - } - throw new Error(`Cannot create initializer for ${syncResource}`); - } - -} - -class ExtensionsPreviewInitializer extends AbstractExtensionsInitializer { - - private previewPromise: Promise | undefined; - private preview: IExtensionsInitializerPreviewResult | null = null; - - constructor( - private readonly extensionsData: IUserData, - @IExtensionManagementService extensionManagementService: IExtensionManagementService, - @IIgnoredExtensionsManagementService ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, - @IFileService fileService: IFileService, - @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, - @IEnvironmentService environmentService: IEnvironmentService, - @IUserDataSyncLogService logService: IUserDataSyncLogService, - @IUriIdentityService uriIdentityService: IUriIdentityService, - ) { - super(extensionManagementService, ignoredExtensionsManagementService, fileService, userDataProfilesService, environmentService, logService, uriIdentityService); - } - - getPreview(): Promise { - if (!this.previewPromise) { - this.previewPromise = super.initialize(this.extensionsData).then(() => this.preview); - } - return this.previewPromise; - } - - override initialize(): Promise { - throw new Error('should not be called directly'); - } - - protected override async doInitialize(remoteUserData: IRemoteUserData): Promise { - const remoteExtensions = await this.parseExtensions(remoteUserData); - if (!remoteExtensions) { - this.logService.info('Skipping initializing extensions because remote extensions does not exist.'); - return; - } - const installedExtensions = await this.extensionManagementService.getInstalled(); - this.preview = this.generatePreview(remoteExtensions, installedExtensions); - } -} - -class InstalledExtensionsInitializer implements IUserDataInitializer { - - constructor( - private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, - @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, - @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - ) { - } - - async initialize(): Promise { - const preview = await this.extensionsPreviewInitializer.getPreview(); - if (!preview) { - return; - } - - // 1. Initialise already installed extensions state - for (const installedExtension of preview.installedExtensions) { - const syncExtension = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, installedExtension.identifier)); - if (syncExtension?.state) { - const extensionState = this.extensionStorageService.getExtensionState(installedExtension, true) || {}; - Object.keys(syncExtension.state).forEach(key => extensionState[key] = syncExtension.state![key]); - this.extensionStorageService.setExtensionState(installedExtension, extensionState, true); - } - } - - // 2. Initialise extensions enablement - if (preview.disabledExtensions.length) { - for (const identifier of preview.disabledExtensions) { - this.logService.trace(`Disabling extension...`, identifier.id); - await this.extensionEnablementService.disableExtension(identifier); - this.logService.info(`Disabling extension`, identifier.id); - } - } - } -} - -class NewExtensionsInitializer implements IUserDataInitializer { - - constructor( - private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, - @IExtensionService private readonly extensionService: IExtensionService, - @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, - @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, - ) { - } - - async initialize(): Promise { - const preview = await this.extensionsPreviewInitializer.getPreview(); - if (!preview) { - return; - } - - const newlyEnabledExtensions: ILocalExtension[] = []; - const targetPlatform = await this.extensionManagementService.getTargetPlatform(); - const galleryExtensions = await this.galleryService.getExtensions(preview.newExtensions, { targetPlatform, compatible: true }, CancellationToken.None); - for (const galleryExtension of galleryExtensions) { - try { - const extensionToSync = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, galleryExtension.identifier)); - if (!extensionToSync) { - continue; - } - if (extensionToSync.state) { - this.extensionStorageService.setExtensionState(galleryExtension, extensionToSync.state, true); - } - this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); - const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: extensionToSync.preRelease } /* set isMachineScoped to prevent install and sync dialog in web */); - if (!preview.disabledExtensions.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { - newlyEnabledExtensions.push(local); - } - this.logService.info(`Installed extension.`, galleryExtension.identifier.id); - } catch (error) { - this.logService.error(error); - } - } - - const canEnabledExtensions = newlyEnabledExtensions.filter(e => this.extensionService.canAddExtension(toExtensionDescription(e))); - if (!(await this.areExtensionsRunning(canEnabledExtensions))) { - await new Promise((c, e) => { - const disposable = this.extensionService.onDidChangeExtensions(async () => { - try { - if (await this.areExtensionsRunning(canEnabledExtensions)) { - disposable.dispose(); - c(); - } - } catch (error) { - e(error); - } - }); - }); + if (await this.requiresInitialization()) { + await Promise.all(this.initializers.map(initializer => initializer.initializeInstalledExtensions(instantiationService))); } } - private async areExtensionsRunning(extensions: ILocalExtension[]): Promise { - const runningExtensions = await this.extensionService.getExtensions(); - return extensions.every(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value }, e.identifier))); - } } class InitializeOtherResourcesContribution implements IWorkbenchContribution { diff --git a/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts new file mode 100644 index 0000000000..42e3ca1cb8 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/extensionsResource.ts @@ -0,0 +1,313 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT, IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +interface IProfileExtension { + identifier: IExtensionIdentifier; + displayName?: string; + preRelease?: boolean; + disabled?: boolean; + version?: string; +} + +export class ExtensionsResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, + @ILogService private readonly logService: ILogService, + ) { + } + + async initialize(content: string): Promise { + const profileExtensions: IProfileExtension[] = JSON.parse(content); + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, this.userDataProfileService.currentProfile.extensionsResource); + const extensionsToEnableOrDisable: { extension: IExtensionIdentifier; enable: boolean }[] = []; + const extensionsToInstall: IProfileExtension[] = []; + for (const e of profileExtensions) { + const isDisabled = this.extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); + const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier)); + if (!installedExtension || (!installedExtension.isBuiltin && installedExtension.preRelease !== e.preRelease)) { + extensionsToInstall.push(e); + } + if (isDisabled !== !!e.disabled) { + extensionsToEnableOrDisable.push({ extension: e.identifier, enable: !e.disabled }); + } + } + const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => !extension.isBuiltin && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier))); + for (const { extension, enable } of extensionsToEnableOrDisable) { + if (enable) { + this.logService.trace(`Initializing Profile: Enabling extension...`, extension.id); + await this.extensionEnablementService.enableExtension(extension); + this.logService.info(`Initializing Profile: Enabled extension...`, extension.id); + } else { + this.logService.trace(`Initializing Profile: Disabling extension...`, extension.id); + await this.extensionEnablementService.disableExtension(extension); + this.logService.info(`Initializing Profile: Disabled extension...`, extension.id); + } + } + if (extensionsToInstall.length) { + const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsToInstall.map(e => ({ ...e.identifier, version: e.version, hasPreRelease: e.version ? undefined : e.preRelease })), CancellationToken.None); + await Promise.all(extensionsToInstall.map(async e => { + const extension = galleryExtensions.find(galleryExtension => areSameExtensions(galleryExtension.identifier, e.identifier)); + if (!extension) { + return; + } + if (await this.extensionManagementService.canInstall(extension)) { + this.logService.trace(`Initializing Profile: Installing extension...`, extension.identifier.id, extension.version); + await this.extensionManagementService.installFromGallery(extension, { + isMachineScoped: false,/* set isMachineScoped value to prevent install and sync dialog in web */ + donotIncludePackAndDependencies: true, + installGivenVersion: !!e.version, + installPreReleaseVersion: e.preRelease, + profileLocation: this.userDataProfileService.currentProfile.extensionsResource, + context: { [EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT]: true } + }); + this.logService.info(`Initializing Profile: Installed extension...`, extension.identifier.id, extension.version); + } else { + this.logService.info(`Initializing Profile: Skipped installing extension because it cannot be installed.`, extension.identifier.id); + } + })); + } + if (extensionsToUninstall.length) { + await Promise.all(extensionsToUninstall.map(e => this.extensionManagementService.uninstall(e))); + } + } +} + +export class ExtensionsResource implements IProfileResource { + + constructor( + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, + @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile, exclude?: string[]): Promise { + const extensions = await this.getLocalExtensions(profile); + return this.toContent(extensions, exclude); + } + + toContent(extensions: IProfileExtension[], exclude?: string[]): string { + return JSON.stringify(exclude?.length ? extensions.filter(e => !exclude.includes(e.identifier.id.toLowerCase())) : extensions); + } + + async apply(content: string, profile: IUserDataProfile): Promise { + return this.withProfileScopedServices(profile, async (extensionEnablementService) => { + const profileExtensions: IProfileExtension[] = await this.getProfileExtensions(content); + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, profile.extensionsResource); + const extensionsToEnableOrDisable: { extension: IExtensionIdentifier; enable: boolean }[] = []; + const extensionsToInstall: IProfileExtension[] = []; + for (const e of profileExtensions) { + const isDisabled = extensionEnablementService.getDisabledExtensions().some(disabledExtension => areSameExtensions(disabledExtension, e.identifier)); + const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier)); + if (!installedExtension || (!installedExtension.isBuiltin && installedExtension.preRelease !== e.preRelease)) { + extensionsToInstall.push(e); + } + if (isDisabled !== !!e.disabled) { + extensionsToEnableOrDisable.push({ extension: e.identifier, enable: !e.disabled }); + } + } + const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => !extension.isBuiltin && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier))); + for (const { extension, enable } of extensionsToEnableOrDisable) { + if (enable) { + this.logService.trace(`Importing Profile (${profile.name}): Enabling extension...`, extension.id); + await extensionEnablementService.enableExtension(extension); + this.logService.info(`Importing Profile (${profile.name}): Enabled extension...`, extension.id); + } else { + this.logService.trace(`Importing Profile (${profile.name}): Disabling extension...`, extension.id); + await extensionEnablementService.disableExtension(extension); + this.logService.info(`Importing Profile (${profile.name}): Disabled extension...`, extension.id); + } + } + if (extensionsToInstall.length) { + const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsToInstall.map(e => ({ ...e.identifier, version: e.version, hasPreRelease: e.version ? undefined : e.preRelease })), CancellationToken.None); + await Promise.all(extensionsToInstall.map(async e => { + const extension = galleryExtensions.find(galleryExtension => areSameExtensions(galleryExtension.identifier, e.identifier)); + if (!extension) { + return; + } + if (await this.extensionManagementService.canInstall(extension)) { + this.logService.trace(`Importing Profile (${profile.name}): Installing extension...`, extension.identifier.id, extension.version); + await this.extensionManagementService.installFromGallery(extension, { + isMachineScoped: false,/* set isMachineScoped value to prevent install and sync dialog in web */ + donotIncludePackAndDependencies: true, + installGivenVersion: !!e.version, + installPreReleaseVersion: e.preRelease, + profileLocation: profile.extensionsResource, + context: { [EXTENSION_INSTALL_SKIP_WALKTHROUGH_CONTEXT]: true } + }); + this.logService.info(`Importing Profile (${profile.name}): Installed extension...`, extension.identifier.id, extension.version); + } else { + this.logService.info(`Importing Profile (${profile.name}): Skipped installing extension because it cannot be installed.`, extension.identifier.id); + } + })); + } + if (extensionsToUninstall.length) { + await Promise.all(extensionsToUninstall.map(e => this.extensionManagementService.uninstall(e))); + } + }); + } + + async getLocalExtensions(profile: IUserDataProfile): Promise { + return this.withProfileScopedServices(profile, async (extensionEnablementService) => { + const result: Array = []; + const installedExtensions = await this.extensionManagementService.getInstalled(undefined, profile.extensionsResource); + const disabledExtensions = extensionEnablementService.getDisabledExtensions(); + for (const extension of installedExtensions) { + const { identifier, preRelease } = extension; + const disabled = disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)); + if (extension.isBuiltin && !disabled) { + // skip enabled builtin extensions + continue; + } + if (!extension.isBuiltin) { + if (!extension.identifier.uuid) { + // skip user extensions without uuid + continue; + } + } + const profileExtension: IProfileExtension = { identifier, displayName: extension.manifest.displayName }; + if (disabled) { + profileExtension.disabled = true; + } + if (!extension.isBuiltin && extension.pinned) { + profileExtension.version = extension.manifest.version; + } + if (!profileExtension.version && preRelease) { + profileExtension.preRelease = true; + } + result.push(profileExtension); + } + return result; + }); + } + + async getProfileExtensions(content: string): Promise { + return JSON.parse(content); + } + + private async withProfileScopedServices(profile: IUserDataProfile, fn: (extensionEnablementService: IGlobalExtensionEnablementService) => Promise): Promise { + return this.userDataProfileStorageService.withProfileScopedStorageService(profile, + async storageService => { + const disposables = new DisposableStore(); + const instantiationService = this.instantiationService.createChild(new ServiceCollection([IStorageService, storageService])); + const extensionEnablementService = disposables.add(instantiationService.createInstance(GlobalExtensionEnablementService)); + try { + return await fn(extensionEnablementService); + } finally { + disposables.dispose(); + } + }); + } +} + +export abstract class ExtensionsResourceTreeItem implements IProfileResourceTreeItem { + + readonly type = ProfileResourceType.Extensions; + readonly handle = ProfileResourceType.Extensions; + readonly label = { label: localize('extensions', "Extensions") }; + readonly collapsibleState = TreeItemCollapsibleState.Expanded; + contextValue = ProfileResourceType.Extensions; + checkbox: ITreeItemCheckboxState | undefined; + + protected readonly excludedExtensions = new Set(); + + async getChildren(): Promise { + const extensions = (await this.getExtensions()).sort((a, b) => (a.displayName ?? a.identifier.id).localeCompare(b.displayName ?? b.identifier.id)); + const that = this; + return extensions.map(e => ({ + handle: e.identifier.id.toLowerCase(), + parent: this, + label: { label: e.displayName || e.identifier.id }, + description: e.disabled ? localize('disabled', "Disabled") : undefined, + collapsibleState: TreeItemCollapsibleState.None, + checkbox: that.checkbox ? { + get isChecked() { return !that.excludedExtensions.has(e.identifier.id.toLowerCase()); }, + set isChecked(value: boolean) { + if (value) { + that.excludedExtensions.delete(e.identifier.id.toLowerCase()); + } else { + that.excludedExtensions.add(e.identifier.id.toLowerCase()); + } + }, + tooltip: localize('exclude', "Select {0} Extension", e.displayName || e.identifier.id) + } : undefined, + command: { + id: 'extension.open', + title: '', + arguments: [e.identifier.id, undefined, true] + } + })); + } + + async hasContent(): Promise { + const extensions = await this.getExtensions(); + return extensions.length > 0; + } + + abstract getContent(): Promise; + protected abstract getExtensions(): Promise; + +} + +export class ExtensionsResourceExportTreeItem extends ExtensionsResourceTreeItem { + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + } + + protected getExtensions(): Promise { + return this.instantiationService.createInstance(ExtensionsResource).getLocalExtensions(this.profile); + } + + async getContent(): Promise { + return this.instantiationService.createInstance(ExtensionsResource).getContent(this.profile, [...this.excludedExtensions.values()]); + } + +} + +export class ExtensionsResourceImportTreeItem extends ExtensionsResourceTreeItem { + + constructor( + private readonly content: string, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + } + + protected getExtensions(): Promise { + return this.instantiationService.createInstance(ExtensionsResource).getProfileExtensions(this.content); + } + + async getContent(): Promise { + const extensionsResource = this.instantiationService.createInstance(ExtensionsResource); + const extensions = await extensionsResource.getProfileExtensions(this.content); + return extensionsResource.toContent(extensions, [...this.excludedExtensions.values()]); + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts b/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts new file mode 100644 index 0000000000..213c592615 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/globalStateResource.ts @@ -0,0 +1,151 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStringDictionary } from 'vs/base/common/collections'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +interface IGlobalState { + storage: IStringDictionary; +} + +export class GlobalStateResourceInitializer implements IProfileResourceInitializer { + + constructor(@IStorageService private readonly storageService: IStorageService) { + } + + async initialize(content: string): Promise { + const globalState: IGlobalState = JSON.parse(content); + const storageKeys = Object.keys(globalState.storage); + if (storageKeys.length) { + for (const key of storageKeys) { + this.storageService.store(key, globalState.storage[key], StorageScope.PROFILE, StorageTarget.USER); + } + } + } +} + +export class GlobalStateResource implements IProfileResource { + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IUserDataProfileStorageService private readonly userDataProfileStorageService: IUserDataProfileStorageService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile): Promise { + const globalState = await this.getGlobalState(profile); + return JSON.stringify(globalState); + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const globalState: IGlobalState = JSON.parse(content); + await this.writeGlobalState(globalState, profile); + } + + async getGlobalState(profile: IUserDataProfile): Promise { + const storage: IStringDictionary = {}; + const storageData = await this.userDataProfileStorageService.readStorageData(profile); + for (const [key, value] of storageData) { + if (value.value !== undefined && value.target === StorageTarget.USER) { + storage[key] = value.value; + } + } + return { storage }; + } + + private async writeGlobalState(globalState: IGlobalState, profile: IUserDataProfile): Promise { + const storageKeys = Object.keys(globalState.storage); + if (storageKeys.length) { + const updatedStorage = new Map(); + const nonProfileKeys = [ + // Do not include application scope user target keys because they also include default profile user target keys + ...this.storageService.keys(StorageScope.APPLICATION, StorageTarget.MACHINE), + ...this.storageService.keys(StorageScope.WORKSPACE, StorageTarget.USER), + ...this.storageService.keys(StorageScope.WORKSPACE, StorageTarget.MACHINE), + ]; + for (const key of storageKeys) { + if (nonProfileKeys.includes(key)) { + this.logService.info(`Importing Profile (${profile.name}): Ignoring global state key '${key}' because it is not a profile key.`); + } else { + updatedStorage.set(key, globalState.storage[key]); + } + } + await this.userDataProfileStorageService.updateStorageData(profile, updatedStorage, StorageTarget.USER); + } + } +} + +export abstract class GlobalStateResourceTreeItem implements IProfileResourceTreeItem { + + readonly type = ProfileResourceType.GlobalState; + readonly handle = ProfileResourceType.GlobalState; + readonly label = { label: localize('globalState', "UI State") }; + readonly collapsibleState = TreeItemCollapsibleState.Collapsed; + checkbox: ITreeItemCheckboxState | undefined; + + constructor(private readonly resource: URI) { } + + async getChildren(): Promise { + return [{ + handle: this.resource.toString(), + resourceUri: this.resource, + collapsibleState: TreeItemCollapsibleState.None, + parent: this, + command: { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [this.resource, undefined, undefined] + } + }]; + } + + abstract getContent(): Promise; +} + +export class GlobalStateResourceExportTreeItem extends GlobalStateResourceTreeItem { + + constructor( + private readonly profile: IUserDataProfile, + resource: URI, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(resource); + } + + async hasContent(): Promise { + const globalState = await this.instantiationService.createInstance(GlobalStateResource).getGlobalState(this.profile); + return Object.keys(globalState.storage).length > 0; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(GlobalStateResource).getContent(this.profile); + } + +} + +export class GlobalStateResourceImportTreeItem extends GlobalStateResourceTreeItem { + + constructor( + private readonly content: string, + resource: URI, + ) { + super(resource); + } + + async getContent(): Promise { + return this.content; + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts b/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts new file mode 100644 index 0000000000..6739acb65f --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/keybindingsResource.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { platform, Platform } from 'vs/base/common/platform'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { localize } from 'vs/nls'; + +interface IKeybindingsResourceContent { + platform: Platform; + keybindings: string | null; +} + +export class KeybindingsResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async initialize(content: string): Promise { + const keybindingsContent: IKeybindingsResourceContent = JSON.parse(content); + if (keybindingsContent.keybindings === null) { + this.logService.info(`Initializing Profile: No keybindings to apply...`); + return; + } + await this.fileService.writeFile(this.userDataProfileService.currentProfile.keybindingsResource, VSBuffer.fromString(keybindingsContent.keybindings)); + } +} + +export class KeybindingsResource implements IProfileResource { + + constructor( + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile): Promise { + const keybindingsContent = await this.getKeybindingsResourceContent(profile); + return JSON.stringify(keybindingsContent); + } + + async getKeybindingsResourceContent(profile: IUserDataProfile): Promise { + const keybindings = await this.getKeybindingsContent(profile); + return { keybindings, platform }; + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const keybindingsContent: IKeybindingsResourceContent = JSON.parse(content); + if (keybindingsContent.keybindings === null) { + this.logService.info(`Importing Profile (${profile.name}): No keybindings to apply...`); + return; + } + await this.fileService.writeFile(profile.keybindingsResource, VSBuffer.fromString(keybindingsContent.keybindings)); + } + + private async getKeybindingsContent(profile: IUserDataProfile): Promise { + try { + const content = await this.fileService.readFile(profile.keybindingsResource); + return content.value.toString(); + } catch (error) { + // File not found + if (error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return null; + } else { + throw error; + } + } + } + +} + +export class KeybindingsResourceTreeItem implements IProfileResourceTreeItem { + + readonly type = ProfileResourceType.Keybindings; + readonly handle = ProfileResourceType.Keybindings; + readonly label = { label: localize('keybindings', "Keyboard Shortcuts") }; + readonly collapsibleState = TreeItemCollapsibleState.Expanded; + checkbox: ITreeItemCheckboxState | undefined; + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + async getChildren(): Promise { + return [{ + handle: this.profile.keybindingsResource.toString(), + resourceUri: this.profile.keybindingsResource, + collapsibleState: TreeItemCollapsibleState.None, + parent: this, + command: { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [this.profile.keybindingsResource, undefined, undefined] + } + }]; + } + + async hasContent(): Promise { + const keybindingsContent = await this.instantiationService.createInstance(KeybindingsResource).getKeybindingsResourceContent(this.profile); + return keybindingsContent.keybindings !== null; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(KeybindingsResource).getContent(this.profile); + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css b/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css new file mode 100644 index 0000000000..76c49f7f5f --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.profile-view-tree-container .customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .actions { + display: inherit; +} + +.monaco-workbench .pane > .pane-body > .profile-view-message-container { + display: flex; + padding: 13px 20px 0px 20px; + box-sizing: border-box; +} + +.monaco-workbench .pane > .pane-body > .profile-view-message-container p { + margin-block-start: 0em; + margin-block-end: 0em; +} + +.monaco-workbench .pane > .pane-body > .profile-view-message-container a { + color: var(--vscode-textLink-foreground) +} + +.monaco-workbench .pane > .pane-body > .profile-view-message-container a:hover { + text-decoration: underline; + color: var(--vscode-textLink-activeForeground) +} + +.monaco-workbench .pane > .pane-body > .profile-view-message-container a:active { + color: var(--vscode-textLink-activeForeground) +} + +.monaco-workbench .pane > .pane-body > .profile-view-message-container.hide { + display: none; +} + +.monaco-workbench .pane > .pane-body > .profile-view-buttons-container { + display: flex; + flex-direction: column; + padding: 13px 20px; + box-sizing: border-box; +} + +.monaco-workbench .pane > .pane-body > .profile-view-buttons-container > .monaco-button, +.monaco-workbench .pane > .pane-body > .profile-view-buttons-container > .monaco-button-dropdown { + margin-block-start: 13px; + margin-inline-start: 0px; + margin-inline-end: 0px; + max-width: 260px; + margin-left: auto; + margin-right: auto; +} + +.monaco-workbench .pane > .pane-body > .profile-view-buttons-container > .monaco-button-dropdown { + width: 100%; +} + +.monaco-workbench .pane > .pane-body > .profile-view-buttons-container > .monaco-button-dropdown > .monaco-dropdown-button { + display: flex; + align-items: center; + padding: 0 4px; +} diff --git a/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts b/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts new file mode 100644 index 0000000000..18fa473672 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/settingsResource.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; +import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { localize } from 'vs/nls'; + +interface ISettingsContent { + settings: string | null; +} + +export class SettingsResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async initialize(content: string): Promise { + const settingsContent: ISettingsContent = JSON.parse(content); + if (settingsContent.settings === null) { + this.logService.info(`Initializing Profile: No settings to apply...`); + return; + } + await this.fileService.writeFile(this.userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString(settingsContent.settings)); + } +} + +export class SettingsResource implements IProfileResource { + + constructor( + @IFileService private readonly fileService: IFileService, + @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile): Promise { + const settingsContent = await this.getSettingsContent(profile); + return JSON.stringify(settingsContent); + } + + async getSettingsContent(profile: IUserDataProfile): Promise { + const localContent = await this.getLocalFileContent(profile); + if (localContent === null) { + return { settings: null }; + } else { + const ignoredSettings = this.getIgnoredSettings(); + const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(profile.settingsResource); + const settings = updateIgnoredSettings(localContent || '{}', '{}', ignoredSettings, formattingOptions); + return { settings }; + } + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const settingsContent: ISettingsContent = JSON.parse(content); + if (settingsContent.settings === null) { + this.logService.info(`Importing Profile (${profile.name}): No settings to apply...`); + return; + } + const localSettingsContent = await this.getLocalFileContent(profile); + const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(profile.settingsResource); + const contentToUpdate = updateIgnoredSettings(settingsContent.settings, localSettingsContent || '{}', this.getIgnoredSettings(), formattingOptions); + await this.fileService.writeFile(profile.settingsResource, VSBuffer.fromString(contentToUpdate)); + } + + private getIgnoredSettings(): string[] { + const allSettings = Registry.as(Extensions.Configuration).getConfigurationProperties(); + const ignoredSettings = Object.keys(allSettings).filter(key => allSettings[key]?.scope === ConfigurationScope.MACHINE || allSettings[key]?.scope === ConfigurationScope.MACHINE_OVERRIDABLE); + return ignoredSettings; + } + + private async getLocalFileContent(profile: IUserDataProfile): Promise { + try { + const content = await this.fileService.readFile(profile.settingsResource); + return content.value.toString(); + } catch (error) { + // File not found + if (error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return null; + } else { + throw error; + } + } + } + +} + +export class SettingsResourceTreeItem implements IProfileResourceTreeItem { + + readonly type = ProfileResourceType.Settings; + readonly handle = ProfileResourceType.Settings; + readonly label = { label: localize('settings', "Settings") }; + readonly collapsibleState = TreeItemCollapsibleState.Expanded; + checkbox: ITreeItemCheckboxState | undefined; + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + async getChildren(): Promise { + return [{ + handle: this.profile.settingsResource.toString(), + resourceUri: this.profile.settingsResource, + collapsibleState: TreeItemCollapsibleState.None, + parent: this, + command: { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [this.profile.settingsResource, undefined, undefined] + } + }]; + } + + async hasContent(): Promise { + const settingsContent = await this.instantiationService.createInstance(SettingsResource).getSettingsContent(this.profile); + return settingsContent.settings !== null; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(SettingsResource).getContent(this.profile); + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts b/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts new file mode 100644 index 0000000000..438fb1e260 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/snippetsResource.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { ResourceSet } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { localize } from 'vs/nls'; +import { FileOperationError, FileOperationResult, IFileService, IFileStat } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +interface ISnippetsContent { + snippets: IStringDictionary; +} + +export class SnippetsResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IFileService private readonly fileService: IFileService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + ) { + } + + async initialize(content: string): Promise { + const snippetsContent: ISnippetsContent = JSON.parse(content); + for (const key in snippetsContent.snippets) { + const resource = this.uriIdentityService.extUri.joinPath(this.userDataProfileService.currentProfile.snippetsHome, key); + await this.fileService.writeFile(resource, VSBuffer.fromString(snippetsContent.snippets[key])); + } + } +} + +export class SnippetsResource implements IProfileResource { + + constructor( + @IFileService private readonly fileService: IFileService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + ) { + } + + async getContent(profile: IUserDataProfile, excluded?: ResourceSet): Promise { + const snippets = await this.getSnippets(profile, excluded); + return JSON.stringify({ snippets }); + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const snippetsContent: ISnippetsContent = JSON.parse(content); + for (const key in snippetsContent.snippets) { + const resource = this.uriIdentityService.extUri.joinPath(profile.snippetsHome, key); + await this.fileService.writeFile(resource, VSBuffer.fromString(snippetsContent.snippets[key])); + } + } + + private async getSnippets(profile: IUserDataProfile, excluded?: ResourceSet): Promise> { + const snippets: IStringDictionary = {}; + const snippetsResources = await this.getSnippetsResources(profile, excluded); + for (const resource of snippetsResources) { + const key = this.uriIdentityService.extUri.relativePath(profile.snippetsHome, resource)!; + const content = await this.fileService.readFile(resource); + snippets[key] = content.value.toString(); + } + return snippets; + } + + async getSnippetsResources(profile: IUserDataProfile, excluded?: ResourceSet): Promise { + const snippets: URI[] = []; + let stat: IFileStat; + try { + stat = await this.fileService.resolve(profile.snippetsHome); + } catch (e) { + // No snippets + if (e instanceof FileOperationError && e.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return snippets; + } else { + throw e; + } + } + for (const { resource } of stat.children || []) { + if (excluded?.has(resource)) { + continue; + } + const extension = this.uriIdentityService.extUri.extname(resource); + if (extension === '.json' || extension === '.code-snippets') { + snippets.push(resource); + } + } + return snippets; + } +} + +export class SnippetsResourceTreeItem implements IProfileResourceTreeItem { + + readonly type = ProfileResourceType.Snippets; + readonly handle = this.profile.snippetsHome.toString(); + readonly label = { label: localize('snippets', "Snippets") }; + readonly collapsibleState = TreeItemCollapsibleState.Collapsed; + checkbox: ITreeItemCheckboxState | undefined; + + private readonly excludedSnippets = new ResourceSet(); + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { } + + async getChildren(): Promise { + const snippetsResources = await this.instantiationService.createInstance(SnippetsResource).getSnippetsResources(this.profile); + const that = this; + return snippetsResources.map(resource => ({ + handle: resource.toString(), + parent: that, + resourceUri: resource, + collapsibleState: TreeItemCollapsibleState.None, + checkbox: that.checkbox ? { + get isChecked() { return !that.excludedSnippets.has(resource); }, + set isChecked(value: boolean) { + if (value) { + that.excludedSnippets.delete(resource); + } else { + that.excludedSnippets.add(resource); + } + } + } : undefined, + command: { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [resource, undefined, undefined] + } + })); + } + + async hasContent(): Promise { + const snippetsResources = await this.instantiationService.createInstance(SnippetsResource).getSnippetsResources(this.profile); + return snippetsResources.length > 0; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(SnippetsResource).getContent(this.profile, this.excludedSnippets); + } + +} + diff --git a/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts b/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts new file mode 100644 index 0000000000..d21a7bd560 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/tasksResource.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { localize } from 'vs/nls'; +import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { ITreeItemCheckboxState, TreeItemCollapsibleState } from 'vs/workbench/common/views'; +import { IProfileResource, IProfileResourceChildTreeItem, IProfileResourceInitializer, IProfileResourceTreeItem, IUserDataProfileService, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +interface ITasksResourceContent { + tasks: string | null; +} + +export class TasksResourceInitializer implements IProfileResourceInitializer { + + constructor( + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async initialize(content: string): Promise { + const tasksContent: ITasksResourceContent = JSON.parse(content); + if (!tasksContent.tasks) { + this.logService.info(`Initializing Profile: No tasks to apply...`); + return; + } + await this.fileService.writeFile(this.userDataProfileService.currentProfile.tasksResource, VSBuffer.fromString(tasksContent.tasks)); + } +} + +export class TasksResource implements IProfileResource { + + constructor( + @IFileService private readonly fileService: IFileService, + @ILogService private readonly logService: ILogService, + ) { + } + + async getContent(profile: IUserDataProfile): Promise { + const tasksContent = await this.getTasksResourceContent(profile); + return JSON.stringify(tasksContent); + } + + async getTasksResourceContent(profile: IUserDataProfile): Promise { + const tasksContent = await this.getTasksContent(profile); + return { tasks: tasksContent }; + } + + async apply(content: string, profile: IUserDataProfile): Promise { + const tasksContent: ITasksResourceContent = JSON.parse(content); + if (!tasksContent.tasks) { + this.logService.info(`Importing Profile (${profile.name}): No tasks to apply...`); + return; + } + await this.fileService.writeFile(profile.tasksResource, VSBuffer.fromString(tasksContent.tasks)); + } + + private async getTasksContent(profile: IUserDataProfile): Promise { + try { + const content = await this.fileService.readFile(profile.tasksResource); + return content.value.toString(); + } catch (error) { + // File not found + if (error instanceof FileOperationError && error.fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + return null; + } else { + throw error; + } + } + } + +} + +export class TasksResourceTreeItem implements IProfileResourceTreeItem { + + readonly type = ProfileResourceType.Tasks; + readonly handle = ProfileResourceType.Tasks; + readonly label = { label: localize('tasks', "User Tasks") }; + readonly collapsibleState = TreeItemCollapsibleState.Expanded; + checkbox: ITreeItemCheckboxState | undefined; + + constructor( + private readonly profile: IUserDataProfile, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { } + + async getChildren(): Promise { + return [{ + handle: this.profile.tasksResource.toString(), + resourceUri: this.profile.tasksResource, + collapsibleState: TreeItemCollapsibleState.None, + parent: this, + command: { + id: API_OPEN_EDITOR_COMMAND_ID, + title: '', + arguments: [this.profile.tasksResource, undefined, undefined] + } + }]; + } + + async hasContent(): Promise { + const tasksContent = await this.instantiationService.createInstance(TasksResource).getTasksResourceContent(this.profile); + return tasksContent.tasks !== null; + } + + async getContent(): Promise { + return this.instantiationService.createInstance(TasksResource).getContent(this.profile); + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts new file mode 100644 index 0000000000..c16599d875 --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -0,0 +1,1210 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/userDataProfileView'; +import { localize } from 'vs/nls'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { Emitter, Event } from 'vs/base/common/event'; +import * as DOM from 'vs/base/browser/dom'; +import { IUserDataProfileImportExportService, PROFILE_FILTER, PROFILE_EXTENSION, IUserDataProfileContentHandler, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, PROFILES_TITLE, defaultUserDataProfileIcon, IUserDataProfileService, IProfileResourceTreeItem, IProfileResourceChildTreeItem, PROFILES_CATEGORY, IUserDataProfileManagementService, ProfileResourceType, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ISaveProfileResult, IProfileImportOptions, PROFILE_URL_AUTHORITY, toUserDataProfileUri } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IDialogService, IFileDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer'; +import { ILogService } from 'vs/platform/log/common/log'; +import { TreeView, TreeViewPane } from 'vs/workbench/browser/parts/views/treeView'; +import { SettingsResource, SettingsResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/settingsResource'; +import { KeybindingsResource, KeybindingsResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/keybindingsResource'; +import { SnippetsResource, SnippetsResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/snippetsResource'; +import { TasksResource, TasksResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/tasksResource'; +import { ExtensionsResource, ExtensionsResourceExportTreeItem, ExtensionsResourceImportTreeItem, ExtensionsResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/extensionsResource'; +import { GlobalStateResource, GlobalStateResourceExportTreeItem, GlobalStateResourceImportTreeItem, GlobalStateResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/globalStateResource'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorsOrder } from 'vs/workbench/common/editor'; +import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors'; +import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IQuickInputService, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { joinPath } from 'vs/base/common/resources'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; +import { Schemas } from 'vs/base/common/network'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import Severity from 'vs/base/common/severity'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; +import { asText, IRequestService } from 'vs/platform/request/common/request'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { isUndefined } from 'vs/base/common/types'; +import { Action, ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; +import { isWeb } from 'vs/base/common/platform'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Codicon } from 'vs/base/common/codicons'; +import { Barrier } from 'vs/base/common/async'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; +import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConstants'; + +interface IUserDataProfileTemplate { + readonly name: string; + readonly shortName?: string; + readonly settings?: string; + readonly keybindings?: string; + readonly tasks?: string; + readonly snippets?: string; + readonly globalState?: string; + readonly extensions?: string; +} + +function isUserDataProfileTemplate(thing: unknown): thing is IUserDataProfileTemplate { + const candidate = thing as IUserDataProfileTemplate | undefined; + + return !!(candidate && typeof candidate === 'object' + && (candidate.name && typeof candidate.name === 'string') + && (isUndefined(candidate.shortName) || typeof candidate.shortName === 'string') + && (isUndefined(candidate.settings) || typeof candidate.settings === 'string') + && (isUndefined(candidate.globalState) || typeof candidate.globalState === 'string') + && (isUndefined(candidate.extensions) || typeof candidate.extensions === 'string')); +} + +const EXPORT_PROFILE_PREVIEW_VIEW = 'workbench.views.profiles.export.preview'; +const IMPORT_PROFILE_PREVIEW_VIEW = 'workbench.views.profiles.import.preview'; + +export class UserDataProfileImportExportService extends Disposable implements IUserDataProfileImportExportService, IURLHandler { + + private static readonly PROFILE_URL_AUTHORITY_PREFIX = 'profile-'; + + readonly _serviceBrand: undefined; + + private profileContentHandlers = new Map(); + private readonly isProfileExportInProgressContextKey: IContextKey; + private readonly isProfileImportInProgressContextKey: IContextKey; + + private readonly viewContainer: ViewContainer; + private readonly fileUserDataProfileContentHandler: FileUserDataProfileContentHandler; + + constructor( + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IViewsService private readonly viewsService: IViewsService, + @IEditorService private readonly editorService: IEditorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @INotificationService private readonly notificationService: INotificationService, + @IProgressService private readonly progressService: IProgressService, + @IDialogService private readonly dialogService: IDialogService, + @IClipboardService private readonly clipboardService: IClipboardService, + @IOpenerService private readonly openerService: IOpenerService, + @IRequestService private readonly requestService: IRequestService, + @IURLService urlService: IURLService, + @IProductService private readonly productService: IProductService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.registerProfileContentHandler(Schemas.file, this.fileUserDataProfileContentHandler = instantiationService.createInstance(FileUserDataProfileContentHandler)); + this.isProfileExportInProgressContextKey = IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT.bindTo(contextKeyService); + this.isProfileImportInProgressContextKey = IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT.bindTo(contextKeyService); + + this.viewContainer = Registry.as(Extensions.ViewContainersRegistry).registerViewContainer( + { + id: 'userDataProfiles', + title: PROFILES_TITLE, + ctorDescriptor: new SyncDescriptor( + ViewPaneContainer, + ['userDataProfiles', { mergeViewWithContainerWhenSingleView: true }] + ), + icon: defaultUserDataProfileIcon, + hideIfEmpty: true, + }, ViewContainerLocation.Sidebar); + + urlService.registerHandler(this); + } + + private isProfileURL(uri: URI): boolean { + return uri.authority === PROFILE_URL_AUTHORITY || new RegExp(`^${UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX}`).test(uri.authority); + } + + async handleURL(uri: URI): Promise { + if (this.isProfileURL(uri)) { + try { + await this.importProfile(uri); + } catch (error) { + this.notificationService.error(localize('profile import error', "Error while importing profile: {0}", getErrorMessage(error))); + } + return true; + } + return false; + } + + registerProfileContentHandler(id: string, profileContentHandler: IUserDataProfileContentHandler): IDisposable { + if (this.profileContentHandlers.has(id)) { + throw new Error(`Profile content handler with id '${id}' already registered.`); + } + this.profileContentHandlers.set(id, profileContentHandler); + return toDisposable(() => this.unregisterProfileContentHandler(id)); + } + + unregisterProfileContentHandler(id: string): void { + this.profileContentHandlers.delete(id); + } + + async exportProfile(): Promise { + if (this.isProfileExportInProgressContextKey.get()) { + this.logService.warn('Profile export already in progress.'); + return; + } + + return this.showProfileContents(); + } + + async importProfile(uri: URI, options?: IProfileImportOptions): Promise { + if (this.isProfileImportInProgressContextKey.get()) { + this.notificationService.warn('Profile import already in progress.'); + return; + } + + this.isProfileImportInProgressContextKey.set(true); + const disposables = new DisposableStore(); + disposables.add(toDisposable(() => this.isProfileImportInProgressContextKey.set(false))); + + try { + const profileTemplate = await this.progressService.withProgress({ + location: ProgressLocation.Window, + command: showWindowLogActionId, + title: localize('resolving uri', "{0}: Resolving profile content...", options?.preview ? localize('preview profile', "Preview Profile") : localize('import profile', "Create Profile")), + }, () => this.resolveProfileTemplate(uri)); + if (!profileTemplate) { + return; + } + if (options?.preview) { + await this.previewProfile(uri, profileTemplate); + } else { + await this.doImportProfile(profileTemplate); + } + } finally { + disposables.dispose(); + } + } + + async showProfileContents(): Promise { + const view = this.viewsService.getViewWithId(EXPORT_PROFILE_PREVIEW_VIEW); + if (view) { + this.viewsService.openView(view.id, true); + return; + } + const disposables = new DisposableStore(); + try { + const userDataProfilesExportState = disposables.add(this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile)); + const barrier = new Barrier(); + const exportAction = new BarrierAction(barrier, new Action('export', localize('export', "Export"), undefined, true, async () => { + exportAction.enabled = false; + try { + await this.doExportProfile(userDataProfilesExportState); + } catch (error) { + this.notificationService.error(error); + throw error; + } + }), this.notificationService); + const closeAction = new BarrierAction(barrier, new Action('close', localize('close', "Close")), this.notificationService); + await this.showProfilePreviewView(EXPORT_PROFILE_PREVIEW_VIEW, userDataProfilesExportState.profile.name, exportAction, closeAction, true, userDataProfilesExportState); + disposables.add(this.userDataProfileService.onDidChangeCurrentProfile(e => barrier.open())); + await barrier.wait(); + await this.hideProfilePreviewView(EXPORT_PROFILE_PREVIEW_VIEW); + } finally { + disposables.dispose(); + } + } + + async createFromCurrentProfile(name: string): Promise { + const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, this.userDataProfileService.currentProfile); + try { + const profileTemplate = await userDataProfilesExportState.getProfileTemplate(name, undefined); + await this.doImportProfile(profileTemplate); + } finally { + userDataProfilesExportState.dispose(); + } + } + + private async doExportProfile(userDataProfilesExportState: UserDataProfileExportState): Promise { + const profile = await userDataProfilesExportState.getProfileToExport(); + if (!profile) { + return; + } + + this.isProfileExportInProgressContextKey.set(true); + const disposables = new DisposableStore(); + disposables.add(toDisposable(() => this.isProfileExportInProgressContextKey.set(false))); + + try { + await this.progressService.withProgress({ + location: EXPORT_PROFILE_PREVIEW_VIEW, + title: localize('profiles.exporting', "{0}: Exporting...", PROFILES_CATEGORY.value), + }, async progress => { + const id = await this.pickProfileContentHandler(profile.name); + if (!id) { + return; + } + const profileContentHandler = this.profileContentHandlers.get(id); + if (!profileContentHandler) { + return; + } + const saveResult = await profileContentHandler.saveProfile(profile.name.replace('/', '-'), JSON.stringify(profile), CancellationToken.None); + if (!saveResult) { + return; + } + const message = localize('export success', "Profile '{0}' was exported successfully.", profile.name); + if (profileContentHandler.extensionId) { + const buttons: IPromptButton[] = []; + const link = this.productService.webUrl ? `${this.productService.webUrl}/${PROFILE_URL_AUTHORITY}/${id}/${saveResult.id}` : toUserDataProfileUri(`/${id}/${saveResult.id}`, this.productService).toString(); + buttons.push({ + label: localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy Link"), + run: () => this.clipboardService.writeText(link) + }); + if (this.productService.webUrl) { + buttons.push({ + label: localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Open Link"), + run: async () => { + await this.openerService.open(link); + } + }); + } else { + buttons.push({ + label: localize({ key: 'open in', comment: ['&& denotes a mnemonic'] }, "&&Open in {0}", profileContentHandler.name), + run: async () => { + await this.openerService.open(saveResult.link.toString()); + } + }); + } + await this.dialogService.prompt({ + type: Severity.Info, + message, + buttons, + cancelButton: localize('close', "Close") + }); + } else { + await this.dialogService.info(message); + } + }); + } finally { + disposables.dispose(); + } + } + + private async resolveProfileTemplate(uri: URI): Promise { + const profileContent = await this.resolveProfileContent(uri); + if (profileContent === null) { + return null; + } + + const profileTemplate: IUserDataProfileTemplate = JSON.parse(profileContent); + if (!isUserDataProfileTemplate(profileTemplate)) { + throw new Error('Invalid profile content.'); + } + + return profileTemplate; + } + + private async previewProfile(uri: URI, profileTemplate: IUserDataProfileTemplate): Promise { + const disposables = new DisposableStore(); + + try { + const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); + profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); + + const importedProfile = await this.importAndSwitch(profileTemplate, true, false, localize('preview profile', "Preview Profile")); + + if (!importedProfile) { + return; + } + + const barrier = new Barrier(); + const importAction = this.getImportAction(barrier, userDataProfileImportState); + const primaryAction = isWeb + ? new Action('importInDesktop', localize('import in desktop', "Create Profile in {0}", this.productService.nameLong), undefined, true, async () => this.openerService.open(uri, { openExternal: true })) + : importAction; + const secondaryAction = isWeb + ? importAction + : new BarrierAction(barrier, new Action('close', localize('close', "Close")), this.notificationService); + + const view = await this.showProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW, importedProfile.name, primaryAction, secondaryAction, false, userDataProfileImportState); + const message = new MarkdownString(); + message.appendMarkdown(localize('preview profile message', "By default, extensions aren't installed when previewing a profile on the web. You can still install them manually before importing the profile. ")); + message.appendMarkdown(`[${localize('learn more', "Learn more")}](https://aka.ms/vscode-extension-marketplace#_can-i-trust-extensions-from-the-marketplace).`); + view.setMessage(message); + + const that = this; + const disposable = disposables.add(registerAction2(class extends Action2 { + constructor() { + super({ + id: 'previewProfile.installExtensions', + title: localize('install extensions title', "Install Extensions"), + icon: Codicon.cloudDownload, + menu: { + id: MenuId.ViewItemContext, + group: 'inline', + when: ContextKeyExpr.and(ContextKeyExpr.equals('view', IMPORT_PROFILE_PREVIEW_VIEW), ContextKeyExpr.equals('viewItem', ProfileResourceType.Extensions)), + } + }); + } + override async run(): Promise { + return that.progressService.withProgress({ + location: IMPORT_PROFILE_PREVIEW_VIEW, + }, async progress => { + disposable.dispose(); + view.setMessage(undefined); + const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); + if (profileTemplate.extensions) { + await that.instantiationService.createInstance(ExtensionsResource).apply(profileTemplate.extensions, importedProfile); + } + }); + } + })); + disposables.add(Event.debounce(this.extensionManagementService.onDidInstallExtensions, () => undefined, 100)(async () => { + const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); + if (profileTemplate.extensions) { + const profileExtensions = await that.instantiationService.createInstance(ExtensionsResource).getProfileExtensions(profileTemplate.extensions!); + const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + if (profileExtensions.every(e => installed.some(i => areSameExtensions(e.identifier, i.identifier)))) { + disposable.dispose(); + } + } + })); + + await barrier.wait(); + await this.hideProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW); + } finally { + disposables.dispose(); + } + } + + private async doImportProfile(profileTemplate: IUserDataProfileTemplate): Promise { + const disposables = new DisposableStore(); + try { + const userDataProfileImportState = disposables.add(this.instantiationService.createInstance(UserDataProfileImportState, profileTemplate)); + const barrier = new Barrier(); + const importAction = this.getImportAction(barrier, userDataProfileImportState); + if (userDataProfileImportState.isEmpty()) { + await importAction.run(); + } else { + await this.showProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW, profileTemplate.name, importAction, new BarrierAction(barrier, new Action('cancel', localize('cancel', "Cancel")), this.notificationService), false, userDataProfileImportState); + } + await barrier.wait(); + await this.hideProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW); + } finally { + disposables.dispose(); + } + } + + private getImportAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState): IAction { + const title = localize('import', "Create Profile", userDataProfileImportState.profile.name); + const importAction = new BarrierAction(barrier, new Action('import', title, undefined, true, () => { + const importProfileFn = async () => { + importAction.enabled = false; + const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); + const importedProfile = await this.importAndSwitch(profileTemplate, false, true, title); + if (!importedProfile) { + return; + } + }; + if (userDataProfileImportState.isEmpty()) { + return importProfileFn(); + } else { + return this.progressService.withProgress({ + location: IMPORT_PROFILE_PREVIEW_VIEW, + }, () => importProfileFn()); + } + }), this.notificationService); + return importAction; + } + + private async importAndSwitch(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, title: string): Promise { + return this.progressService.withProgress({ + location: ProgressLocation.Window, + command: showWindowLogActionId, + }, async (progress) => { + progress.report({ message: localize('Importing profile', "{0} ({1})...", title, profileTemplate.name) }); + const profile = await this.getProfileToImport(profileTemplate, temporaryProfile); + if (!profile) { + return undefined; + } + + if (profileTemplate.settings) { + progress.report({ message: localize('progress settings', "{0} ({1}): Applying Settings...", title, profileTemplate.name) }); + await this.instantiationService.createInstance(SettingsResource).apply(profileTemplate.settings, profile); + } + if (profileTemplate.keybindings) { + progress.report({ message: localize('progress keybindings', "{0} ({1}): Applying Keyboard Shortcuts...", title, profileTemplate.name) }); + await this.instantiationService.createInstance(KeybindingsResource).apply(profileTemplate.keybindings, profile); + } + if (profileTemplate.tasks) { + progress.report({ message: localize('progress tasks', "{0} ({1}): Applying Tasks...", title, profileTemplate.name) }); + await this.instantiationService.createInstance(TasksResource).apply(profileTemplate.tasks, profile); + } + if (profileTemplate.snippets) { + progress.report({ message: localize('progress snippets', "{0} ({1}): Applying Snippets...", title, profileTemplate.name) }); + await this.instantiationService.createInstance(SnippetsResource).apply(profileTemplate.snippets, profile); + } + if (profileTemplate.globalState) { + progress.report({ message: localize('progress global state', "{0} ({1}): Applying State...", title, profileTemplate.name) }); + await this.instantiationService.createInstance(GlobalStateResource).apply(profileTemplate.globalState, profile); + } + if (profileTemplate.extensions && extensions) { + progress.report({ message: localize('progress extensions', "{0} ({1}): Applying Extensions...", title, profileTemplate.name) }); + await this.instantiationService.createInstance(ExtensionsResource).apply(profileTemplate.extensions, profile); + } + + progress.report({ message: localize('switching profile', "{0} ({1}): Applying...", title, profileTemplate.name) }); + await this.userDataProfileManagementService.switchProfile(profile); + return profile; + }); + } + + private async resolveProfileContent(resource: URI): Promise { + if (await this.fileUserDataProfileContentHandler.canHandle(resource)) { + return this.fileUserDataProfileContentHandler.readProfile(resource, CancellationToken.None); + } + + if (this.isProfileURL(resource)) { + let handlerId: string, idOrUri: string | URI; + if (resource.authority === PROFILE_URL_AUTHORITY) { + idOrUri = this.uriIdentityService.extUri.basename(resource); + handlerId = this.uriIdentityService.extUri.basename(this.uriIdentityService.extUri.dirname(resource)); + } else { + handlerId = resource.authority.substring(UserDataProfileImportExportService.PROFILE_URL_AUTHORITY_PREFIX.length); + idOrUri = URI.parse(resource.path.substring(1)); + } + await this.extensionService.activateByEvent(`onProfile:${handlerId}`); + const profileContentHandler = this.profileContentHandlers.get(handlerId); + if (profileContentHandler) { + return profileContentHandler.readProfile(idOrUri, CancellationToken.None); + } + } + + await this.extensionService.activateByEvent('onProfile'); + for (const profileContentHandler of this.profileContentHandlers.values()) { + const content = await profileContentHandler.readProfile(resource, CancellationToken.None); + if (content !== null) { + return content; + } + } + + const context = await this.requestService.request({ type: 'GET', url: resource.toString(true) }, CancellationToken.None); + if (context.res.statusCode === 200) { + return await asText(context); + } else { + const message = await asText(context); + throw new Error(`Failed to get profile from URL: ${resource.toString()}. Status code: ${context.res.statusCode}. Message: ${message}`); + } + } + + private async pickProfileContentHandler(name: string): Promise { + await this.extensionService.activateByEvent('onProfile'); + if (this.profileContentHandlers.size === 1) { + return this.profileContentHandlers.keys().next().value; + } + const options: QuickPickItem[] = []; + for (const [id, profileContentHandler] of this.profileContentHandlers) { + options.push({ id, label: profileContentHandler.name, description: profileContentHandler.description }); + } + const result = await this.quickInputService.pick(options.reverse(), + { + title: localize('select profile content handler', "Export '{0}' profile as...", name), + hideInput: true + }); + return result?.id; + } + + private async getProfileToImport(profileTemplate: IUserDataProfileTemplate, temp?: boolean): Promise { + const profileName = temp ? `${profileTemplate.name} (${localize('preview', "Preview")})` : profileTemplate.name; + const profile = this.userDataProfilesService.profiles.find(p => p.name === profileName); + if (profile) { + if (temp) { + return this.userDataProfilesService.createNamedProfile(`${profileName} ${this.getProfileNameIndex(profileName)}`, { shortName: profileTemplate.shortName, transient: temp }); + } + + enum ImportProfileChoice { + Overwrite = 0, + CreateNew = 1, + Cancel = 2 + } + const { result } = await this.dialogService.prompt({ + type: Severity.Info, + message: localize('profile already exists', "Profile with name '{0}' already exists. Do you want to overwrite it?", profileName), + buttons: [ + { + label: localize({ key: 'overwrite', comment: ['&& denotes a mnemonic'] }, "&&Overwrite"), + run: () => ImportProfileChoice.Overwrite + }, + { + label: localize({ key: 'create new', comment: ['&& denotes a mnemonic'] }, "&&Create New Profile"), + run: () => ImportProfileChoice.CreateNew + }, + ], + cancelButton: { + run: () => ImportProfileChoice.Cancel + } + }); + + if (result === ImportProfileChoice.Overwrite) { + return profile; + } + + if (result === ImportProfileChoice.Cancel) { + return undefined; + } + + // Create new profile + const name = await this.quickInputService.input({ + placeHolder: localize('name', "Profile name"), + title: localize('create new title', "Create New Profile"), + value: `${profileName} ${this.getProfileNameIndex(profileName)}`, + validateInput: async (value: string) => { + if (this.userDataProfilesService.profiles.some(p => p.name === value)) { + return localize('profileExists', "Profile with name {0} already exists.", value); + } + return undefined; + } + }); + if (!name) { + return undefined; + } + return this.userDataProfilesService.createNamedProfile(name); + } else { + return this.userDataProfilesService.createNamedProfile(profileName, { shortName: profileTemplate.shortName, transient: temp }); + } + } + + private getProfileNameIndex(name: string): number { + const nameRegEx = new RegExp(`${escapeRegExpCharacters(name)}\\s(\\d+)`); + let nameIndex = 0; + for (const profile of this.userDataProfilesService.profiles) { + const matches = nameRegEx.exec(profile.name); + const index = matches ? parseInt(matches[1]) : 0; + nameIndex = index > nameIndex ? index : nameIndex; + } + return nameIndex + 1; + } + + private async showProfilePreviewView(id: string, name: string, primary: IAction, secondary: IAction, refreshAction: boolean, userDataProfilesData: UserDataProfileImportExportState): Promise { + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + const treeView = this.instantiationService.createInstance(TreeView, id, name); + if (refreshAction) { + treeView.showRefreshAction = true; + } + const actionRunner = new ActionRunner(); + const descriptor: ITreeViewDescriptor = { + id, + name, + ctorDescriptor: new SyncDescriptor(UserDataProfilePreviewViewPane, [userDataProfilesData, primary, secondary, actionRunner]), + canToggleVisibility: false, + canMoveView: false, + treeView, + collapsed: false, + }; + + viewsRegistry.registerViews([descriptor], this.viewContainer); + return (await this.viewsService.openView(id, true))!; + } + + private async hideProfilePreviewView(id: string): Promise { + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + const viewDescriptor = viewsRegistry.getView(id); + if (viewDescriptor) { + (viewDescriptor as ITreeViewDescriptor).treeView.dispose(); + viewsRegistry.deregisterViews([viewDescriptor], this.viewContainer); + } + await this.closeAllImportExportPreviewEditors(); + } + + private async closeAllImportExportPreviewEditors(): Promise { + const editorsToColse = this.editorService.getEditors(EditorsOrder.SEQUENTIAL).filter(({ editor }) => editor.resource?.scheme === USER_DATA_PROFILE_EXPORT_SCHEME || editor.resource?.scheme === USER_DATA_PROFILE_EXPORT_PREVIEW_SCHEME || editor.resource?.scheme === USER_DATA_PROFILE_IMPORT_PREVIEW_SCHEME); + if (editorsToColse.length) { + await this.editorService.closeEditors(editorsToColse); + } + } + + async setProfile(profile: IUserDataProfileTemplate): Promise { + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + title: localize('profiles.applying', "{0}: Applying...", PROFILES_CATEGORY.value), + }, async progress => { + if (profile.settings) { + await this.instantiationService.createInstance(SettingsResource).apply(profile.settings, this.userDataProfileService.currentProfile); + } + if (profile.globalState) { + await this.instantiationService.createInstance(GlobalStateResource).apply(profile.globalState, this.userDataProfileService.currentProfile); + } + if (profile.extensions) { + await this.instantiationService.createInstance(ExtensionsResource).apply(profile.extensions, this.userDataProfileService.currentProfile); + } + }); + this.notificationService.info(localize('applied profile', "{0}: Applied successfully.", PROFILES_CATEGORY.value)); + } + +} + +class FileUserDataProfileContentHandler implements IUserDataProfileContentHandler { + + readonly name = localize('local', "Local"); + readonly description = localize('file', "file"); + + constructor( + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IFileService private readonly fileService: IFileService, + @ITextFileService private readonly textFileService: ITextFileService, + ) { } + + async saveProfile(name: string, content: string, token: CancellationToken): Promise { + const link = await this.fileDialogService.showSaveDialog({ + title: localize('export profile dialog', "Save Profile"), + filters: PROFILE_FILTER, + defaultUri: this.uriIdentityService.extUri.joinPath(await this.fileDialogService.defaultFilePath(), `${name}.${PROFILE_EXTENSION}`), + }); + if (!link) { + return null; + } + await this.textFileService.create([{ resource: link, value: content, options: { overwrite: true } }]); + return { link, id: link.toString() }; + } + + async canHandle(uri: URI): Promise { + return uri.scheme !== Schemas.http && uri.scheme !== Schemas.https && await this.fileService.canHandleResource(uri); + } + + async readProfile(uri: URI, token: CancellationToken): Promise { + if (await this.canHandle(uri)) { + return (await this.fileService.readFile(uri, undefined, token)).value.toString(); + } + return null; + } + + async selectProfile(): Promise { + const profileLocation = await this.fileDialogService.showOpenDialog({ + canSelectFolders: false, + canSelectFiles: true, + canSelectMany: false, + filters: PROFILE_FILTER, + title: localize('select profile', "Select Profile"), + }); + return profileLocation ? profileLocation[0] : null; + } + +} + +class UserDataProfilePreviewViewPane extends TreeViewPane { + + private buttonsContainer!: HTMLElement; + private primaryButton!: Button; + private secondaryButton!: Button; + private messageContainer!: HTMLElement; + private dimension: DOM.Dimension | undefined; + private totalTreeItemsCount: number = 0; + + constructor( + private readonly userDataProfileData: UserDataProfileImportExportState, + private readonly primaryAction: Action, + private readonly secondaryAction: Action, + private readonly actionRunner: IActionRunner, + options: IViewletViewOptions, + @IKeybindingService keybindingService: IKeybindingService, + @IContextMenuService contextMenuService: IContextMenuService, + @IConfigurationService configurationService: IConfigurationService, + @IContextKeyService contextKeyService: IContextKeyService, + @IViewDescriptorService viewDescriptorService: IViewDescriptorService, + @IInstantiationService instantiationService: IInstantiationService, + @IOpenerService openerService: IOpenerService, + @IThemeService themeService: IThemeService, + @ITelemetryService telemetryService: ITelemetryService, + @INotificationService notificationService: INotificationService, + ) { + super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService, notificationService); + } + + protected override renderTreeView(container: HTMLElement): void { + this.treeView.dataProvider = this.userDataProfileData; + super.renderTreeView(DOM.append(container, DOM.$('.profile-view-tree-container'))); + this.messageContainer = DOM.append(container, DOM.$('.profile-view-message-container.hide')); + this.createButtons(container); + this._register(this.treeView.onDidChangeCheckboxState(items => { + this.treeView.refresh(this.userDataProfileData.onDidChangeCheckboxState(items)); + this.updateConfirmButtonEnablement(); + })); + this.computeAndLayout(); + this._register(Event.any(this.userDataProfileData.onDidChangeRoots, this.treeView.onDidCollapseItem, this.treeView.onDidExpandItem)(() => this.computeAndLayout())); + } + + private async computeAndLayout() { + const roots = await this.userDataProfileData.getRoots(); + const children = await Promise.all(roots.map(async (root) => { + let expanded = root.collapsibleState === TreeItemCollapsibleState.Expanded; + try { + expanded = !this.treeView.isCollapsed(root); + } catch (error) { /* Ignore because element might not be added yet */ } + if (expanded) { + const children = await root.getChildren(); + return children ?? []; + } + return []; + })); + this.totalTreeItemsCount = roots.length + children.flat().length; + this.updateConfirmButtonEnablement(); + if (this.dimension) { + this.layoutTreeView(this.dimension.height, this.dimension.width); + } + } + + private createButtons(container: HTMLElement): void { + this.buttonsContainer = DOM.append(container, DOM.$('.profile-view-buttons-container')); + + this.primaryButton = this._register(new Button(this.buttonsContainer, { ...defaultButtonStyles })); + this.primaryButton.element.classList.add('profile-view-button'); + this.primaryButton.label = this.primaryAction.label; + this.primaryButton.enabled = this.primaryAction.enabled; + this._register(this.primaryButton.onDidClick(() => this.actionRunner.run(this.primaryAction))); + this._register(this.primaryAction.onDidChange(e => { + if (e.enabled !== undefined) { + this.primaryButton.enabled = e.enabled; + } + })); + + this.secondaryButton = this._register(new Button(this.buttonsContainer, { secondary: true, ...defaultButtonStyles })); + this.secondaryButton.label = this.secondaryAction.label; + this.secondaryButton.element.classList.add('profile-view-button'); + this.secondaryButton.enabled = this.secondaryAction.enabled; + this._register(this.secondaryButton.onDidClick(() => this.actionRunner.run(this.secondaryAction))); + this._register(this.secondaryAction.onDidChange(e => { + if (e.enabled !== undefined) { + this.secondaryButton.enabled = e.enabled; + } + })); + } + + protected override layoutTreeView(height: number, width: number): void { + this.dimension = new DOM.Dimension(width, height); + + let messageContainerHeight = 0; + if (!this.messageContainer.classList.contains('hide')) { + messageContainerHeight = DOM.getClientArea(this.messageContainer).height; + } + + const buttonContainerHeight = 108; + this.buttonsContainer.style.height = `${buttonContainerHeight}px`; + this.buttonsContainer.style.width = `${width}px`; + + super.layoutTreeView(Math.min(height - buttonContainerHeight - messageContainerHeight, 22 * this.totalTreeItemsCount), width); + } + + private updateConfirmButtonEnablement(): void { + this.primaryButton.enabled = this.primaryAction.enabled && this.userDataProfileData.isEnabled(); + } + + private readonly renderDisposables = this._register(new DisposableStore()); + setMessage(message: MarkdownString | undefined): void { + this.messageContainer.classList.toggle('hide', !message); + DOM.clearNode(this.messageContainer); + if (message) { + this.renderDisposables.clear(); + const rendered = this.renderDisposables.add(renderMarkdown(message, { + actionHandler: { + callback: (content) => { + this.openerService.open(content, { allowCommands: true }).catch(onUnexpectedError); + }, + disposables: this.renderDisposables + } + })); + DOM.append(this.messageContainer, rendered.element); + } + } + + refresh(): Promise { + return this.treeView.refresh(); + } +} + +const USER_DATA_PROFILE_EXPORT_SCHEME = 'userdataprofileexport'; +const USER_DATA_PROFILE_EXPORT_PREVIEW_SCHEME = 'userdataprofileexportpreview'; +const USER_DATA_PROFILE_IMPORT_PREVIEW_SCHEME = 'userdataprofileimportpreview'; + +abstract class UserDataProfileImportExportState extends Disposable implements ITreeViewDataProvider { + + private readonly _onDidChangeRoots = this._register(new Emitter()); + readonly onDidChangeRoots = this._onDidChangeRoots.event; + + private readonly descriptions = new Map(); + + constructor( + @IQuickInputService protected readonly quickInputService: IQuickInputService, + ) { + super(); + } + + private _canSelect = true; + get canSelect(): boolean { + return this._canSelect; + } + set canSelect(canSelect: boolean) { + this._canSelect = canSelect; + this.rootsPromise = undefined; + } + + setDescription(type: ProfileResourceType, description: string | undefined): void { + if (description) { + this.descriptions.set(type, description); + } else { + this.descriptions.delete(type); + } + } + + onDidChangeCheckboxState(items: readonly ITreeItem[]): readonly ITreeItem[] { + const toRefresh: ITreeItem[] = []; + for (const item of items) { + if (item.children) { + for (const child of item.children) { + if (child.checkbox) { + child.checkbox.isChecked = !!item.checkbox?.isChecked; + } + } + toRefresh.push(item); + } else { + const parent = (item).parent; + if (item.checkbox?.isChecked && parent?.checkbox) { + parent.checkbox.isChecked = true; + toRefresh.push(parent); + } + } + } + return items; + } + + async getChildren(element?: ITreeItem): Promise { + if (element) { + return (element).getChildren(); + } else { + this.rootsPromise = undefined; + this._onDidChangeRoots.fire(); + return this.getRoots(); + } + } + + private roots: IProfileResourceTreeItem[] = []; + private rootsPromise: Promise | undefined; + getRoots(): Promise { + if (!this.rootsPromise) { + this.rootsPromise = (async () => { + this.roots = await this.fetchRoots(); + for (const root of this.roots) { + if (this.canSelect) { + root.checkbox = { isChecked: true, tooltip: localize('select', "Select {0}", root.label.label) }; + } else { + root.checkbox = undefined; + } + root.description = this.descriptions.get(root.type); + } + return this.roots; + })(); + } + return this.rootsPromise; + } + + isEnabled(resourceType?: ProfileResourceType): boolean { + if (resourceType !== undefined) { + return this.roots.some(root => root.type === resourceType && this.isSelected(root)); + } + return this.roots.some(root => this.isSelected(root)); + } + + async getProfileTemplate(name: string, shortName: string | undefined): Promise { + const roots = await this.getRoots(); + let settings: string | undefined; + let keybindings: string | undefined; + let tasks: string | undefined; + let snippets: string | undefined; + let extensions: string | undefined; + let globalState: string | undefined; + for (const root of roots) { + if (!this.isSelected(root)) { + continue; + } + if (root instanceof SettingsResourceTreeItem) { + settings = await root.getContent(); + } else if (root instanceof KeybindingsResourceTreeItem) { + keybindings = await root.getContent(); + } else if (root instanceof TasksResourceTreeItem) { + tasks = await root.getContent(); + } else if (root instanceof SnippetsResourceTreeItem) { + snippets = await root.getContent(); + } else if (root instanceof ExtensionsResourceTreeItem) { + extensions = await root.getContent(); + } else if (root instanceof GlobalStateResourceTreeItem) { + globalState = await root.getContent(); + } + } + + return { + name, + shortName, + settings, + keybindings, + tasks, + snippets, + extensions, + globalState + }; + } + + private isSelected(treeItem: IProfileResourceTreeItem): boolean { + return treeItem.checkbox?.isChecked ?? true; + } + + protected abstract fetchRoots(): Promise; +} + +class UserDataProfileExportState extends UserDataProfileImportExportState { + + private readonly disposables = this._register(new DisposableStore()); + + constructor( + readonly profile: IUserDataProfile, + @IQuickInputService quickInputService: IQuickInputService, + @IFileService private readonly fileService: IFileService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(quickInputService); + } + + protected async fetchRoots(): Promise { + this.disposables.clear(); + this.disposables.add(this.fileService.registerProvider(USER_DATA_PROFILE_EXPORT_SCHEME, this._register(new InMemoryFileSystemProvider()))); + const previewFileSystemProvider = this._register(new InMemoryFileSystemProvider()); + this.disposables.add(this.fileService.registerProvider(USER_DATA_PROFILE_EXPORT_PREVIEW_SCHEME, previewFileSystemProvider)); + const roots: IProfileResourceTreeItem[] = []; + const exportPreviewProfle = this.createExportPreviewProfile(this.profile); + + const settingsResource = this.instantiationService.createInstance(SettingsResource); + const settingsContent = await settingsResource.getContent(this.profile); + await settingsResource.apply(settingsContent, exportPreviewProfle); + const settingsResourceTreeItem = this.instantiationService.createInstance(SettingsResourceTreeItem, exportPreviewProfle); + if (await settingsResourceTreeItem.hasContent()) { + roots.push(settingsResourceTreeItem); + } + + const keybindingsResource = this.instantiationService.createInstance(KeybindingsResource); + const keybindingsContent = await keybindingsResource.getContent(this.profile); + await keybindingsResource.apply(keybindingsContent, exportPreviewProfle); + const keybindingsResourceTreeItem = this.instantiationService.createInstance(KeybindingsResourceTreeItem, exportPreviewProfle); + if (await keybindingsResourceTreeItem.hasContent()) { + roots.push(keybindingsResourceTreeItem); + } + + const snippetsResource = this.instantiationService.createInstance(SnippetsResource); + const snippetsContent = await snippetsResource.getContent(this.profile); + await snippetsResource.apply(snippetsContent, exportPreviewProfle); + const snippetsResourceTreeItem = this.instantiationService.createInstance(SnippetsResourceTreeItem, exportPreviewProfle); + if (await snippetsResourceTreeItem.hasContent()) { + roots.push(snippetsResourceTreeItem); + } + + const tasksResource = this.instantiationService.createInstance(TasksResource); + const tasksContent = await tasksResource.getContent(this.profile); + await tasksResource.apply(tasksContent, exportPreviewProfle); + const tasksResourceTreeItem = this.instantiationService.createInstance(TasksResourceTreeItem, exportPreviewProfle); + if (await tasksResourceTreeItem.hasContent()) { + roots.push(tasksResourceTreeItem); + } + + const globalStateResource = joinPath(exportPreviewProfle.globalStorageHome, 'globalState.json').with({ scheme: USER_DATA_PROFILE_EXPORT_PREVIEW_SCHEME }); + const globalStateResourceTreeItem = this.instantiationService.createInstance(GlobalStateResourceExportTreeItem, exportPreviewProfle, globalStateResource); + const content = await globalStateResourceTreeItem.getContent(); + if (content) { + await this.fileService.writeFile(globalStateResource, VSBuffer.fromString(JSON.stringify(JSON.parse(content), null, '\t'))); + roots.push(globalStateResourceTreeItem); + } + + const extensionsResourceTreeItem = this.instantiationService.createInstance(ExtensionsResourceExportTreeItem, exportPreviewProfle); + if (await extensionsResourceTreeItem.hasContent()) { + roots.push(extensionsResourceTreeItem); + } + + previewFileSystemProvider.setReadOnly(true); + + return roots; + } + + private createExportPreviewProfile(profile: IUserDataProfile): IUserDataProfile { + return { + id: profile.id, + name: profile.name, + location: profile.location, + isDefault: profile.isDefault, + shortName: profile.shortName, + globalStorageHome: profile.globalStorageHome, + settingsResource: profile.settingsResource.with({ scheme: USER_DATA_PROFILE_EXPORT_SCHEME }), + keybindingsResource: profile.keybindingsResource.with({ scheme: USER_DATA_PROFILE_EXPORT_SCHEME }), + tasksResource: profile.tasksResource.with({ scheme: USER_DATA_PROFILE_EXPORT_SCHEME }), + snippetsHome: profile.snippetsHome.with({ scheme: USER_DATA_PROFILE_EXPORT_SCHEME }), + extensionsResource: profile.extensionsResource, + cacheHome: profile.cacheHome, + useDefaultFlags: profile.useDefaultFlags, + isTransient: profile.isTransient + }; + } + + async getProfileToExport(): Promise { + let name: string | undefined = this.profile.name; + if (this.profile.isDefault) { + name = await this.quickInputService.input({ + placeHolder: localize('export profile name', "Name the profile"), + title: localize('export profile title', "Export Profile"), + async validateInput(input) { + if (!input.trim()) { + return localize('profile name required', "Profile name must be provided."); + } + return undefined; + }, + }); + if (!name) { + return null; + } + } + + return super.getProfileTemplate(name, this.profile.shortName); + } + +} + +class UserDataProfileImportState extends UserDataProfileImportExportState { + + private readonly disposables = this._register(new DisposableStore()); + + constructor( + readonly profile: IUserDataProfileTemplate, + @IFileService private readonly fileService: IFileService, + @IQuickInputService quickInputService: IQuickInputService, + @IInstantiationService private readonly instantiationService: IInstantiationService + ) { + super(quickInputService); + } + + protected async fetchRoots(): Promise { + this.disposables.clear(); + + const inMemoryProvider = this._register(new InMemoryFileSystemProvider()); + this.disposables.add(this.fileService.registerProvider(USER_DATA_PROFILE_IMPORT_PREVIEW_SCHEME, inMemoryProvider)); + const roots: IProfileResourceTreeItem[] = []; + const importPreviewProfle = toUserDataProfile(generateUuid(), this.profile.name, URI.file('/root').with({ scheme: USER_DATA_PROFILE_IMPORT_PREVIEW_SCHEME }), URI.file('/cache').with({ scheme: USER_DATA_PROFILE_IMPORT_PREVIEW_SCHEME })); + + if (this.profile.settings) { + const settingsResource = this.instantiationService.createInstance(SettingsResource); + await settingsResource.apply(this.profile.settings, importPreviewProfle); + const settingsResourceTreeItem = this.instantiationService.createInstance(SettingsResourceTreeItem, importPreviewProfle); + if (await settingsResourceTreeItem.hasContent()) { + roots.push(settingsResourceTreeItem); + } + } + + if (this.profile.keybindings) { + const keybindingsResource = this.instantiationService.createInstance(KeybindingsResource); + await keybindingsResource.apply(this.profile.keybindings, importPreviewProfle); + const keybindingsResourceTreeItem = this.instantiationService.createInstance(KeybindingsResourceTreeItem, importPreviewProfle); + if (await keybindingsResourceTreeItem.hasContent()) { + roots.push(keybindingsResourceTreeItem); + } + } + + if (this.profile.snippets) { + const snippetsResource = this.instantiationService.createInstance(SnippetsResource); + await snippetsResource.apply(this.profile.snippets, importPreviewProfle); + const snippetsResourceTreeItem = this.instantiationService.createInstance(SnippetsResourceTreeItem, importPreviewProfle); + if (await snippetsResourceTreeItem.hasContent()) { + roots.push(snippetsResourceTreeItem); + } + } + + if (this.profile.tasks) { + const tasksResource = this.instantiationService.createInstance(TasksResource); + await tasksResource.apply(this.profile.tasks, importPreviewProfle); + const tasksResourceTreeItem = this.instantiationService.createInstance(TasksResourceTreeItem, importPreviewProfle); + if (await tasksResourceTreeItem.hasContent()) { + roots.push(tasksResourceTreeItem); + } + } + + if (this.profile.globalState) { + const globalStateResource = joinPath(importPreviewProfle.globalStorageHome, 'globalState.json'); + const content = VSBuffer.fromString(JSON.stringify(JSON.parse(this.profile.globalState), null, '\t')); + if (content) { + await this.fileService.writeFile(globalStateResource, content); + roots.push(this.instantiationService.createInstance(GlobalStateResourceImportTreeItem, this.profile.globalState, globalStateResource)); + } + } + + if (this.profile.extensions) { + const extensionsResourceTreeItem = this.instantiationService.createInstance(ExtensionsResourceImportTreeItem, this.profile.extensions); + if (await extensionsResourceTreeItem.hasContent()) { + roots.push(extensionsResourceTreeItem); + } + } + + inMemoryProvider.setReadOnly(true); + + return roots; + } + + isEmpty(): boolean { + return !(this.profile.settings || this.profile.keybindings || this.profile.tasks || this.profile.snippets || this.profile.globalState || this.profile.extensions); + } + + async getProfileTemplateToImport(): Promise { + return this.getProfileTemplate(this.profile.name, this.profile.shortName); + } + +} + +class BarrierAction extends Action { + constructor(barrier: Barrier, action: Action, + notificationService: INotificationService) { + super(action.id, action.label, action.class, action.enabled, async () => { + try { + await action.run(); + } catch (error) { + notificationService.error(error); + throw error; + } + barrier.open(); + }); + } +} + +registerSingleton(IUserDataProfileImportExportService, UserDataProfileImportExportService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts new file mode 100644 index 0000000000..2d5b28f74a --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileInit.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Barrier, Promises } from 'vs/base/common/async'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IUserDataInitializer } from 'vs/workbench/services/userData/browser/userDataInit'; +import { IProfileResourceInitializer, IUserDataProfileService, IUserDataProfileTemplate, ProfileResourceType } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { SettingsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/settingsResource'; +import { GlobalStateResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/globalStateResource'; +import { KeybindingsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/keybindingsResource'; +import { TasksResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/tasksResource'; +import { SnippetsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/snippetsResource'; +import { ExtensionsResourceInitializer } from 'vs/workbench/services/userDataProfile/browser/extensionsResource'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { isString } from 'vs/base/common/types'; +import { IRequestService, asJson } from 'vs/platform/request/common/request'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { URI } from 'vs/base/common/uri'; + +export class UserDataProfileInitializer implements IUserDataInitializer { + + _serviceBrand: any; + + private readonly initialized: ProfileResourceType[] = []; + private readonly initializationFinished = new Barrier(); + + constructor( + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, + @IFileService private readonly fileService: IFileService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IStorageService private readonly storageService: IStorageService, + @ILogService private readonly logService: ILogService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IRequestService private readonly requestService: IRequestService, + ) { + } + + async whenInitializationFinished(): Promise { + await this.initializationFinished.wait(); + } + + async requiresInitialization(): Promise { + if (!this.environmentService.options?.profile?.contents) { + return false; + } + if (!this.storageService.isNew(StorageScope.PROFILE)) { + return false; + } + return true; + } + + async initializeRequiredResources(): Promise { + this.logService.trace(`UserDataProfileInitializer#initializeRequiredResources`); + const promises = []; + const profileTemplate = await this.getProfileTemplate(); + if (profileTemplate?.settings) { + promises.push(this.initialize(new SettingsResourceInitializer(this.userDataProfileService, this.fileService, this.logService), profileTemplate.settings, ProfileResourceType.Settings)); + } + if (profileTemplate?.globalState) { + promises.push(this.initialize(new GlobalStateResourceInitializer(this.storageService), profileTemplate.globalState, ProfileResourceType.GlobalState)); + } + await Promise.all(promises); + } + + async initializeOtherResources(instantiationService: IInstantiationService): Promise { + try { + this.logService.trace(`UserDataProfileInitializer#initializeOtherResources`); + const promises = []; + const profileTemplate = await this.getProfileTemplate(); + if (profileTemplate?.keybindings) { + promises.push(this.initialize(new KeybindingsResourceInitializer(this.userDataProfileService, this.fileService, this.logService), profileTemplate.keybindings, ProfileResourceType.Keybindings)); + } + if (profileTemplate?.tasks) { + promises.push(this.initialize(new TasksResourceInitializer(this.userDataProfileService, this.fileService, this.logService), profileTemplate.tasks, ProfileResourceType.Tasks)); + } + if (profileTemplate?.snippets) { + promises.push(this.initialize(new SnippetsResourceInitializer(this.userDataProfileService, this.fileService, this.uriIdentityService), profileTemplate.snippets, ProfileResourceType.Snippets)); + } + promises.push(this.initializeInstalledExtensions(instantiationService)); + await Promises.settled(promises); + } finally { + this.initializationFinished.open(); + } + } + + private initializeInstalledExtensionsPromise: Promise | undefined; + async initializeInstalledExtensions(instantiationService: IInstantiationService): Promise { + if (!this.initializeInstalledExtensionsPromise) { + const profileTemplate = await this.getProfileTemplate(); + if (profileTemplate?.extensions) { + this.initializeInstalledExtensionsPromise = this.initialize(instantiationService.createInstance(ExtensionsResourceInitializer), profileTemplate.extensions, ProfileResourceType.Extensions); + } else { + this.initializeInstalledExtensionsPromise = Promise.resolve(); + } + + } + return this.initializeInstalledExtensionsPromise; + } + + private profileTemplatePromise: Promise | undefined; + private getProfileTemplate(): Promise { + if (!this.profileTemplatePromise) { + this.profileTemplatePromise = this.doGetProfileTemplate(); + } + return this.profileTemplatePromise; + } + + private async doGetProfileTemplate(): Promise { + if (!this.environmentService.options?.profile?.contents) { + return null; + } + if (isString(this.environmentService.options.profile.contents)) { + try { + return JSON.parse(this.environmentService.options.profile.contents); + } catch (error) { + this.logService.error(error); + return null; + } + } + try { + const url = URI.revive(this.environmentService.options.profile.contents).toString(true); + const context = await this.requestService.request({ type: 'GET', url }, CancellationToken.None); + if (context.res.statusCode === 200) { + return await asJson(context); + } else { + this.logService.warn(`UserDataProfileInitializer: Failed to get profile from URL: ${url}. Status code: ${context.res.statusCode}.`); + } + } catch (error) { + this.logService.error(error); + } + return null; + } + + private async initialize(initializer: IProfileResourceInitializer, content: string, profileResource: ProfileResourceType): Promise { + try { + if (this.initialized.includes(profileResource)) { + this.logService.info(`UserDataProfileInitializer: ${profileResource} initialized already.`); + return; + } + this.initialized.push(profileResource); + this.logService.trace(`UserDataProfileInitializer: Initializing ${profileResource}`); + await initializer.initialize(content); + this.logService.info(`UserDataProfileInitializer: Initialized ${profileResource}`); + } catch (error) { + this.logService.info(`UserDataProfileInitializer: Error while initializing ${profileResource}`); + this.logService.error(error); + } + } + +} diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 88e931a2b1..549fd007be 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -3,16 +3,28 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, UseDefaultProfileFlags, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, IUserDataProfileUpdateOptions } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IWorkspaceContextService, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; 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 { IUserDataProfileManagementService, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { DidChangeUserDataProfileEvent, IUserDataProfileManagementService, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +export type ProfileManagementActionExecutedClassification = { + owner: 'sandy081'; + comment: 'Logged when profile management action is excuted'; + id: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the action that was run.' }; +}; + +export type ProfileManagementActionExecutedEvent = { + id: string; +}; export class UserDataProfileManagementService extends Disposable implements IUserDataProfileManagementService { readonly _serviceBrand: undefined; @@ -25,84 +37,105 @@ export class UserDataProfileManagementService extends Disposable implements IUse @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @ITelemetryService private readonly telemetryService: ITelemetryService, ) { super(); this._register(userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); + this._register(userDataProfilesService.onDidResetWorkspaces(() => this.onDidResetWorkspaces())); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => this.onDidChangeCurrentProfile(e))); } private onDidChangeProfiles(e: DidChangeProfilesEvent): void { if (e.removed.some(profile => profile.id === this.userDataProfileService.currentProfile.id)) { - this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current settings profile has been removed. Please reload to switch back to default settings profile")); + this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); return; } } - async createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise { - const profile = await this.userDataProfilesService.createProfile(name, useDefaultFlags, this.getWorkspaceIdentifier()); + private onDidResetWorkspaces(): void { + if (!this.userDataProfileService.currentProfile.isDefault) { + this.enterProfile(this.userDataProfilesService.defaultProfile, false, localize('reload message when removed', "The current profile has been removed. Please reload to switch back to default profile")); + return; + } + } + + private async onDidChangeCurrentProfile(e: DidChangeUserDataProfileEvent): Promise { + if (e.previous.isTransient) { + await this.userDataProfilesService.cleanUpTransientProfiles(); + } + } + + async createAndEnterProfile(name: string, options?: IUserDataProfileOptions, fromExisting?: boolean): Promise { + const profile = await this.userDataProfilesService.createNamedProfile(name, options, toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())); await this.enterProfile(profile, !!fromExisting); + this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'createAndEnterProfile' }); return profile; } - async renameProfile(profile: IUserDataProfile, name: string): Promise { + async createAndEnterTransientProfile(): Promise { + const profile = await this.userDataProfilesService.createTransientProfile(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace())); + await this.enterProfile(profile, false); + this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'createAndEnterTransientProfile' }); + return profile; + } + + async updateProfile(profile: IUserDataProfile, updateOptions: IUserDataProfileUpdateOptions): Promise { if (!this.userDataProfilesService.profiles.some(p => p.id === profile.id)) { - throw new Error(`Settings profile ${profile.name} does not exist`); + throw new Error(`Profile ${profile.name} does not exist`); } if (profile.isDefault) { - throw new Error(localize('cannotRenameDefaultProfile', "Cannot rename the default settings profile")); + throw new Error(localize('cannotRenameDefaultProfile', "Cannot rename the default profile")); } - await this.userDataProfilesService.updateProfile(profile, name); + await this.userDataProfilesService.updateProfile(profile, updateOptions); + this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'updateProfile' }); } async removeProfile(profile: IUserDataProfile): Promise { if (!this.userDataProfilesService.profiles.some(p => p.id === profile.id)) { - throw new Error(`Settings profile ${profile.name} does not exist`); + throw new Error(`Profile ${profile.name} does not exist`); } if (profile.isDefault) { - throw new Error(localize('cannotDeleteDefaultProfile', "Cannot delete the default settings profile")); + throw new Error(localize('cannotDeleteDefaultProfile', "Cannot delete the default profile")); } await this.userDataProfilesService.removeProfile(profile); + this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'removeProfile' }); } async switchProfile(profile: IUserDataProfile): Promise { - const workspaceIdentifier = this.getWorkspaceIdentifier(); + const workspaceIdentifier = toWorkspaceIdentifier(this.workspaceContextService.getWorkspace()); if (!this.userDataProfilesService.profiles.some(p => p.id === profile.id)) { throw new Error(`Profile ${profile.name} does not exist`); } if (this.userDataProfileService.currentProfile.id === profile.id) { return; } - await this.userDataProfilesService.setProfileForWorkspace(profile, workspaceIdentifier); + await this.userDataProfilesService.setProfileForWorkspace(workspaceIdentifier, profile); await this.enterProfile(profile, false); - } - - private getWorkspaceIdentifier(): WorkspaceIdentifier { - const workspace = this.workspaceContextService.getWorkspace(); - switch (this.workspaceContextService.getWorkbenchState()) { - case WorkbenchState.FOLDER: - return { uri: workspace.folders[0].uri, id: workspace.id }; - case WorkbenchState.WORKSPACE: - return { configPath: workspace.configuration!, id: workspace.id }; - } - return 'empty-window'; + this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'switchProfile' }); } private async enterProfile(profile: IUserDataProfile, preserveData: boolean, reloadMessage?: string): Promise { const isRemoteWindow = !!this.environmentService.remoteAuthority; if (!isRemoteWindow) { - this.extensionService.stopExtensionHosts(); + if (!(await this.extensionService.stopExtensionHosts(localize('switch profile', "Switching to a profile.")))) { + // If extension host did not stop, do not switch profile + if (this.userDataProfilesService.profiles.some(p => p.id === this.userDataProfileService.currentProfile.id)) { + await this.userDataProfilesService.setProfileForWorkspace(toWorkspaceIdentifier(this.workspaceContextService.getWorkspace()), this.userDataProfileService.currentProfile); + } + throw new CancellationError(); + } } // In a remote window update current profile before reloading so that data is preserved from current profile if asked to preserve await this.userDataProfileService.updateCurrentProfile(profile, preserveData); if (isRemoteWindow) { - const result = await this.dialogService.confirm({ - type: 'info', - message: reloadMessage ?? localize('reload message', "Switching a settings profile requires reloading VS Code."), + const { confirmed } = await this.dialogService.confirm({ + message: reloadMessage ?? localize('reload message', "Switching a profile requires reloading VS Code."), primaryButton: localize('reload button', "&&Reload"), }); - if (result.confirmed) { + if (confirmed) { await this.hostService.reload(); } } else { @@ -111,4 +144,4 @@ export class UserDataProfileManagementService extends Disposable implements IUse } } -registerSingleton(IUserDataProfileManagementService, UserDataProfileManagementService); +registerSingleton(IUserDataProfileManagementService, UserDataProfileManagementService, InstantiationType.Eager /* Eager because it updates the current window profile by listening to profiles changes */); diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileStorageService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileStorageService.ts new file mode 100644 index 0000000000..2ee5c4c3ca --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileStorageService.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { IStorageDatabase } from 'vs/base/parts/storage/common/storage'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { AbstractUserDataProfileStorageService, IProfileStorageChanges, IUserDataProfileStorageService } from 'vs/platform/userDataProfile/common/userDataProfileStorageService'; +import { isProfileUsingDefaultStorage, IStorageService, IStorageValueChangeEvent, StorageScope } from 'vs/platform/storage/common/storage'; +import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IndexedDBStorageDatabase } from 'vs/workbench/services/storage/browser/storageService'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +export class UserDataProfileStorageService extends AbstractUserDataProfileStorageService implements IUserDataProfileStorageService { + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + constructor( + @IStorageService storageService: IStorageService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @ILogService private readonly logService: ILogService, + ) { + super(storageService); + this._register(Event.filter(storageService.onDidChangeTarget, e => e.scope === StorageScope.PROFILE)(e => this.onDidChangeStorageTargetInCurrentProfile())); + this._register(Event.filter(storageService.onDidChangeValue, e => e.scope === StorageScope.PROFILE)(e => this.onDidChangeStorageValueInCurrentProfile(e))); + } + + private onDidChangeStorageTargetInCurrentProfile(): void { + // Not broadcasting changes to other windows/tabs as it is not required in web. + // Revisit if needed in future. + this._onDidChange.fire({ targetChanges: [this.userDataProfileService.currentProfile], valueChanges: [] }); + } + + private onDidChangeStorageValueInCurrentProfile(e: IStorageValueChangeEvent): void { + // Not broadcasting changes to other windows/tabs as it is not required in web + // Revisit if needed in future. + this._onDidChange.fire({ targetChanges: [], valueChanges: [{ profile: this.userDataProfileService.currentProfile, changes: [e] }] }); + } + + protected createStorageDatabase(profile: IUserDataProfile): Promise { + return isProfileUsingDefaultStorage(profile) ? IndexedDBStorageDatabase.createApplicationStorage(this.logService) : IndexedDBStorageDatabase.createProfileStorage(profile, this.logService); + } +} + +registerSingleton(IUserDataProfileStorageService, UserDataProfileStorageService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataProfile/common/extensionsProfile.ts b/src/vs/workbench/services/userDataProfile/common/extensionsProfile.ts deleted file mode 100644 index 223e72facf..0000000000 --- a/src/vs/workbench/services/userDataProfile/common/extensionsProfile.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { IResourceProfile } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; - -interface IProfileExtension { - identifier: IExtensionIdentifier; - preRelease?: boolean; - disabled?: boolean; -} - -export class ExtensionsProfile implements IResourceProfile { - - constructor( - @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, - @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, - @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, - @ILogService private readonly logService: ILogService, - ) { - } - - async getProfileContent(): Promise { - const extensions = await this.getLocalExtensions(); - return JSON.stringify(extensions); - } - - async applyProfile(content: string): Promise { - const profileExtensions: IProfileExtension[] = JSON.parse(content); - const installedExtensions = await this.extensionManagementService.getInstalled(); - const extensionsToEnableOrDisable: { extension: ILocalExtension; enablementState: EnablementState }[] = []; - const extensionsToInstall: IProfileExtension[] = []; - for (const e of profileExtensions) { - const installedExtension = installedExtensions.find(installed => areSameExtensions(installed.identifier, e.identifier)); - if (!installedExtension || installedExtension.preRelease !== e.preRelease) { - extensionsToInstall.push(e); - } - if (installedExtension && this.extensionEnablementService.isEnabled(installedExtension) !== !e.disabled) { - extensionsToEnableOrDisable.push({ extension: installedExtension, enablementState: e.disabled ? EnablementState.DisabledGlobally : EnablementState.EnabledGlobally }); - } - } - const extensionsToUninstall: ILocalExtension[] = installedExtensions.filter(extension => extension.type === ExtensionType.User && !profileExtensions.some(({ identifier }) => areSameExtensions(identifier, extension.identifier))); - for (const { extension, enablementState } of extensionsToEnableOrDisable) { - this.logService.trace(`Profile: Updating extension enablement...`, extension.identifier.id); - await this.extensionEnablementService.setEnablement([extension], enablementState); - this.logService.info(`Profile: Updated extension enablement`, extension.identifier.id); - } - if (extensionsToInstall.length) { - const galleryExtensions = await this.extensionGalleryService.getExtensions(extensionsToInstall.map(e => ({ ...e.identifier, hasPreRelease: e.preRelease })), CancellationToken.None); - await Promise.all(extensionsToInstall.map(async e => { - const extension = galleryExtensions.find(galleryExtension => areSameExtensions(galleryExtension.identifier, e.identifier)); - if (!extension) { - return; - } - if (await this.extensionManagementService.canInstall(extension)) { - this.logService.trace(`Profile: Installing extension...`, e.identifier.id, extension.version); - await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true, installPreReleaseVersion: e.preRelease } /* set isMachineScoped value to prevent install and sync dialog in web */); - this.logService.info(`Profile: Installed extension.`, e.identifier.id, extension.version); - } else { - this.logService.info(`Profile: Skipped installing extension because it cannot be installed.`, extension.displayName || extension.identifier.id); - } - })); - } - if (extensionsToUninstall.length) { - await Promise.all(extensionsToUninstall.map(e => this.extensionManagementService.uninstall(e))); - } - } - - private async getLocalExtensions(): Promise { - const result: IProfileExtension[] = []; - const installedExtensions = await this.extensionManagementService.getInstalled(undefined); - for (const extension of installedExtensions) { - const { identifier, preRelease } = extension; - const enablementState = this.extensionEnablementService.getEnablementState(extension); - const disabled = !this.extensionEnablementService.isEnabledEnablementState(enablementState); - if (!disabled && extension.type === ExtensionType.System) { - // skip enabled system extensions - continue; - } - if (disabled && enablementState !== EnablementState.DisabledGlobally && enablementState !== EnablementState.DisabledWorkspace) { - //skip extensions that are not disabled by user - continue; - } - const profileExtension: IProfileExtension = { identifier }; - if (disabled) { - profileExtension.disabled = true; - } - if (preRelease) { - profileExtension.preRelease = true; - } - result.push(profileExtension); - } - return result; - } -} diff --git a/src/vs/workbench/services/userDataProfile/common/globalStateProfile.ts b/src/vs/workbench/services/userDataProfile/common/globalStateProfile.ts deleted file mode 100644 index 8d843dfc93..0000000000 --- a/src/vs/workbench/services/userDataProfile/common/globalStateProfile.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IStringDictionary } from 'vs/base/common/collections'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IResourceProfile } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; - -interface IGlobalState { - storage: IStringDictionary; -} - -export class GlobalStateProfile implements IResourceProfile { - - constructor( - @IStorageService private readonly storageService: IStorageService, - @ILogService private readonly logService: ILogService, - ) { - } - - async getProfileContent(): Promise { - const globalState = await this.getLocalGlobalState(); - return JSON.stringify(globalState); - } - - async applyProfile(content: string): Promise { - const globalState: IGlobalState = JSON.parse(content); - await this.writeLocalGlobalState(globalState); - } - - private async getLocalGlobalState(): Promise { - const storage: IStringDictionary = {}; - for (const { key } of Registry.as(Extensions.ProfileStorageRegistry).all) { - const value = this.storageService.get(key, StorageScope.PROFILE); - if (value) { - storage[key] = value; - } - } - return { storage }; - } - - private async writeLocalGlobalState(globalState: IGlobalState): Promise { - const profileKeys: string[] = Object.keys(globalState.storage); - const updatedStorage: IStringDictionary = globalState.storage; - for (const { key } of Registry.as(Extensions.ProfileStorageRegistry).all) { - if (!profileKeys.includes(key)) { - // Remove the key if it does not exist in the profile - updatedStorage[key] = undefined; - } - } - const updatedStorageKeys: string[] = Object.keys(updatedStorage); - if (updatedStorageKeys.length) { - this.logService.trace(`Profile: Updating global state...`); - for (const key of updatedStorageKeys) { - this.storageService.store(key, globalState.storage[key], StorageScope.PROFILE, StorageTarget.USER); - } - this.logService.info(`Profile: Updated global state`, updatedStorageKeys); - } - } -} diff --git a/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts b/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts new file mode 100644 index 0000000000..d2764438cf --- /dev/null +++ b/src/vs/workbench/services/userDataProfile/common/remoteUserDataProfiles.ts @@ -0,0 +1,180 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { distinct } from 'vs/base/common/arrays'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfileIpc'; + +const associatedRemoteProfilesKey = 'associatedRemoteProfiles'; + +export const IRemoteUserDataProfilesService = createDecorator('IRemoteUserDataProfilesService'); +export interface IRemoteUserDataProfilesService { + readonly _serviceBrand: undefined; + getRemoteProfiles(): Promise; + getRemoteProfile(localProfile: IUserDataProfile): Promise; +} + +class RemoteUserDataProfilesService extends Disposable implements IRemoteUserDataProfilesService { + + readonly _serviceBrand: undefined; + + private readonly initPromise: Promise; + + private remoteUserDataProfilesService: IUserDataProfilesService | undefined; + + constructor( + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, + @IStorageService private readonly storageService: IStorageService, + @ILogService private readonly logService: ILogService, + ) { + super(); + this.initPromise = this.init(); + } + + private async init(): Promise { + const connection = this.remoteAgentService.getConnection(); + if (!connection) { + return; + } + + const environment = await this.remoteAgentService.getEnvironment(); + if (!environment) { + return; + } + + this.remoteUserDataProfilesService = new UserDataProfilesService(environment.profiles.all, environment.profiles.home, connection.getChannel('userDataProfiles')); + this._register(this.userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeLocalProfiles(e))); + + // Associate current local profile with remote profile + const remoteProfile = await this.getAssociatedRemoteProfile(this.userDataProfileService.currentProfile, this.remoteUserDataProfilesService); + if (!remoteProfile.isDefault) { + this.setAssociatedRemoteProfiles([...this.getAssociatedRemoteProfiles(), remoteProfile.id]); + } + + this.cleanUp(); + } + + private async onDidChangeLocalProfiles(e: DidChangeProfilesEvent): Promise { + for (const profile of e.removed) { + const remoteProfile = this.remoteUserDataProfilesService?.profiles.find(p => p.id === profile.id); + if (remoteProfile) { + await this.remoteUserDataProfilesService?.removeProfile(remoteProfile); + } + } + } + + async getRemoteProfiles(): Promise { + await this.initPromise; + + if (!this.remoteUserDataProfilesService) { + throw new Error('Remote profiles service not available in the current window'); + } + + return this.remoteUserDataProfilesService.profiles; + } + + async getRemoteProfile(localProfile: IUserDataProfile): Promise { + await this.initPromise; + + if (!this.remoteUserDataProfilesService) { + throw new Error('Remote profiles service not available in the current window'); + } + + return this.getAssociatedRemoteProfile(localProfile, this.remoteUserDataProfilesService); + } + + private async getAssociatedRemoteProfile(localProfile: IUserDataProfile, remoteUserDataProfilesService: IUserDataProfilesService): Promise { + // If the local profile is the default profile, return the remote default profile + if (localProfile.isDefault) { + return remoteUserDataProfilesService.defaultProfile; + } + + let profile = remoteUserDataProfilesService.profiles.find(p => p.id === localProfile.id); + if (!profile) { + profile = await remoteUserDataProfilesService.createProfile(localProfile.id, localProfile.name, { + shortName: localProfile.shortName, + transient: localProfile.isTransient, + useDefaultFlags: localProfile.useDefaultFlags, + }); + this.setAssociatedRemoteProfiles([...this.getAssociatedRemoteProfiles(), this.userDataProfileService.currentProfile.id]); + } + return profile; + } + + private getAssociatedRemoteProfiles(): string[] { + if (this.environmentService.remoteAuthority) { + const remotes = this.parseAssociatedRemoteProfiles(); + return remotes[this.environmentService.remoteAuthority] ?? []; + } + return []; + } + + private setAssociatedRemoteProfiles(profiles: string[]): void { + if (this.environmentService.remoteAuthority) { + const remotes = this.parseAssociatedRemoteProfiles(); + profiles = distinct(profiles); + if (profiles.length) { + remotes[this.environmentService.remoteAuthority] = profiles; + } else { + delete remotes[this.environmentService.remoteAuthority]; + } + if (Object.keys(remotes).length) { + this.storageService.store(associatedRemoteProfilesKey, JSON.stringify(remotes), StorageScope.APPLICATION, StorageTarget.MACHINE); + } else { + this.storageService.remove(associatedRemoteProfilesKey, StorageScope.APPLICATION); + } + } + } + + private parseAssociatedRemoteProfiles(): IStringDictionary { + if (this.environmentService.remoteAuthority) { + const value = this.storageService.get(associatedRemoteProfilesKey, StorageScope.APPLICATION); + try { + return value ? JSON.parse(value) : {}; + } catch (error) { + this.logService.error(error); + } + } + return {}; + } + + private async cleanUp(): Promise { + const associatedRemoteProfiles: string[] = []; + for (const profileId of this.getAssociatedRemoteProfiles()) { + const remoteProfile = this.remoteUserDataProfilesService?.profiles.find(p => p.id === profileId); + if (!remoteProfile) { + continue; + } + const localProfile = this.userDataProfilesService.profiles.find(p => p.id === profileId); + if (localProfile) { + if (localProfile.name !== remoteProfile.name || localProfile.shortName !== remoteProfile.shortName) { + await this.remoteUserDataProfilesService?.updateProfile(remoteProfile, { name: localProfile.name, shortName: localProfile.shortName }); + } + associatedRemoteProfiles.push(profileId); + continue; + } + if (remoteProfile) { + // Cleanup remote profiles those are not available locally + await this.remoteUserDataProfilesService?.removeProfile(remoteProfile); + } + } + this.setAssociatedRemoteProfiles(associatedRemoteProfiles); + } + +} + +registerSingleton(IRemoteUserDataProfilesService, RemoteUserDataProfilesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataProfile/common/settingsProfile.ts b/src/vs/workbench/services/userDataProfile/common/settingsProfile.ts deleted file mode 100644 index 94f3523a66..0000000000 --- a/src/vs/workbench/services/userDataProfile/common/settingsProfile.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { VSBuffer } from 'vs/base/common/buffer'; -import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IUserDataProfileService, IResourceProfile, ProfileCreationOptions } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { removeComments, updateIgnoredSettings } from 'vs/platform/userDataSync/common/settingsMerge'; -import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync'; - -interface ISettingsContent { - settings: string; -} - -export class SettingsProfile implements IResourceProfile { - - constructor( - @IFileService private readonly fileService: IFileService, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - @IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService, - @ILogService private readonly logService: ILogService, - ) { - } - - async getProfileContent(options?: ProfileCreationOptions): Promise { - const ignoredSettings = this.getIgnoredSettings(); - const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.userDataProfileService.currentProfile.settingsResource); - const localContent = await this.getLocalFileContent(); - let settingsProfileContent = updateIgnoredSettings(localContent || '{}', '{}', ignoredSettings, formattingOptions); - if (options?.skipComments) { - settingsProfileContent = removeComments(settingsProfileContent, formattingOptions); - } - const settingsContent: ISettingsContent = { - settings: settingsProfileContent - }; - return JSON.stringify(settingsContent); - } - - async applyProfile(content: string): Promise { - const settingsContent: ISettingsContent = JSON.parse(content); - this.logService.trace(`Profile: Applying settings...`); - const localSettingsContent = await this.getLocalFileContent(); - const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.userDataProfileService.currentProfile.settingsResource); - const contentToUpdate = updateIgnoredSettings(settingsContent.settings, localSettingsContent || '{}', this.getIgnoredSettings(), formattingOptions); - await this.fileService.writeFile(this.userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString(contentToUpdate)); - this.logService.info(`Profile: Applied settings`); - } - - private getIgnoredSettings(): string[] { - const allSettings = Registry.as(Extensions.Configuration).getConfigurationProperties(); - const ignoredSettings = Object.keys(allSettings).filter(key => allSettings[key]?.scope === ConfigurationScope.MACHINE || allSettings[key]?.scope === ConfigurationScope.MACHINE_OVERRIDABLE); - return ignoredSettings; - } - - private async getLocalFileContent(): Promise { - try { - const content = await this.fileService.readFile(this.userDataProfileService.currentProfile.settingsResource); - return content.value.toString(); - } catch (error) { - return null; - } - } - -} diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index c163de38c5..1d413f0809 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -8,9 +8,15 @@ import { Event } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IUserDataProfile, PROFILES_ENABLEMENT_CONFIG, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { ContextKeyDefinedExpr, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys'; +import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfileUpdateOptions } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { URI } from 'vs/base/common/uri'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { Codicon } from 'vs/base/common/codicons'; +import { ITreeItem, ITreeItemLabel } from 'vs/workbench/common/views'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IProductService } from 'vs/platform/product/common/productService'; export interface DidChangeUserDataProfileEvent { readonly preserveData: boolean; @@ -26,21 +32,26 @@ export interface IUserDataProfileService { readonly onDidChangeCurrentProfile: Event; readonly currentProfile: IUserDataProfile; updateCurrentProfile(currentProfile: IUserDataProfile, preserveData: boolean): Promise; + getShortName(profile: IUserDataProfile): string; } export const IUserDataProfileManagementService = createDecorator('IUserDataProfileManagementService'); export interface IUserDataProfileManagementService { readonly _serviceBrand: undefined; - createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise; + createAndEnterProfile(name: string, options?: IUserDataProfileOptions, fromExisting?: boolean): Promise; + createAndEnterTransientProfile(): Promise; removeProfile(profile: IUserDataProfile): Promise; - renameProfile(profile: IUserDataProfile, name: string): Promise; + updateProfile(profile: IUserDataProfile, updateOptions: IUserDataProfileUpdateOptions): Promise; switchProfile(profile: IUserDataProfile): Promise; } export interface IUserDataProfileTemplate { readonly settings?: string; + readonly keybindings?: string; + readonly tasks?: string; + readonly snippets?: string; readonly globalState?: string; readonly extensions?: string; } @@ -54,27 +65,86 @@ export function isUserDataProfileTemplate(thing: unknown): thing is IUserDataPro && (isUndefined(candidate.extensions) || typeof candidate.extensions === 'string')); } -export type ProfileCreationOptions = { readonly skipComments: boolean }; +export const PROFILE_URL_AUTHORITY = 'profile'; +export function toUserDataProfileUri(path: string, productService: IProductService): URI { + return URI.from({ + scheme: productService.urlProtocol, + authority: PROFILE_URL_AUTHORITY, + path: path.startsWith('/') ? path : `/${path}` + }); +} + +export interface IProfileImportOptions { + readonly preview?: boolean; +} export const IUserDataProfileImportExportService = createDecorator('IUserDataProfileImportExportService'); export interface IUserDataProfileImportExportService { readonly _serviceBrand: undefined; - exportProfile(options?: ProfileCreationOptions): Promise; - importProfile(profile: IUserDataProfileTemplate): Promise; + registerProfileContentHandler(id: string, profileContentHandler: IUserDataProfileContentHandler): IDisposable; + unregisterProfileContentHandler(id: string): void; + + exportProfile(): Promise; + importProfile(uri: URI, options?: IProfileImportOptions): Promise; + showProfileContents(): Promise; + createFromCurrentProfile(name: string): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; } -export interface IResourceProfile { - getProfileContent(): Promise; - applyProfile(content: string): Promise; +export const enum ProfileResourceType { + Settings = 'settings', + Keybindings = 'keybindings', + Snippets = 'snippets', + Tasks = 'tasks', + Extensions = 'extensions', + GlobalState = 'globalState', } -export const ManageProfilesSubMenu = new MenuId('SettingsProfiles'); -export const PROFILES_TTILE = { value: localize('settings profiles', "Settings Profiles"), original: 'Settings Profiles' }; -export const PROFILES_CATEGORY = PROFILES_TTILE.value; +export interface IProfileResourceInitializer { + initialize(content: string): Promise; +} + +export interface IProfileResource { + getContent(profile: IUserDataProfile): Promise; + apply(content: string, profile: IUserDataProfile): Promise; +} + +export interface IProfileResourceTreeItem extends ITreeItem { + readonly type: ProfileResourceType; + readonly label: ITreeItemLabel; + getChildren(): Promise; + getContent(): Promise; +} + +export interface IProfileResourceChildTreeItem extends ITreeItem { + parent: IProfileResourceTreeItem; +} + +export interface ISaveProfileResult { + readonly id: string; + readonly link: URI; +} + +export interface IUserDataProfileContentHandler { + readonly name: string; + readonly description?: string; + readonly extensionId?: string; + saveProfile(name: string, content: string, token: CancellationToken): Promise; + readProfile(idOrUri: string | URI, token: CancellationToken): Promise; +} + +export const defaultUserDataProfileIcon = registerIcon('defaultProfile-icon', Codicon.settings, localize('defaultProfileIcon', 'Icon for Default Profile.')); + +export const ProfilesMenu = new MenuId('Profiles'); +export const MANAGE_PROFILES_ACTION_ID = 'workbench.profiles.actions.manage'; +export const PROFILES_TITLE = { value: localize('profiles', "Profiles"), original: 'Profiles' }; +export const PROFILES_CATEGORY = { ...PROFILES_TITLE }; export const PROFILE_EXTENSION = 'code-profile'; -export const PROFILE_FILTER = [{ name: localize('profile', "Settings Profile"), extensions: [PROFILE_EXTENSION] }]; -export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`)); -export const CURRENT_PROFILE_CONTEXT = new RawContextKey('currentSettingsProfile', ''); -export const HAS_PROFILES_CONTEXT = new RawContextKey('hasSettingsProfiles', false); +export const PROFILE_FILTER = [{ name: localize('profile', "Profile"), extensions: [PROFILE_EXTENSION] }]; +export const PROFILES_ENABLEMENT_CONTEXT = new RawContextKey('profiles.enabled', true); +export const CURRENT_PROFILE_CONTEXT = new RawContextKey('currentProfile', ''); +export const IS_CURRENT_PROFILE_TRANSIENT_CONTEXT = new RawContextKey('isCurrentProfileTransient', false); +export const HAS_PROFILES_CONTEXT = new RawContextKey('hasProfiles', false); +export const IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT = new RawContextKey('isProfileExportInProgress', false); +export const IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT = new RawContextKey('isProfileImportInProgress', false); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts deleted file mode 100644 index e337c61461..0000000000 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileImportExportService.ts +++ /dev/null @@ -1,96 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { ExtensionsProfile } from 'vs/workbench/services/userDataProfile/common/extensionsProfile'; -import { GlobalStateProfile } from 'vs/workbench/services/userDataProfile/common/globalStateProfile'; -import { IUserDataProfileTemplate, IUserDataProfileImportExportService, PROFILES_CATEGORY, IUserDataProfileManagementService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { SettingsProfile } from 'vs/workbench/services/userDataProfile/common/settingsProfile'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; - -export class UserDataProfileImportExportService implements IUserDataProfileImportExportService { - - readonly _serviceBrand: undefined; - - private readonly settingsProfile: SettingsProfile; - private readonly globalStateProfile: GlobalStateProfile; - private readonly extensionsProfile: ExtensionsProfile; - - constructor( - @IInstantiationService instantiationService: IInstantiationService, - @IProgressService private readonly progressService: IProgressService, - @INotificationService private readonly notificationService: INotificationService, - @IUserDataProfileManagementService private readonly userDataProfileManagementService: IUserDataProfileManagementService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - ) { - this.settingsProfile = instantiationService.createInstance(SettingsProfile); - this.globalStateProfile = instantiationService.createInstance(GlobalStateProfile); - this.extensionsProfile = instantiationService.createInstance(ExtensionsProfile); - } - - async exportProfile(options?: { skipComments: boolean }): Promise { - const settings = await this.settingsProfile.getProfileContent(options); - const globalState = await this.globalStateProfile.getProfileContent(); - const extensions = await this.extensionsProfile.getProfileContent(); - return { - settings, - globalState, - extensions - }; - } - - async importProfile(profileTemplate: IUserDataProfileTemplate): Promise { - const name = await this.quickInputService.input({ - placeHolder: localize('name', "Profile name"), - title: localize('save profile as', "Create from Current Profile..."), - }); - if (!name) { - return undefined; - } - - await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('profiles.importing', "{0}: Importing...", PROFILES_CATEGORY), - }, async progress => { - await this.userDataProfileManagementService.createAndEnterProfile(name); - if (profileTemplate.settings) { - await this.settingsProfile.applyProfile(profileTemplate.settings); - } - if (profileTemplate.globalState) { - await this.globalStateProfile.applyProfile(profileTemplate.globalState); - } - if (profileTemplate.extensions) { - await this.extensionsProfile.applyProfile(profileTemplate.extensions); - } - }); - - this.notificationService.info(localize('imported profile', "{0}: Imported successfully.", PROFILES_CATEGORY)); - } - - async setProfile(profile: IUserDataProfileTemplate): Promise { - await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title: localize('profiles.applying', "{0}: Applying...", PROFILES_CATEGORY), - }, async progress => { - if (profile.settings) { - await this.settingsProfile.applyProfile(profile.settings); - } - if (profile.globalState) { - await this.globalStateProfile.applyProfile(profile.globalState); - } - if (profile.extensions) { - await this.extensionsProfile.applyProfile(profile.extensions); - } - }); - this.notificationService.info(localize('applied profile', "{0}: Applied successfully.", PROFILES_CATEGORY)); - } - -} - -registerSingleton(IUserDataProfileImportExportService, UserDataProfileImportExportService); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index 3b2b6d4722..87c9554227 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -6,8 +6,9 @@ import { Promises } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { defaultUserDataProfileIcon, DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class UserDataProfileService extends Disposable implements IUserDataProfileService { @@ -29,15 +30,6 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi super(); this._currentProfile = currentProfile; this._register(userDataProfilesService.onDidChangeProfiles(e => { - /** - * If the current profile is default profile, then reset it because, - * In Desktop the extensions resource will be set/unset in the default profile when profiles are changed. - */ - if (this._currentProfile.isDefault) { - this._currentProfile = userDataProfilesService.defaultProfile; - return; - } - const updatedCurrentProfile = e.updated.find(p => this._currentProfile.id === p.id); if (updatedCurrentProfile) { this._currentProfile = updatedCurrentProfile; @@ -63,4 +55,12 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi }); await Promises.settled(joiners); } + + getShortName(profile: IUserDataProfile): string { + if (!profile.isDefault && profile.shortName && ThemeIcon.fromId(profile.shortName)) { + return profile.shortName; + } + return `$(${defaultUserDataProfileIcon.id})`; + } + } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry.ts deleted file mode 100644 index 8b4281774b..0000000000 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. 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 { Registry } from 'vs/platform/registry/common/platform'; - -export namespace Extensions { - export const ProfileStorageRegistry = 'workbench.registry.profile.storage'; -} - -export interface IProfileStorageKey { - readonly key: string; - readonly description?: string; -} - -/** - * A registry for storage keys used for profiles - */ -export interface IProfileStorageRegistry { - /** - * An event that is triggered when storage keys are registered. - */ - readonly onDidRegister: Event; - - /** - * All registered storage keys - */ - readonly all: IProfileStorageKey[]; - - /** - * Register profile storage keys - * - * @param keys keys to register - */ - registerKeys(keys: IProfileStorageKey[]): void; -} - -class ProfileStorageRegistryImpl extends Disposable implements IProfileStorageRegistry { - - private readonly _onDidRegister = this._register(new Emitter()); - readonly onDidRegister = this._onDidRegister.event; - - private readonly storageKeys = new Map(); - - get all(): IProfileStorageKey[] { - return [...this.storageKeys.values()].flat(); - } - - registerKeys(keys: IProfileStorageKey[]): void { - for (const key of keys) { - this.storageKeys.set(key.key, key); - } - this._onDidRegister.fire(keys); - } - -} - -Registry.add(Extensions.ProfileStorageRegistry, new ProfileStorageRegistryImpl()); - diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncEnablementService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncEnablementService.ts index 7b3c671a38..acaf069750 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncEnablementService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncEnablementService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataSyncEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncEnablementService as BaseUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; @@ -18,4 +18,4 @@ export class UserDataSyncEnablementService extends BaseUserDataSyncEnablementSer } -registerSingleton(IUserDataSyncEnablementService, UserDataSyncEnablementService); +registerSingleton(IUserDataSyncEnablementService, UserDataSyncEnablementService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts new file mode 100644 index 0000000000..448b2762a4 --- /dev/null +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncInit.ts @@ -0,0 +1,423 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { AbstractExtensionsInitializer, IExtensionsInitializerPreviewResult } from 'vs/platform/userDataSync/common/extensionsSync'; +import { GlobalStateInitializer, UserDataSyncStoreTypeSynchronizer } from 'vs/platform/userDataSync/common/globalStateSync'; +import { KeybindingsInitializer } from 'vs/platform/userDataSync/common/keybindingsSync'; +import { SettingsInitializer } from 'vs/platform/userDataSync/common/settingsSync'; +import { SnippetsInitializer } from 'vs/platform/userDataSync/common/snippetsSync'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; +import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IRequestService } from 'vs/platform/request/common/request'; +import { IRemoteUserData, IUserData, IUserDataSyncResourceInitializer, IUserDataSyncLogService, IUserDataSyncStoreManagementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; +import { AuthenticationSessionInfo, getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; +import { getSyncAreaLabel } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { isWeb } from 'vs/base/common/platform'; +import { Barrier, Promises } from 'vs/base/common/async'; +import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IExtensionService, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IIgnoredExtensionsManagementService } from 'vs/platform/userDataSync/common/ignoredExtensions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; +import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; +import { TasksInitializer } from 'vs/platform/userDataSync/common/tasksSync'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { IUserDataInitializer } from 'vs/workbench/services/userData/browser/userDataInit'; + +export class UserDataSyncInitializer implements IUserDataInitializer { + + _serviceBrand: any; + + private readonly initialized: SyncResource[] = []; + private readonly initializationFinished = new Barrier(); + private globalStateUserData: IUserData | null = null; + + constructor( + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, + @ICredentialsService private readonly credentialsService: ICredentialsService, + @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, + @IFileService private readonly fileService: IFileService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IStorageService private readonly storageService: IStorageService, + @IProductService private readonly productService: IProductService, + @IRequestService private readonly requestService: IRequestService, + @ILogService private readonly logService: ILogService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + ) { + this.createUserDataSyncStoreClient().then(userDataSyncStoreClient => { + if (!userDataSyncStoreClient) { + this.initializationFinished.open(); + } + }); + } + + private _userDataSyncStoreClientPromise: Promise | undefined; + private createUserDataSyncStoreClient(): Promise { + if (!this._userDataSyncStoreClientPromise) { + this._userDataSyncStoreClientPromise = (async (): Promise => { + try { + if (!isWeb) { + this.logService.trace(`Skipping initializing user data in desktop`); + return undefined; + } + + if (!this.storageService.isNew(StorageScope.APPLICATION)) { + this.logService.trace(`Skipping initializing user data as application was opened before`); + return undefined; + } + + if (!this.storageService.isNew(StorageScope.WORKSPACE)) { + this.logService.trace(`Skipping initializing user data as workspace was opened before`); + return undefined; + } + + if (this.environmentService.options?.settingsSyncOptions?.authenticationProvider && !this.environmentService.options.settingsSyncOptions.enabled) { + this.logService.trace(`Skipping initializing user data as settings sync is disabled`); + return undefined; + } + + let authenticationSession; + try { + authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService); + } catch (error) { + this.logService.error(error); + } + if (!authenticationSession) { + this.logService.trace(`Skipping initializing user data as authentication session is not set`); + return undefined; + } + + await this.initializeUserDataSyncStore(authenticationSession); + + const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; + if (!userDataSyncStore) { + this.logService.trace(`Skipping initializing user data as sync service is not provided`); + return undefined; + } + + const userDataSyncStoreClient = new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService); + userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); + + const manifest = await userDataSyncStoreClient.manifest(null); + if (manifest === null) { + userDataSyncStoreClient.dispose(); + this.logService.trace(`Skipping initializing user data as there is no data`); + return undefined; + } + + this.logService.info(`Using settings sync service ${userDataSyncStore.url.toString()} for initialization`); + return userDataSyncStoreClient; + + } catch (error) { + this.logService.error(error); + return undefined; + } + })(); + } + + return this._userDataSyncStoreClientPromise; + } + + private async initializeUserDataSyncStore(authenticationSession: AuthenticationSessionInfo): Promise { + const userDataSyncStore = this.userDataSyncStoreManagementService.userDataSyncStore; + if (!userDataSyncStore?.canSwitch) { + return; + } + + const disposables = new DisposableStore(); + try { + const userDataSyncStoreClient = disposables.add(new UserDataSyncStoreClient(userDataSyncStore.url, this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService)); + userDataSyncStoreClient.setAuthToken(authenticationSession.accessToken, authenticationSession.providerId); + + // Cache global state data for global state initialization + this.globalStateUserData = await userDataSyncStoreClient.readResource(SyncResource.GlobalState, null); + + if (this.globalStateUserData) { + const userDataSyncStoreType = new UserDataSyncStoreTypeSynchronizer(userDataSyncStoreClient, this.storageService, this.environmentService, this.fileService, this.logService).getSyncStoreType(this.globalStateUserData); + if (userDataSyncStoreType) { + await this.userDataSyncStoreManagementService.switch(userDataSyncStoreType); + + // Unset cached global state data if urls are changed + if (!isEqual(userDataSyncStore.url, this.userDataSyncStoreManagementService.userDataSyncStore?.url)) { + this.logService.info('Switched settings sync store'); + this.globalStateUserData = null; + } + } + } + } finally { + disposables.dispose(); + } + } + + async whenInitializationFinished(): Promise { + await this.initializationFinished.wait(); + } + + async requiresInitialization(): Promise { + this.logService.trace(`UserDataInitializationService#requiresInitialization`); + const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); + return !!userDataSyncStoreClient; + } + + async initializeRequiredResources(): Promise { + this.logService.trace(`UserDataInitializationService#initializeRequiredResources`); + return this.initialize([SyncResource.Settings, SyncResource.GlobalState]); + } + + async initializeOtherResources(instantiationService: IInstantiationService): Promise { + try { + this.logService.trace(`UserDataInitializationService#initializeOtherResources`); + await Promise.allSettled([this.initialize([SyncResource.Keybindings, SyncResource.Snippets, SyncResource.Tasks]), this.initializeExtensions(instantiationService)]); + } finally { + this.initializationFinished.open(); + } + } + + private async initializeExtensions(instantiationService: IInstantiationService): Promise { + try { + await Promise.all([this.initializeInstalledExtensions(instantiationService), this.initializeNewExtensions(instantiationService)]); + } finally { + this.initialized.push(SyncResource.Extensions); + } + } + + private initializeInstalledExtensionsPromise: Promise | undefined; + async initializeInstalledExtensions(instantiationService: IInstantiationService): Promise { + if (!this.initializeInstalledExtensionsPromise) { + this.initializeInstalledExtensionsPromise = (async () => { + this.logService.trace(`UserDataInitializationService#initializeInstalledExtensions`); + const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); + if (extensionsPreviewInitializer) { + await instantiationService.createInstance(InstalledExtensionsInitializer, extensionsPreviewInitializer).initialize(); + } + })(); + } + return this.initializeInstalledExtensionsPromise; + } + + private initializeNewExtensionsPromise: Promise | undefined; + private async initializeNewExtensions(instantiationService: IInstantiationService): Promise { + if (!this.initializeNewExtensionsPromise) { + this.initializeNewExtensionsPromise = (async () => { + this.logService.trace(`UserDataInitializationService#initializeNewExtensions`); + const extensionsPreviewInitializer = await this.getExtensionsPreviewInitializer(instantiationService); + if (extensionsPreviewInitializer) { + await instantiationService.createInstance(NewExtensionsInitializer, extensionsPreviewInitializer).initialize(); + } + })(); + } + return this.initializeNewExtensionsPromise; + } + + private extensionsPreviewInitializerPromise: Promise | undefined; + private getExtensionsPreviewInitializer(instantiationService: IInstantiationService): Promise { + if (!this.extensionsPreviewInitializerPromise) { + this.extensionsPreviewInitializerPromise = (async () => { + const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); + if (!userDataSyncStoreClient) { + return null; + } + const userData = await userDataSyncStoreClient.readResource(SyncResource.Extensions, null); + return instantiationService.createInstance(ExtensionsPreviewInitializer, userData); + })(); + } + return this.extensionsPreviewInitializerPromise; + } + + private async initialize(syncResources: SyncResource[]): Promise { + const userDataSyncStoreClient = await this.createUserDataSyncStoreClient(); + if (!userDataSyncStoreClient) { + return; + } + + await Promises.settled(syncResources.map(async syncResource => { + try { + if (this.initialized.includes(syncResource)) { + this.logService.info(`${getSyncAreaLabel(syncResource)} initialized already.`); + return; + } + this.initialized.push(syncResource); + this.logService.trace(`Initializing ${getSyncAreaLabel(syncResource)}`); + const initializer = this.createSyncResourceInitializer(syncResource); + const userData = await userDataSyncStoreClient.readResource(syncResource, syncResource === SyncResource.GlobalState ? this.globalStateUserData : null); + await initializer.initialize(userData); + this.logService.info(`Initialized ${getSyncAreaLabel(syncResource)}`); + } catch (error) { + this.logService.info(`Error while initializing ${getSyncAreaLabel(syncResource)}`); + this.logService.error(error); + } + })); + } + + private createSyncResourceInitializer(syncResource: SyncResource): IUserDataSyncResourceInitializer { + switch (syncResource) { + case SyncResource.Settings: return new SettingsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); + case SyncResource.Keybindings: return new KeybindingsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); + case SyncResource.Tasks: return new TasksInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); + case SyncResource.Snippets: return new SnippetsInitializer(this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.storageService, this.uriIdentityService); + case SyncResource.GlobalState: return new GlobalStateInitializer(this.storageService, this.fileService, this.userDataProfilesService, this.environmentService, this.logService, this.uriIdentityService); + } + throw new Error(`Cannot create initializer for ${syncResource}`); + } + +} + +class ExtensionsPreviewInitializer extends AbstractExtensionsInitializer { + + private previewPromise: Promise | undefined; + private preview: IExtensionsInitializerPreviewResult | null = null; + + constructor( + private readonly extensionsData: IUserData, + @IExtensionManagementService extensionManagementService: IExtensionManagementService, + @IIgnoredExtensionsManagementService ignoredExtensionsManagementService: IIgnoredExtensionsManagementService, + @IFileService fileService: IFileService, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, + @IEnvironmentService environmentService: IEnvironmentService, + @IUserDataSyncLogService logService: IUserDataSyncLogService, + @IStorageService storageService: IStorageService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + ) { + super(extensionManagementService, ignoredExtensionsManagementService, fileService, userDataProfilesService, environmentService, logService, storageService, uriIdentityService); + } + + getPreview(): Promise { + if (!this.previewPromise) { + this.previewPromise = super.initialize(this.extensionsData).then(() => this.preview); + } + return this.previewPromise; + } + + override initialize(): Promise { + throw new Error('should not be called directly'); + } + + protected override async doInitialize(remoteUserData: IRemoteUserData): Promise { + const remoteExtensions = await this.parseExtensions(remoteUserData); + if (!remoteExtensions) { + this.logService.info('Skipping initializing extensions because remote extensions does not exist.'); + return; + } + const installedExtensions = await this.extensionManagementService.getInstalled(); + this.preview = this.generatePreview(remoteExtensions, installedExtensions); + } +} + +class InstalledExtensionsInitializer implements IUserDataSyncResourceInitializer { + + constructor( + private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, + @IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService, + @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + } + + async initialize(): Promise { + const preview = await this.extensionsPreviewInitializer.getPreview(); + if (!preview) { + return; + } + + // 1. Initialise already installed extensions state + for (const installedExtension of preview.installedExtensions) { + const syncExtension = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, installedExtension.identifier)); + if (syncExtension?.state) { + const extensionState = this.extensionStorageService.getExtensionState(installedExtension, true) || {}; + Object.keys(syncExtension.state).forEach(key => extensionState[key] = syncExtension.state![key]); + this.extensionStorageService.setExtensionState(installedExtension, extensionState, true); + } + } + + // 2. Initialise extensions enablement + if (preview.disabledExtensions.length) { + for (const identifier of preview.disabledExtensions) { + this.logService.trace(`Disabling extension...`, identifier.id); + await this.extensionEnablementService.disableExtension(identifier); + this.logService.info(`Disabling extension`, identifier.id); + } + } + } +} + +class NewExtensionsInitializer implements IUserDataSyncResourceInitializer { + + constructor( + private readonly extensionsPreviewInitializer: ExtensionsPreviewInitializer, + @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, + @IExtensionGalleryService private readonly galleryService: IExtensionGalleryService, + @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, + @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, + ) { + } + + async initialize(): Promise { + const preview = await this.extensionsPreviewInitializer.getPreview(); + if (!preview) { + return; + } + + const newlyEnabledExtensions: ILocalExtension[] = []; + const targetPlatform = await this.extensionManagementService.getTargetPlatform(); + const galleryExtensions = await this.galleryService.getExtensions(preview.newExtensions, { targetPlatform, compatible: true }, CancellationToken.None); + for (const galleryExtension of galleryExtensions) { + try { + const extensionToSync = preview.remoteExtensions.find(({ identifier }) => areSameExtensions(identifier, galleryExtension.identifier)); + if (!extensionToSync) { + continue; + } + if (extensionToSync.state) { + this.extensionStorageService.setExtensionState(galleryExtension, extensionToSync.state, true); + } + this.logService.trace(`Installing extension...`, galleryExtension.identifier.id); + const local = await this.extensionManagementService.installFromGallery(galleryExtension, { + isMachineScoped: false, /* set isMachineScoped to prevent install and sync dialog in web */ + donotIncludePackAndDependencies: true, + installGivenVersion: !!extensionToSync.version, + installPreReleaseVersion: extensionToSync.preRelease + }); + if (!preview.disabledExtensions.some(identifier => areSameExtensions(identifier, galleryExtension.identifier))) { + newlyEnabledExtensions.push(local); + } + this.logService.info(`Installed extension.`, galleryExtension.identifier.id); + } catch (error) { + this.logService.error(error); + } + } + + const canEnabledExtensions = newlyEnabledExtensions.filter(e => this.extensionService.canAddExtension(toExtensionDescription(e))); + if (!(await this.areExtensionsRunning(canEnabledExtensions))) { + await new Promise((c, e) => { + const disposable = this.extensionService.onDidChangeExtensions(async () => { + try { + if (await this.areExtensionsRunning(canEnabledExtensions)) { + disposable.dispose(); + c(); + } + } catch (error) { + e(error); + } + }); + }); + } + } + + private async areExtensionsRunning(extensions: ILocalExtension[]): Promise { + await this.extensionService.whenInstalledExtensionsRegistered(); + const runningExtensions = this.extensionService.extensions; + return extensions.every(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value }, e.identifier))); + } +} diff --git a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts index fa132a2ff4..442becd812 100644 --- a/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts +++ b/src/vs/workbench/services/userDataSync/browser/userDataSyncWorkbenchService.ts @@ -3,13 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask, IUserDataSyncStoreManagementService, SyncStatus, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncService, IAuthenticationProvider, isAuthenticationProvider, IUserDataAutoSyncService, IUserDataSyncStoreManagementService, SyncStatus, IUserDataSyncEnablementService, IUserDataSyncResource, IResourcePreview, USER_DATA_SYNC_SCHEME, } from 'vs/platform/userDataSync/common/userDataSync'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResource, CONTEXT_ENABLE_SYNC_MERGES_VIEW, SYNC_MERGES_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE } from 'vs/workbench/services/userDataSync/common/userDataSync'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID, SYNC_TITLE, SYNC_CONFLICTS_VIEW_ID, CONTEXT_ENABLE_SYNC_CONFLICTS_VIEW, CONTEXT_HAS_CONFLICTS, IUserDataSyncConflictsView } from 'vs/workbench/services/userDataSync/common/userDataSync'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; -import { flatten, equals } from 'vs/base/common/arrays'; +import { flatten } from 'vs/base/common/arrays'; import { getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount'; @@ -18,16 +18,13 @@ import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { localize } from 'vs/nls'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { Action } from 'vs/base/common/actions'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { IViewsService, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -35,14 +32,13 @@ import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDat import { UserDataSyncStoreTypeSynchronizer } from 'vs/platform/userDataSync/common/globalStateSync'; import { ICredentialsService } from 'vs/platform/credentials/common/credentials'; import { CancellationError } from 'vs/base/common/errors'; - -type FirstTimeSyncClassification = { - owner: 'sandy081'; - comment: 'Action taken when there are merges while turning on settins sync'; - action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'action taken turning on sync. Eg: merge, pull, manual or cancel' }; -}; - -type FirstTimeSyncAction = 'pull' | 'push' | 'merge' | 'manual'; +import { raceCancellationError } from 'vs/base/common/async'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { isDiffEditorInput } from 'vs/workbench/common/editor'; +import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService'; +import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; type AccountQuickPickItem = { label: string; authenticationProvider: IAuthenticationProvider; account?: UserDataSyncAccount; description?: string }; @@ -56,11 +52,18 @@ class UserDataSyncAccount implements IUserDataSyncAccount { get token(): string { return this.session.idToken || this.session.accessToken; } } +type MergeEditorInput = { base: URI; input1: { uri: URI }; input2: { uri: URI }; result: URI }; +export function isMergeEditorInput(editor: unknown): editor is MergeEditorInput { + const candidate = editor as MergeEditorInput; + return URI.isUri(candidate?.base) && URI.isUri(candidate?.input1?.uri) && URI.isUri(candidate?.input2?.uri) && URI.isUri(candidate?.result); +} + export class UserDataSyncWorkbenchService extends Disposable implements IUserDataSyncWorkbenchService { _serviceBrand: any; private static DONOT_USE_WORKBENCH_SESSION_STORAGE_KEY = 'userDataSyncAccount.donotUseWorkbenchSession'; + private static CACHED_AUTHENTICATION_PROVIDER_KEY = 'userDataSyncAccountProvider'; private static CACHED_SESSION_STORAGE_KEY = 'userDataSyncAccountPreference'; get enabled() { return !!this.userDataSyncStoreManagementService.userDataSyncStore; } @@ -81,13 +84,15 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat private readonly syncEnablementContext: IContextKey; private readonly syncStatusContext: IContextKey; private readonly accountStatusContext: IContextKey; - private readonly mergesViewEnablementContext: IContextKey; + private readonly enableConflictsViewContext: IContextKey; + private readonly hasConflicts: IContextKey; private readonly activityViewsEnablementContext: IContextKey; - readonly userDataSyncPreview: UserDataSyncPreview = this._register(new UserDataSyncPreview(this.userDataSyncService)); + private turnOnSyncCancellationToken: CancellationTokenSource | undefined = undefined; constructor( @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IUserDataSyncAccountService private readonly userDataSyncAccountService: IUserDataSyncAccountService, @IQuickInputService private readonly quickInputService: IQuickInputService, @@ -98,7 +103,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @ILogService private readonly logService: ILogService, @IProductService private readonly productService: IProductService, @IExtensionService private readonly extensionService: IExtensionService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, @ICredentialsService private readonly credentialsService: ICredentialsService, @INotificationService private readonly notificationService: INotificationService, @IProgressService private readonly progressService: IProgressService, @@ -109,13 +114,16 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat @IUserDataSyncStoreManagementService private readonly userDataSyncStoreManagementService: IUserDataSyncStoreManagementService, @ILifecycleService private readonly lifecycleService: ILifecycleService, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IEditorService private readonly editorService: IEditorService, + @IUserDataInitializationService private readonly userDataInitializationService: IUserDataInitializationService, ) { super(); this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService); this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService); this.accountStatusContext = CONTEXT_ACCOUNT_STATE.bindTo(contextKeyService); this.activityViewsEnablementContext = CONTEXT_ENABLE_ACTIVITY_VIEWS.bindTo(contextKeyService); - this.mergesViewEnablementContext = CONTEXT_ENABLE_SYNC_MERGES_VIEW.bindTo(contextKeyService); + this.hasConflicts = CONTEXT_HAS_CONFLICTS.bindTo(contextKeyService); + this.enableConflictsViewContext = CONTEXT_ENABLE_SYNC_CONFLICTS_VIEW.bindTo(contextKeyService); if (this.userDataSyncStoreManagementService.userDataSyncStore) { this.syncStatusContext.set(this.userDataSyncService.status); @@ -137,7 +145,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat private async waitAndInitialize(): Promise { /* wait */ - await this.extensionService.whenInstalledExtensionsRegistered(); + await Promise.all([this.extensionService.whenInstalledExtensionsRegistered(), this.userDataInitializationService.whenInitializationFinished()]); /* initialize */ try { @@ -162,8 +170,15 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat private async initialize(): Promise { const authenticationSession = await getCurrentAuthenticationSessionInfo(this.credentialsService, this.productService); - if (this.currentSessionId === undefined && this.useWorkbenchSessionId && (authenticationSession?.id)) { - this.currentSessionId = authenticationSession?.id; + if (this.currentSessionId === undefined && authenticationSession?.id) { + if (this.environmentService.options?.settingsSyncOptions?.authenticationProvider && this.environmentService.options.settingsSyncOptions.enabled) { + this.currentSessionId = authenticationSession.id; + } + + // Backward compatibility + else if (this.useWorkbenchSessionId) { + this.currentSessionId = authenticationSession.id; + } this.useWorkbenchSessionId = false; } @@ -183,7 +198,22 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat this._register(Event.filter(this.authenticationService.onDidChangeSessions, e => this.isSupportedAuthenticationProviderId(e.providerId))(({ event }) => this.onDidChangeSessions(event))); this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorage(e))); - this._register(Event.filter(this.userDataSyncAccountService.onTokenFailed, isSuccessive => isSuccessive)(() => this.onDidSuccessiveAuthFailures())); + this._register(Event.filter(this.userDataSyncAccountService.onTokenFailed, bailout => bailout)(() => this.onDidAuthFailure())); + this.hasConflicts.set(this.userDataSyncService.conflicts.length > 0); + this._register(this.userDataSyncService.onDidChangeConflicts(conflicts => { + this.hasConflicts.set(conflicts.length > 0); + if (!conflicts.length) { + this.enableConflictsViewContext.reset(); + } + // Close merge editors with no conflicts + this.editorService.editors.filter(input => { + const remoteResource = isDiffEditorInput(input) ? input.original.resource : isMergeEditorInput(input) ? input.input1.uri : undefined; + if (remoteResource?.scheme !== USER_DATA_SYNC_SCHEME) { + return false; + } + return !this.userDataSyncService.conflicts.some(({ conflicts }) => conflicts.some(({ previewResource }) => this.uriIdentityService.extUri.isEqual(previewResource, input.resource))); + }).forEach(input => input.dispose()); + })); } private async update(): Promise { @@ -200,6 +230,9 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat this._all = allAccounts; const current = this.current; + if (current) { + this.currentAuthenticationProviderId = current.authenticationProviderId; + } await this.updateToken(current); this.updateAccountStatus(current ? AccountStatus.Available : AccountStatus.Unavailable); } @@ -272,46 +305,50 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat throw new Error(localize('no account', "No account available")); } - await this.turnOnUsingCurrentAccount(); - } - - async turnOnUsingCurrentAccount(): Promise { - if (this.userDataSyncEnablementService.isEnabled()) { - return; - } - - if (this.userDataSyncService.status !== SyncStatus.Idle) { - throw new Error('Cannot turn on sync while syncing'); - } - - if (this.accountStatus !== AccountStatus.Available) { - throw new Error(localize('no account', "No account available")); - } - - const syncTitle = SYNC_TITLE; - const title = `${syncTitle} [(${localize('show log', "show log")})](command:${SHOW_SYNC_LOG_COMMAND_ID})`; - const manualSyncTask = await this.userDataSyncService.createManualSyncTask(); - const disposable = isWeb - ? Disposable.None /* In web long running shutdown handlers will not work */ - : this.lifecycleService.onBeforeShutdown(e => e.veto(this.onBeforeShutdown(manualSyncTask), 'veto.settingsSync')); - + const turnOnSyncCancellationToken = this.turnOnSyncCancellationToken = new CancellationTokenSource(); + const disposable = isWeb ? Disposable.None : this.lifecycleService.onBeforeShutdown(e => e.veto((async () => { + const { confirmed } = await this.dialogService.confirm({ + type: 'warning', + message: localize('sync in progress', "Settings Sync is being turned on. Would you like to cancel it?"), + title: localize('settings sync', "Settings Sync"), + primaryButton: localize({ key: 'yes', comment: ['&& denotes a mnemonic'] }, "&&Yes"), + cancelButton: localize('no', "No") + }); + if (confirmed) { + turnOnSyncCancellationToken.cancel(); + } + return !confirmed; + })(), 'veto.settingsSync')); try { - await this.syncBeforeTurningOn(title, manualSyncTask); + await this.doTurnOnSync(turnOnSyncCancellationToken.token); } finally { disposable.dispose(); + this.turnOnSyncCancellationToken = undefined; } - await this.userDataAutoSyncService.turnOn(); if (this.userDataSyncStoreManagementService.userDataSyncStore?.canSwitch) { await this.synchroniseUserDataSyncStoreType(); } - this.notificationService.info(localize('sync turned on', "{0} is turned on", title)); + this.currentAuthenticationProviderId = this.current?.authenticationProviderId; + if (this.environmentService.options?.settingsSyncOptions?.enablementHandler && this.currentAuthenticationProviderId) { + this.environmentService.options.settingsSyncOptions.enablementHandler(true, this.currentAuthenticationProviderId); + } + + this.notificationService.info(localize('sync turned on', "{0} is turned on", SYNC_TITLE)); } - turnoff(everywhere: boolean): Promise { - return this.userDataAutoSyncService.turnOff(everywhere); + async turnoff(everywhere: boolean): Promise { + if (this.userDataSyncEnablementService.isEnabled()) { + await this.userDataAutoSyncService.turnOff(everywhere); + if (this.environmentService.options?.settingsSyncOptions?.enablementHandler && this.currentAuthenticationProviderId) { + this.environmentService.options.settingsSyncOptions.enablementHandler(false, this.currentAuthenticationProviderId); + } + } + if (this.turnOnSyncCancellationToken) { + this.turnOnSyncCancellationToken.cancel(); + } } async synchroniseUserDataSyncStoreType(): Promise { @@ -333,151 +370,100 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat return this.userDataAutoSyncService.triggerSync(['Sync Now'], false, true); } - private async onBeforeShutdown(manualSyncTask: IManualSyncTask): Promise { - const result = await this.dialogService.confirm({ - type: 'warning', - message: localize('sync in progress', "Settings Sync is being turned on. Would you like to cancel it?"), - title: localize('settings sync', "Settings Sync"), - primaryButton: localize({ key: 'yes', comment: ['&& denotes a mnemonic'] }, "&&Yes"), - secondaryButton: localize({ key: 'no', comment: ['&& denotes a mnemonic'] }, "&&No"), - }); - if (result.confirmed) { - await manualSyncTask.stop(); - } - return !result.confirmed; - } - - private async syncBeforeTurningOn(title: string, manualSyncTask: IManualSyncTask): Promise { + private async doTurnOnSync(token: CancellationToken): Promise { + const disposables = new DisposableStore(); + const manualSyncTask = await this.userDataSyncService.createManualSyncTask(); try { - let action: FirstTimeSyncAction = 'manual'; - await this.progressService.withProgress({ - location: ProgressLocation.Notification, - title, + location: ProgressLocation.Window, + title: SYNC_TITLE, + command: SHOW_SYNC_LOG_COMMAND_ID, delay: 500, }, async progress => { progress.report({ message: localize('turning on', "Turning on...") }); - - const preview = await manualSyncTask.preview(); - const hasRemoteData = manualSyncTask.manifest !== null; - const hasLocalData = await this.userDataSyncService.hasLocalData(); - const hasMergesFromAnotherMachine = preview.some(([syncResource, { isLastSyncFromCurrentMachine, resourcePreviews }]) => - syncResource !== SyncResource.GlobalState && !isLastSyncFromCurrentMachine - && resourcePreviews.some(r => r.localChange !== Change.None || r.remoteChange !== Change.None)); - - action = await this.getFirstTimeSyncAction(hasRemoteData, hasLocalData, hasMergesFromAnotherMachine); - const progressDisposable = manualSyncTask.onSynchronizeResources(synchronizingResources => - synchronizingResources.length ? progress.report({ message: localize('syncing resource', "Syncing {0}...", getSyncAreaLabel(synchronizingResources[0][0])) }) : undefined); - try { - switch (action) { - case 'merge': - await manualSyncTask.merge(); - if (manualSyncTask.status !== SyncStatus.HasConflicts) { - await manualSyncTask.apply(); - } - return; - case 'pull': return await manualSyncTask.pull(); - case 'push': return await manualSyncTask.push(); - case 'manual': return; + disposables.add(this.userDataSyncService.onDidChangeStatus(status => { + if (status === SyncStatus.HasConflicts) { + progress.report({ message: localize('resolving conflicts', "Resolving conflicts...") }); + } else { + progress.report({ message: localize('syncing...', "Turning on...") }); } - } finally { - progressDisposable.dispose(); + })); + await manualSyncTask.merge(); + if (this.userDataSyncService.status === SyncStatus.HasConflicts) { + await this.handleConflictsWhileTurningOn(token); } + await manualSyncTask.apply(); }); - if (manualSyncTask.status === SyncStatus.HasConflicts) { - await this.dialogService.show( - Severity.Warning, - localize('conflicts detected', "Conflicts Detected"), - [localize('merge Manually', "Merge Manually...")], - { - detail: localize('resolve', "Unable to merge due to conflicts. Please merge manually to continue..."), - } - ); - await manualSyncTask.discardConflicts(); - action = 'manual'; - } - if (action === 'manual') { - await this.syncManually(manualSyncTask); - } } catch (error) { await manualSyncTask.stop(); throw error; } finally { - manualSyncTask.dispose(); + disposables.dispose(); } } - private async getFirstTimeSyncAction(hasRemoteData: boolean, hasLocalData: boolean, hasMergesFromAnotherMachine: boolean): Promise { - - if (!hasLocalData /* no data on local */ - || !hasRemoteData /* no data on remote */ - || !hasMergesFromAnotherMachine /* no merges with another machine */ - ) { - return 'merge'; - } - - const result = await this.dialogService.show( - Severity.Info, - localize('merge or replace', "Merge or Replace"), - [ - localize('merge', "Merge"), - localize('replace local', "Replace Local"), - localize('merge Manually', "Merge Manually..."), - localize('cancel', "Cancel"), + private async handleConflictsWhileTurningOn(token: CancellationToken): Promise { + await this.dialogService.prompt({ + type: Severity.Warning, + message: localize('conflicts detected', "Conflicts Detected"), + detail: localize('resolve', "Please resolve conflicts to turn on..."), + buttons: [ + { + label: localize({ key: 'show conflicts', comment: ['&& denotes a mnemonic'] }, "&&Show Conflicts"), + run: async () => { + const waitUntilConflictsAreResolvedPromise = raceCancellationError(Event.toPromise(Event.filter(this.userDataSyncService.onDidChangeConflicts, conficts => conficts.length === 0)), token); + await this.showConflicts(this.userDataSyncService.conflicts[0]?.conflicts[0]); + await waitUntilConflictsAreResolvedPromise; + } + }, + { + label: localize({ key: 'replace local', comment: ['&& denotes a mnemonic'] }, "Replace &&Local"), + run: async () => this.replace(true) + }, + { + label: localize({ key: 'replace remote', comment: ['&& denotes a mnemonic'] }, "Replace &&Remote"), + run: () => this.replace(false) + }, ], - { - cancelId: 3, - detail: localize('first time sync detail', "It looks like you last synced from another machine.\nWould you like to merge or replace with your data in the cloud?"), + cancelButton: { + run: () => { + throw new CancellationError(); + } } - ); - switch (result.choice) { - case 0: - this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' }); - return 'merge'; - case 1: - this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'pull' }); - return 'pull'; - case 2: - this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'manual' }); - return 'manual'; - } - this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' }); - throw new CancellationError(); + }); } - private async syncManually(task: IManualSyncTask): Promise { - const visibleViewContainer = this.viewsService.getVisibleViewContainer(ViewContainerLocation.Sidebar); - const preview = await task.preview(); - this.userDataSyncPreview.setManualSyncPreview(task, preview); - - this.mergesViewEnablementContext.set(true); - await this.waitForActiveSyncViews(); - await this.viewsService.openView(SYNC_MERGES_VIEW_ID); - - const error = await Event.toPromise(this.userDataSyncPreview.onDidCompleteManualSync); - this.userDataSyncPreview.unsetManualSyncPreview(); - - this.mergesViewEnablementContext.set(false); - if (visibleViewContainer) { - this.viewsService.openViewContainer(visibleViewContainer.id); - } else { - const viewContainer = this.viewDescriptorService.getViewContainerByViewId(SYNC_MERGES_VIEW_ID); - this.viewsService.closeViewContainer(viewContainer!.id); + private async replace(local: boolean): Promise { + for (const conflict of this.userDataSyncService.conflicts) { + for (const preview of conflict.conflicts) { + await this.accept({ syncResource: conflict.syncResource, profile: conflict.profile }, local ? preview.remoteResource : preview.localResource, undefined, { force: true }); + } } + } - if (error) { - throw error; + async accept(resource: IUserDataSyncResource, conflictResource: URI, content: string | null | undefined, apply: boolean | { force: boolean }): Promise { + return this.userDataSyncService.accept(resource, conflictResource, content, apply); + } + + async showConflicts(conflictToOpen?: IResourcePreview): Promise { + if (!this.userDataSyncService.conflicts.length) { + return; + } + this.enableConflictsViewContext.set(true); + const view = await this.viewsService.openView(SYNC_CONFLICTS_VIEW_ID); + if (view && conflictToOpen) { + await view.open(conflictToOpen); } } async resetSyncedData(): Promise { - const result = await this.dialogService.confirm({ + const { confirmed } = await this.dialogService.confirm({ + type: 'info', message: localize('reset', "This will clear your data in the cloud and stop sync on all your devices."), title: localize('reset title', "Clear"), - type: 'info', primaryButton: localize({ key: 'resetButton', comment: ['&& denotes a mnemonic'] }, "&&Reset"), }); - if (result.confirmed) { + if (confirmed) { await this.userDataSyncService.resetRemote(); } } @@ -503,7 +489,13 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } async signIn(): Promise { - await this.pick(); + const currentAuthenticationProviderId = this.currentAuthenticationProviderId; + const authenticationProvider = currentAuthenticationProviderId ? this.authenticationProviders.find(p => p.id === currentAuthenticationProviderId) : undefined; + if (authenticationProvider) { + await this.doSignIn(authenticationProvider); + } else { + await this.pick(); + } } private async pick(): Promise { @@ -511,20 +503,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat if (!result) { return false; } - let sessionId: string, accountName: string, accountId: string, authenticationProviderId: string; - if (isAuthenticationProvider(result)) { - const session = await this.authenticationService.createSession(result.id, result.scopes); - sessionId = session.id; - accountName = session.account.label; - accountId = session.account.id; - authenticationProviderId = result.id; - } else { - sessionId = result.sessionId; - accountName = result.accountName; - accountId = result.accountId; - authenticationProviderId = result.authenticationProviderId; - } - await this.switch(sessionId, accountName, accountId, authenticationProviderId); + await this.doSignIn(result); return true; } @@ -598,29 +577,29 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat return quickPickItems; } - private async switch(sessionId: string, accountName: string, accountId: string, authenticationProviderId: string): Promise { - const currentAccount = this.current; - if (this.userDataSyncEnablementService.isEnabled() && (currentAccount && currentAccount.accountName !== accountName)) { - // accounts are switched while sync is enabled. + private async doSignIn(accountOrAuthProvider: UserDataSyncAccount | IAuthenticationProvider): Promise { + let sessionId: string; + if (isAuthenticationProvider(accountOrAuthProvider)) { + if (this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.id === accountOrAuthProvider.id) { + sessionId = await this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.signIn(); + } else { + sessionId = (await this.authenticationService.createSession(accountOrAuthProvider.id, accountOrAuthProvider.scopes)).id; + } + } else { + if (this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.id === accountOrAuthProvider.authenticationProviderId) { + sessionId = await this.environmentService.options?.settingsSyncOptions?.authenticationProvider?.signIn(); + } else { + sessionId = accountOrAuthProvider.sessionId; + } } this.currentSessionId = sessionId; await this.update(); } - private async onDidSuccessiveAuthFailures(): Promise { + private async onDidAuthFailure(): Promise { this.telemetryService.publicLog2<{}, { owner: 'sandy081'; comment: 'Report when there are successive auth failures during settings sync' }>('sync/successiveAuthFailures'); this.currentSessionId = undefined; await this.update(); - - if (this.userDataSyncEnablementService.isEnabled()) { - this.notificationService.notify({ - severity: Severity.Error, - message: localize('successive auth failures', "Settings sync is suspended because of successive authorization failures. Please sign in again to continue synchronizing"), - actions: { - primary: [new Action('sign in', localize('sign in', "Sign in"), undefined, true, () => this.signIn())] - } - }); - } } private onDidChangeSessions(e: AuthenticationSessionsChangeEvent): void { @@ -638,6 +617,25 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } } + private _cachedCurrentAuthenticationProviderId: string | undefined | null = null; + private get currentAuthenticationProviderId(): string | undefined { + if (this._cachedCurrentAuthenticationProviderId === null) { + this._cachedCurrentAuthenticationProviderId = this.storageService.get(UserDataSyncWorkbenchService.CACHED_AUTHENTICATION_PROVIDER_KEY, StorageScope.APPLICATION); + } + return this._cachedCurrentAuthenticationProviderId; + } + + private set currentAuthenticationProviderId(currentAuthenticationProviderId: string | undefined) { + if (this._cachedCurrentAuthenticationProviderId !== currentAuthenticationProviderId) { + this._cachedCurrentAuthenticationProviderId = currentAuthenticationProviderId; + if (currentAuthenticationProviderId === undefined) { + this.storageService.remove(UserDataSyncWorkbenchService.CACHED_AUTHENTICATION_PROVIDER_KEY, StorageScope.APPLICATION); + } else { + this.storageService.store(UserDataSyncWorkbenchService.CACHED_AUTHENTICATION_PROVIDER_KEY, currentAuthenticationProviderId, StorageScope.APPLICATION, StorageTarget.MACHINE); + } + } + } + private _cachedCurrentSessionId: string | undefined | null = null; private get currentSessionId(): string | undefined { if (this._cachedCurrentSessionId === null) { @@ -673,150 +671,4 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat } -class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview { - - private _resources: ReadonlyArray = []; - get resources() { return Object.freeze(this._resources); } - private _onDidChangeResources = this._register(new Emitter>()); - readonly onDidChangeResources = this._onDidChangeResources.event; - - private _conflicts: ReadonlyArray = []; - get conflicts() { return Object.freeze(this._conflicts); } - private _onDidChangeConflicts = this._register(new Emitter>()); - readonly onDidChangeConflicts = this._onDidChangeConflicts.event; - - private _onDidCompleteManualSync = this._register(new Emitter()); - readonly onDidCompleteManualSync = this._onDidCompleteManualSync.event; - private manualSync: { preview: [SyncResource, ISyncResourcePreview][]; task: IManualSyncTask; disposables: DisposableStore } | undefined; - - constructor( - private readonly userDataSyncService: IUserDataSyncService - ) { - super(); - this.updateConflicts(userDataSyncService.conflicts); - this._register(userDataSyncService.onDidChangeConflicts(conflicts => this.updateConflicts(conflicts))); - } - - setManualSyncPreview(task: IManualSyncTask, preview: [SyncResource, ISyncResourcePreview][]): void { - const disposables = new DisposableStore(); - this.manualSync = { task, preview, disposables }; - this.updateResources(); - } - - unsetManualSyncPreview(): void { - if (this.manualSync) { - this.manualSync.disposables.dispose(); - this.manualSync = undefined; - } - this.updateResources(); - } - - async accept(syncResource: SyncResource, resource: URI, content?: string | null): Promise { - if (this.manualSync) { - const syncPreview = await this.manualSync.task.accept(resource, content); - this.updatePreview(syncPreview); - } else { - await this.userDataSyncService.accept(syncResource, resource, content, false); - } - } - - async merge(resource: URI): Promise { - if (!this.manualSync) { - throw new Error('Can merge only while syncing manually'); - } - const syncPreview = await this.manualSync.task.merge(resource); - this.updatePreview(syncPreview); - } - - async discard(resource: URI): Promise { - if (!this.manualSync) { - throw new Error('Can discard only while syncing manually'); - } - const syncPreview = await this.manualSync.task.discard(resource); - this.updatePreview(syncPreview); - } - - async apply(): Promise { - if (!this.manualSync) { - throw new Error('Can apply only while syncing manually'); - } - - try { - const syncPreview = await this.manualSync.task.apply(); - this.updatePreview(syncPreview); - if (!this._resources.length) { - this._onDidCompleteManualSync.fire(undefined); - } - } catch (error) { - await this.manualSync.task.stop(); - this.updatePreview([]); - this._onDidCompleteManualSync.fire(error); - } - } - - async cancel(): Promise { - if (!this.manualSync) { - throw new Error('Can cancel only while syncing manually'); - } - await this.manualSync.task.stop(); - this.updatePreview([]); - this._onDidCompleteManualSync.fire(new CancellationError()); - } - - async pull(): Promise { - if (!this.manualSync) { - throw new Error('Can pull only while syncing manually'); - } - await this.manualSync.task.pull(); - this.updatePreview([]); - } - - async push(): Promise { - if (!this.manualSync) { - throw new Error('Can push only while syncing manually'); - } - await this.manualSync.task.push(); - this.updatePreview([]); - } - - private updatePreview(preview: [SyncResource, ISyncResourcePreview][]) { - if (this.manualSync) { - this.manualSync.preview = preview; - this.updateResources(); - } - } - - private updateConflicts(conflicts: [SyncResource, IResourcePreview[]][]): void { - const newConflicts = this.toUserDataSyncResourceGroups(conflicts); - if (!equals(newConflicts, this._conflicts, (a, b) => isEqual(a.local, b.local))) { - this._conflicts = newConflicts; - this._onDidChangeConflicts.fire(this.conflicts); - } - } - - private updateResources(): void { - const newResources = this.toUserDataSyncResourceGroups( - (this.manualSync?.preview || []) - .map(([syncResource, syncResourcePreview]) => - ([ - syncResource, - syncResourcePreview.resourcePreviews - ])) - ); - if (!equals(newResources, this._resources, (a, b) => isEqual(a.local, b.local) && a.mergeState === b.mergeState)) { - this._resources = newResources; - this._onDidChangeResources.fire(this.resources); - } - } - - private toUserDataSyncResourceGroups(syncResourcePreviews: [SyncResource, IResourcePreview[]][]): IUserDataSyncResource[] { - return flatten( - syncResourcePreviews.map(([syncResource, resourcePreviews]) => - resourcePreviews.map(({ localResource, remoteResource, previewResource, acceptedResource, localChange, remoteChange, mergeState }) => - ({ syncResource, local: localResource, remote: remoteResource, merged: previewResource, accepted: acceptedResource, localChange, remoteChange, mergeState }))) - ); - } - -} - -registerSingleton(IUserDataSyncWorkbenchService, UserDataSyncWorkbenchService); +registerSingleton(IUserDataSyncWorkbenchService, UserDataSyncWorkbenchService, InstantiationType.Eager /* Eager because it initializes settings sync accounts */); diff --git a/src/vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService.ts b/src/vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService.ts index 00acd93dcb..cfeb02bc0d 100644 --- a/src/vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService.ts +++ b/src/vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataSyncEnablementService, SyncResource } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncEnablementService } from 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService'; @@ -35,9 +35,6 @@ export class WebUserDataSyncEnablementService extends UserDataSyncEnablementServ if (this.enabled !== enabled) { this.enabled = enabled; super.setEnablement(enabled); - if (this.workbenchEnvironmentService.options?.settingsSyncOptions?.enablementHandler) { - this.workbenchEnvironmentService.options.settingsSyncOptions.enablementHandler(this.enabled); - } } } @@ -51,4 +48,4 @@ export class WebUserDataSyncEnablementService extends UserDataSyncEnablementServ } -registerSingleton(IUserDataSyncEnablementService, WebUserDataSyncEnablementService); +registerSingleton(IUserDataSyncEnablementService, WebUserDataSyncEnablementService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataSync/common/userDataSync.ts b/src/vs/workbench/services/userDataSync/common/userDataSync.ts index 2bbcd5d57f..94620a3dae 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSync.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSync.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IAuthenticationProvider, SyncStatus, SyncResource, Change, MergeState } from 'vs/platform/userDataSync/common/userDataSync'; +import { IAuthenticationProvider, SyncStatus, SyncResource, IUserDataSyncResource, IResourcePreview } from 'vs/platform/userDataSync/common/userDataSync'; import { Event } from 'vs/base/common/event'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; +import { IView } from 'vs/workbench/common/views'; export interface IUserDataSyncAccount { readonly authenticationProviderId: string; @@ -18,30 +19,6 @@ export interface IUserDataSyncAccount { readonly accountId: string; } -export interface IUserDataSyncPreview { - readonly onDidChangeResources: Event>; - readonly resources: ReadonlyArray; - - accept(syncResource: SyncResource, resource: URI, content?: string | null): Promise; - merge(resource?: URI): Promise; - discard(resource?: URI): Promise; - pull(): Promise; - push(): Promise; - apply(): Promise; - cancel(): Promise; -} - -export interface IUserDataSyncResource { - readonly syncResource: SyncResource; - readonly local: URI; - readonly remote: URI; - readonly merged: URI; - readonly accepted: URI; - readonly localChange: Change; - readonly remoteChange: Change; - readonly mergeState: MergeState; -} - export const IUserDataSyncWorkbenchService = createDecorator('IUserDataSyncWorkbenchService'); export interface IUserDataSyncWorkbenchService { _serviceBrand: any; @@ -55,10 +32,7 @@ export interface IUserDataSyncWorkbenchService { readonly accountStatus: AccountStatus; readonly onDidChangeAccountStatus: Event; - readonly userDataSyncPreview: IUserDataSyncPreview; - turnOn(): Promise; - turnOnUsingCurrentAccount(): Promise; turnoff(everyWhere: boolean): Promise; signIn(): Promise; @@ -67,6 +41,9 @@ export interface IUserDataSyncWorkbenchService { syncNow(): Promise; synchroniseUserDataSyncStoreType(): Promise; + + showConflicts(conflictToOpen?: IResourcePreview): Promise; + accept(resource: IUserDataSyncResource, conflictResource: URI, content: string | null | undefined, apply: boolean): Promise; } export function getSyncAreaLabel(source: SyncResource): string { @@ -77,6 +54,8 @@ export function getSyncAreaLabel(source: SyncResource): string { case SyncResource.Tasks: return localize('tasks', "User Tasks"); case SyncResource.Extensions: return localize('extensions', "Extensions"); case SyncResource.GlobalState: return localize('ui state label', "UI State"); + case SyncResource.Profiles: return localize('profiles', "Profiles"); + case SyncResource.WorkspaceState: return localize('workspace state label', "Workspace State"); } } @@ -86,6 +65,10 @@ export const enum AccountStatus { Available = 'available', } +export interface IUserDataSyncConflictsView extends IView { + open(conflict: IResourcePreview): Promise; +} + export const SYNC_TITLE = localize('sync category', "Settings Sync"); export const SYNC_VIEW_ICON = registerIcon('settings-sync-view-icon', Codicon.sync, localize('syncViewIcon', 'View icon of the Settings Sync view.')); @@ -95,7 +78,8 @@ export const CONTEXT_SYNC_STATE = new RawContextKey('syncStatus', SyncSt export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey('syncEnabled', false); export const CONTEXT_ACCOUNT_STATE = new RawContextKey('userDataSyncAccountStatus', AccountStatus.Uninitialized); export const CONTEXT_ENABLE_ACTIVITY_VIEWS = new RawContextKey(`enableSyncActivityViews`, false); -export const CONTEXT_ENABLE_SYNC_MERGES_VIEW = new RawContextKey(`enableSyncMergesView`, false); +export const CONTEXT_ENABLE_SYNC_CONFLICTS_VIEW = new RawContextKey(`enableSyncConflictsView`, false); +export const CONTEXT_HAS_CONFLICTS = new RawContextKey('hasConflicts', false); // Commands export const CONFIGURE_SYNC_COMMAND_ID = 'workbench.userDataSync.actions.configure'; @@ -103,4 +87,4 @@ export const SHOW_SYNC_LOG_COMMAND_ID = 'workbench.userDataSync.actions.showLog' // VIEWS export const SYNC_VIEW_CONTAINER_ID = 'workbench.view.sync'; -export const SYNC_MERGES_VIEW_ID = 'workbench.views.sync.merges'; +export const SYNC_CONFLICTS_VIEW_ID = 'workbench.views.sync.conflicts'; diff --git a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts index d8d750e24b..652252e055 100644 --- a/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts +++ b/src/vs/workbench/services/userDataSync/common/userDataSyncUtil.ts @@ -6,7 +6,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IUserDataSyncUtilService, getDefaultIgnoredSettings } from 'vs/platform/userDataSync/common/userDataSync'; import { IStringDictionary } from 'vs/base/common/collections'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; @@ -53,4 +53,4 @@ class UserDataSyncUtilService implements IUserDataSyncUtilService { } -registerSingleton(IUserDataSyncUtilService, UserDataSyncUtilService); +registerSingleton(IUserDataSyncUtilService, UserDataSyncUtilService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts index 0710db4842..c158f2893b 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts @@ -7,7 +7,7 @@ import { IUserDataAutoSyncService, UserDataSyncError } from 'vs/platform/userDat import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; class UserDataAutoSyncService implements IUserDataAutoSyncService { @@ -36,4 +36,4 @@ class UserDataAutoSyncService implements IUserDataAutoSyncService { } -registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService); +registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts index 2b3b7b8495..3a271868a3 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts @@ -5,7 +5,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { IUserDataSyncAccountService, IUserDataSyncAccount } from 'vs/platform/userDataSync/common/userDataSyncAccount'; @@ -44,4 +44,4 @@ export class UserDataSyncAccountService extends Disposable implements IUserDataS } -registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService); +registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts index a0a2ee21aa..a9708bbd01 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts @@ -6,7 +6,7 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataSyncMachinesService, IUserDataSyncMachine } from 'vs/platform/userDataSync/common/userDataSyncMachines'; import { Event } from 'vs/base/common/event'; @@ -47,4 +47,4 @@ class UserDataSyncMachinesService extends Disposable implements IUserDataSyncMac } -registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService); +registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts index 3934153b49..c62fed814b 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts @@ -9,7 +9,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { AbstractUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { UserDataSyncStoreManagementServiceChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc'; class UserDataSyncStoreManagementService extends AbstractUserDataSyncStoreManagementService implements IUserDataSyncStoreManagementService { @@ -37,4 +37,4 @@ class UserDataSyncStoreManagementService extends AbstractUserDataSyncStoreManage } -registerSingleton(IUserDataSyncStoreManagementService, UserDataSyncStoreManagementService); +registerSingleton(IUserDataSyncStoreManagementService, UserDataSyncStoreManagementService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService.ts b/src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts similarity index 50% rename from src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService.ts rename to src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts index 5077fe94b8..0846c685a2 100644 --- a/src/vs/workbench/services/sharedProcess/electron-sandbox/sharedProcessWorkerWorkbenchService.ts +++ b/src/vs/workbench/services/utilityProcess/electron-sandbox/utilityProcessWorkerWorkbenchService.ts @@ -5,17 +5,18 @@ import { ILogService } from 'vs/platform/log/common/log'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IOnDidTerminateSharedProcessWorkerProcess, ipcSharedProcessWorkerChannelName, ISharedProcessWorkerProcess, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService'; import { IPCClient, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { generateUuid } from 'vs/base/common/uuid'; import { acquirePort } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp'; +import { IOnDidTerminateUtilityrocessWorkerProcess, ipcUtilityProcessWorkerChannelName, IUtilityProcessWorkerProcess, IUtilityProcessWorkerService } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService'; +import { Barrier, timeout } from 'vs/base/common/async'; -export const ISharedProcessWorkerWorkbenchService = createDecorator('sharedProcessWorkerWorkbenchService'); +export const IUtilityProcessWorkerWorkbenchService = createDecorator('utilityProcessWorkerWorkbenchService'); -export interface ISharedProcessWorker extends IDisposable { +export interface IUtilityProcessWorker extends IDisposable { /** * A IPC client to communicate to the worker process. @@ -31,15 +32,15 @@ export interface ISharedProcessWorker extends IDisposable { * should be restarted in case of an unexpected * termination. */ - onDidTerminate: Promise; + onDidTerminate: Promise; } -export interface ISharedProcessWorkerWorkbenchService { +export interface IUtilityProcessWorkerWorkbenchService { readonly _serviceBrand: undefined; /** - * Will fork a new process with the provided module identifier off the shared + * Will fork a new process with the provided module identifier in a utility * process and establishes a message port connection to that process. * * Requires the forked process to be AMD module that uses our IPC channel framework @@ -60,51 +61,66 @@ export interface ISharedProcessWorkerWorkbenchService { * @returns the worker IPC client to communicate with. Provides a `dispose` method that * allows to terminate the worker if needed. */ - createWorker(process: ISharedProcessWorkerProcess): Promise; + createWorker(process: IUtilityProcessWorkerProcess): Promise; + + /** + * Notifies the service that the workbench window has restored. + */ + notifyRestored(): void; } -export class SharedProcessWorkerWorkbenchService extends Disposable implements ISharedProcessWorkerWorkbenchService { +export class UtilityProcessWorkerWorkbenchService extends Disposable implements IUtilityProcessWorkerWorkbenchService { declare readonly _serviceBrand: undefined; - private _sharedProcessWorkerService: ISharedProcessWorkerService | undefined = undefined; - private get sharedProcessWorkerService(): ISharedProcessWorkerService { - if (!this._sharedProcessWorkerService) { - this._sharedProcessWorkerService = ProxyChannel.toService(this.sharedProcessService.getChannel(ipcSharedProcessWorkerChannelName)); + private _utilityProcessWorkerService: IUtilityProcessWorkerService | undefined = undefined; + private get utilityProcessWorkerService(): IUtilityProcessWorkerService { + if (!this._utilityProcessWorkerService) { + const channel = this.mainProcessService.getChannel(ipcUtilityProcessWorkerChannelName); + this._utilityProcessWorkerService = ProxyChannel.toService(channel); } - return this._sharedProcessWorkerService; + return this._utilityProcessWorkerService; } + private readonly restoredBarrier = new Barrier(); + constructor( readonly windowId: number, @ILogService private readonly logService: ILogService, - @ISharedProcessService private readonly sharedProcessService: ISharedProcessService + @IMainProcessService private readonly mainProcessService: IMainProcessService ) { super(); } - async createWorker(process: ISharedProcessWorkerProcess): Promise { - this.logService.trace('Renderer->SharedProcess#createWorker'); + async createWorker(process: IUtilityProcessWorkerProcess): Promise { + this.logService.trace('Renderer->UtilityProcess#createWorker'); - // Get ready to acquire the message port from the shared process worker + // We want to avoid heavy utility process work to happen before + // the window has restored. As such, make sure we await the + // `Restored` phase before making a connection attempt, but also + // add a timeout to be safe against possible deadlocks. + + await Promise.race([this.restoredBarrier.wait(), timeout(2000)]); + + // Get ready to acquire the message port from the utility process worker const nonce = generateUuid(); - const responseChannel = 'vscode:createSharedProcessWorkerMessageChannelResult'; + const responseChannel = 'vscode:createUtilityProcessWorkerMessageChannelResult'; const portPromise = acquirePort(undefined /* we trigger the request via service call! */, responseChannel, nonce); - // Actually talk with the shared process service + // Actually talk with the utility process service // to create a new process from a worker - const onDidTerminate = this.sharedProcessWorkerService.createWorker({ + const onDidTerminate = this.utilityProcessWorkerService.createWorker({ process, reply: { windowId: this.windowId, channel: responseChannel, nonce } }); - // Dispose worker upon disposal via shared process service + // Dispose worker upon disposal via utility process service const disposables = new DisposableStore(); disposables.add(toDisposable(() => { - this.logService.trace('Renderer->SharedProcess#disposeWorker', process); + this.logService.trace('Renderer->UtilityProcess#disposeWorker', process); - this.sharedProcessWorkerService.disposeWorker({ + this.utilityProcessWorkerService.disposeWorker({ process, reply: { windowId: this.windowId } }); @@ -112,8 +128,22 @@ export class SharedProcessWorkerWorkbenchService extends Disposable implements I const port = await portPromise; const client = disposables.add(new MessagePortClient(port, `window:${this.windowId},module:${process.moduleId}`)); - this.logService.trace('Renderer->SharedProcess#createWorkerChannel: connection established'); + this.logService.trace('Renderer->UtilityProcess#createWorkerChannel: connection established'); + + onDidTerminate.then(({ reason }) => { + if (reason?.code === 0) { + this.logService.trace(`[UtilityProcessWorker]: terminated normally with code ${reason.code}, signal: ${reason.signal}`); + } else { + this.logService.error(`[UtilityProcessWorker]: terminated unexpectedly with code ${reason?.code}, signal: ${reason?.signal}`); + } + }); return { client, onDidTerminate, dispose: () => disposables.dispose() }; } + + notifyRestored(): void { + if (!this.restoredBarrier.isOpen()) { + this.restoredBarrier.open(); + } + } } diff --git a/src/vs/workbench/services/views/browser/treeViewsService.ts b/src/vs/workbench/services/views/browser/treeViewsService.ts index e40d1a7c54..a952141e01 100644 --- a/src/vs/workbench/services/views/browser/treeViewsService.ts +++ b/src/vs/workbench/services/views/browser/treeViewsService.ts @@ -3,12 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { VSDataTransfer } from 'vs/base/common/dataTransfer'; import { ITreeItem } from 'vs/workbench/common/views'; import { ITreeViewsService as ITreeViewsServiceCommon, TreeviewsService } from 'vs/workbench/services/views/common/treeViewsService'; -export interface ITreeViewsService extends ITreeViewsServiceCommon { } +export interface ITreeViewsService extends ITreeViewsServiceCommon { } export const ITreeViewsService = createDecorator('treeViewsService'); -registerSingleton(ITreeViewsService, TreeviewsService); +registerSingleton(ITreeViewsService, TreeviewsService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 0766e0d9b6..cd98d253d3 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -8,10 +8,10 @@ import { IContextKey, RawContextKey, IContextKeyService, ContextKeyExpr } from ' import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { toDisposable, DisposableStore, Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, DisposableStore, Disposable, IDisposable, DisposableMap } from 'vs/base/common/lifecycle'; import { ViewPaneContainer, ViewPaneContainerAction, ViewsSubMenu } from 'vs/workbench/browser/parts/views/viewPaneContainer'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; @@ -19,10 +19,12 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { getViewsStateStorageId, ViewContainerModel } from 'vs/workbench/services/views/common/viewContainerModel'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; +import { IStringDictionary } from 'vs/base/common/collections'; -interface ICachedViewContainerInfo { - containerId: string; +interface IViewsCustomizations { + viewContainerLocations: IStringDictionary; + viewLocations: IStringDictionary; + viewContainerBadgeEnablementStates: IStringDictionary; } function getViewContainerStorageId(viewContainerId: string): string { return `${viewContainerId}.state`; } @@ -31,8 +33,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor declare readonly _serviceBrand: undefined; - private static readonly CACHED_VIEW_POSITIONS = 'views.cachedViewPositions'; - private static readonly CACHED_VIEW_CONTAINER_LOCATIONS = 'views.cachedViewContainerLocations'; + private static readonly VIEWS_CUSTOMIZATIONS = 'views.customizations'; private static readonly COMMON_CONTAINER_ID_PREFIX = 'workbench.views.service'; private readonly _onDidChangeContainer: Emitter<{ views: IViewDescriptor[]; from: ViewContainer; to: ViewContainer }> = this._register(new Emitter<{ views: IViewDescriptor[]; from: ViewContainer; to: ViewContainer }>()); @@ -44,8 +45,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly _onDidChangeContainerLocation: Emitter<{ viewContainer: ViewContainer; from: ViewContainerLocation; to: ViewContainerLocation }> = this._register(new Emitter<{ viewContainer: ViewContainer; from: ViewContainerLocation; to: ViewContainerLocation }>()); readonly onDidChangeContainerLocation: Event<{ viewContainer: ViewContainer; from: ViewContainerLocation; to: ViewContainerLocation }> = this._onDidChangeContainerLocation.event; - private readonly viewContainerModels: Map; - private readonly viewsVisibilityActionDisposables: Map; + private readonly viewContainerModels = this._register(new DisposableMap()); + private readonly viewsVisibilityActionDisposables = this._register(new DisposableMap()); private readonly activeViewContextKeys: Map>; private readonly movableViewContextKeys: Map>; private readonly defaultViewLocationContextKeys: Map>; @@ -54,40 +55,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private readonly viewsRegistry: IViewsRegistry; private readonly viewContainersRegistry: IViewContainersRegistry; - private cachedViewInfo: Map; - private cachedViewContainerInfo: Map; - - private _cachedViewPositionsValue: string | undefined; - private get cachedViewPositionsValue(): string { - if (!this._cachedViewPositionsValue) { - this._cachedViewPositionsValue = this.getStoredCachedViewPositionsValue(); - } - - return this._cachedViewPositionsValue; - } - - private set cachedViewPositionsValue(value: string) { - if (this.cachedViewPositionsValue !== value) { - this._cachedViewPositionsValue = value; - this.setStoredCachedViewPositionsValue(value); - } - } - - private _cachedViewContainerLocationsValue: string | undefined; - private get cachedViewContainerLocationsValue(): string { - if (!this._cachedViewContainerLocationsValue) { - this._cachedViewContainerLocationsValue = this.getStoredCachedViewContainerLocationsValue(); - } - - return this._cachedViewContainerLocationsValue; - } - - private set cachedViewContainerLocationsValue(value: string) { - if (this._cachedViewContainerLocationsValue !== value) { - this._cachedViewContainerLocationsValue = value; - this.setStoredCachedViewContainerLocationsValue(value); - } - } + private viewContainersCustomLocations: Map; + private viewDescriptorsCustomLocations: Map; + private viewContainerBadgeEnablementStates: Map; private readonly _onDidChangeViewContainers = this._register(new Emitter<{ added: ReadonlyArray<{ container: ViewContainer; location: ViewContainerLocation }>; removed: ReadonlyArray<{ container: ViewContainer; location: ViewContainerLocation }> }>()); readonly onDidChangeViewContainers = this._onDidChangeViewContainers.event; @@ -102,8 +72,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor ) { super(); - this.viewContainerModels = new Map(); - this.viewsVisibilityActionDisposables = new Map(); this.activeViewContextKeys = new Map>(); this.movableViewContextKeys = new Map>(); this.defaultViewLocationContextKeys = new Map>(); @@ -112,8 +80,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); this.viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); - this.cachedViewContainerInfo = this.getCachedViewContainerLocations(); - this.cachedViewInfo = this.getCachedViewPositions(); + this.migrateToViewsCustomizationsStorage(); + this.viewContainersCustomLocations = new Map(Object.entries(this.viewCustomizations.viewContainerLocations)); + this.viewDescriptorsCustomLocations = new Map(Object.entries(this.viewCustomizations.viewLocations)); + this.viewContainerBadgeEnablementStates = new Map(Object.entries(this.viewCustomizations.viewContainerBadgeEnablementStates)); // Register all containers that were registered before this ctor this.viewContainers.forEach(viewContainer => this.onDidRegisterViewContainer(viewContainer)); @@ -121,7 +91,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this._register(this.viewsRegistry.onViewsRegistered(views => this.onDidRegisterViews(views))); this._register(this.viewsRegistry.onViewsDeregistered(({ views, viewContainer }) => this.onDidDeregisterViews(views, viewContainer))); - this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => this.moveViews(views, from, to))); + this._register(this.viewsRegistry.onDidChangeContainer(({ views, from, to }) => this.onDidChangeDefaultContainer(views, from, to))); this._register(this.viewContainersRegistry.onDidRegister(({ viewContainer }) => { this.onDidRegisterViewContainer(viewContainer); @@ -133,58 +103,62 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this._onDidChangeViewContainers.fire({ removed: [{ container: viewContainer, location: this.getViewContainerLocation(viewContainer) }], added: [] }); })); - this._register(toDisposable(() => { - this.viewContainerModels.forEach(({ disposable }) => disposable.dispose()); - this.viewContainerModels.clear(); - this.viewsVisibilityActionDisposables.forEach(disposables => disposables.dispose()); - this.viewsVisibilityActionDisposables.clear(); - })); - this._register(this.storageService.onDidChangeValue((e) => { this.onDidStorageChange(e); })); - this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions())); + this.extensionService.whenInstalledExtensionsRegistered().then(() => this.whenExtensionsRegistered()); - // Cached View Containers Locations should be registered before Cached View Positions - // Because View Containers cache should be updated first because View Positions Cache depends on View Containers Cache if views are moved to generated view containers - Registry.as(Extensions.ProfileStorageRegistry) - .registerKeys([{ - key: ViewDescriptorService.CACHED_VIEW_CONTAINER_LOCATIONS, - description: localize('cachedViewContainerPositions', "View Container locations customizations"), - }, { - key: ViewDescriptorService.CACHED_VIEW_POSITIONS, - description: localize('cachedViewPositions', "View locations customizations"), - }]); } - private registerGroupedViews(groupedViews: Map): void { - // Register views that have already been registered to their correct view containers - for (const containerId of groupedViews.keys()) { + private migrateToViewsCustomizationsStorage(): void { + if (this.storageService.get(ViewDescriptorService.VIEWS_CUSTOMIZATIONS, StorageScope.PROFILE)) { + return; + } + + const viewContainerLocationsValue = this.storageService.get('views.cachedViewContainerLocations', StorageScope.PROFILE); + const viewDescriptorLocationsValue = this.storageService.get('views.cachedViewPositions', StorageScope.PROFILE); + if (!viewContainerLocationsValue && !viewDescriptorLocationsValue) { + return; + } + + const viewContainerLocations: [string, ViewContainerLocation][] = viewContainerLocationsValue ? JSON.parse(viewContainerLocationsValue) : []; + const viewDescriptorLocations: [string, { containerId: string }][] = viewDescriptorLocationsValue ? JSON.parse(viewDescriptorLocationsValue) : []; + const viewsCustomizations: IViewsCustomizations = { + viewContainerLocations: viewContainerLocations.reduce>((result, [id, location]) => { result[id] = location; return result; }, {}), + viewLocations: viewDescriptorLocations.reduce>((result, [id, { containerId }]) => { result[id] = containerId; return result; }, {}), + viewContainerBadgeEnablementStates: {} + }; + this.storageService.store(ViewDescriptorService.VIEWS_CUSTOMIZATIONS, JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + this.storageService.remove('views.cachedViewContainerLocations', StorageScope.PROFILE); + this.storageService.remove('views.cachedViewPositions', StorageScope.PROFILE); + } + + private registerGroupedViews(groupedViews: Map): void { + for (const [containerId, views] of groupedViews.entries()) { const viewContainer = this.viewContainersRegistry.get(containerId); - const containerData = groupedViews.get(containerId)!; // The container has not been registered yet if (!viewContainer || !this.viewContainerModels.has(viewContainer)) { - if (containerData.cachedContainerInfo && this.isGeneratedContainerId(containerData.cachedContainerInfo.containerId)) { - if (!this.viewContainersRegistry.get(containerId)) { - this.registerGeneratedViewContainer(this.cachedViewContainerInfo.get(containerId)!, containerId); + // Register if the container is a genarated container + if (this.isGeneratedContainerId(containerId)) { + const viewContainerLocation = this.viewContainersCustomLocations.get(containerId); + if (viewContainerLocation !== undefined) { + this.registerGeneratedViewContainer(viewContainerLocation, containerId); } } - - // Registration of a generated container handles registration of its views + // Registration of the container handles registration of its views continue; } // Filter out views that have already been added to the view container model // This is needed when statically-registered views are moved to // other statically registered containers as they will both try to add on startup - const viewsToAdd = containerData.views.filter(view => this.getViewContainerModel(viewContainer).allViewDescriptors.filter(vd => vd.id === view.id).length === 0); + const viewsToAdd = views.filter(view => this.getViewContainerModel(viewContainer).allViewDescriptors.filter(vd => vd.id === view.id).length === 0); this.addViews(viewContainer, viewsToAdd); } } - private deregisterGroupedViews(groupedViews: Map): void { - // Register views that have already been registered to their correct view containers - for (const viewContainerId of groupedViews.keys()) { + private deregisterGroupedViews(groupedViews: Map): void { + for (const [viewContainerId, views] of groupedViews.entries()) { const viewContainer = this.viewContainersRegistry.get(viewContainerId); // The container has not been registered yet @@ -192,15 +166,13 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor continue; } - this.removeViews(viewContainer, groupedViews.get(viewContainerId)!.views); + this.removeViews(viewContainer, views); } } - private fallbackOrphanedViews(): void { - for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { - const containerId = containerInfo.containerId; - - // check if cached view container is registered + private moveOrphanViewsToDefaultLocation(): void { + for (const [viewId, containerId] of this.viewDescriptorsCustomLocations.entries()) { + // check if the view container exists if (this.viewContainersRegistry.get(containerId)) { continue; } @@ -214,20 +186,29 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } - private onDidRegisterExtensions(): void { - // If an extension is uninstalled, this method will handle resetting views to default locations - this.fallbackOrphanedViews(); + whenExtensionsRegistered(): void { + + // Handle those views whose custom parent view container does not exist anymore + // May be the extension contributing this view container is no longer installed + // Or the parent view container is generated and no longer available. + this.moveOrphanViewsToDefaultLocation(); // Clean up empty generated view containers - for (const viewContainerId of [...this.cachedViewContainerInfo.keys()]) { - this.cleanUpViewContainer(viewContainerId); + for (const viewContainerId of [...this.viewContainersCustomLocations.keys()]) { + this.cleanUpGeneratedViewContainer(viewContainerId); } + + // Save updated view customizations after cleanup + this.saveViewCustomizations(); + + // Register visibility actions for all views + this.registerViewsVisibilityActions(); } private onDidRegisterViews(views: { views: IViewDescriptor[]; viewContainer: ViewContainer }[]): void { this.contextKeyService.bufferChangeEvents(() => { views.forEach(({ views, viewContainer }) => { - // When views are registered, we need to regroup them based on the cache + // When views are registered, we need to regroup them based on the customizations const regroupedViews = this.regroupViews(viewContainer.id, views); // Once they are grouped, try registering them which occurs @@ -245,7 +226,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private onDidDeregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void { - // When views are registered, we need to regroup them based on the cache + // When views are registered, we need to regroup them based on the customizations const regroupedViews = this.regroupViews(viewContainer.id, views); this.deregisterGroupedViews(regroupedViews); this.contextKeyService.bufferChangeEvents(() => { @@ -253,19 +234,19 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor }); } - private regroupViews(containerId: string, views: IViewDescriptor[]): Map { - const ret = new Map(); + private regroupViews(containerId: string, views: IViewDescriptor[]): Map { + const viewsByContainer = new Map(); - views.forEach(viewDescriptor => { - const containerInfo = this.cachedViewInfo.get(viewDescriptor.id); - const correctContainerId = containerInfo?.containerId || containerId; + for (const viewDescriptor of views) { + const correctContainerId = this.viewDescriptorsCustomLocations.get(viewDescriptor.id) ?? containerId; + let containerViews = viewsByContainer.get(correctContainerId); + if (!containerViews) { + viewsByContainer.set(correctContainerId, containerViews = []); + } + containerViews.push(viewDescriptor); + } - const containerData = ret.get(correctContainerId) || { cachedContainerInfo: containerInfo, views: [] }; - containerData.views.push(viewDescriptor); - ret.set(correctContainerId, containerData); - }); - - return ret; + return viewsByContainer; } getViewDescriptorById(viewId: string): IViewDescriptor | null { @@ -282,16 +263,15 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } getViewContainerByViewId(viewId: string): ViewContainer | null { - const containerId = this.cachedViewInfo.get(viewId)?.containerId; + const containerId = this.viewDescriptorsCustomLocations.get(viewId); return containerId ? this.viewContainersRegistry.get(containerId) ?? null : - this.viewsRegistry.getViewContainer(viewId); + this.getDefaultContainerById(viewId); } getViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation { - const location = this.cachedViewContainerInfo.get(viewContainer.id); - return location !== undefined ? location : this.getDefaultViewContainerLocation(viewContainer); + return this.viewContainersCustomLocations.get(viewContainer.id) ?? this.getDefaultViewContainerLocation(viewContainer); } getDefaultViewContainerLocation(viewContainer: ViewContainer): ViewContainerLocation { @@ -318,32 +298,19 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewContainersRegistry.getDefaultViewContainer(location); } - private doMoveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation, requestedIndex?: number, skipDiskWrite?: boolean): void { - const from = this.getViewContainerLocation(viewContainer); - const to = location; - if (from !== to) { - this.cachedViewContainerInfo.set(viewContainer.id, to); - - const defaultLocation = this.isGeneratedContainerId(viewContainer.id) ? true : this.getViewContainerLocation(viewContainer) === this.getDefaultViewContainerLocation(viewContainer); - this.getOrCreateDefaultViewContainerLocationContextKey(viewContainer).set(defaultLocation); - - viewContainer.requestedIndex = requestedIndex; - this._onDidChangeContainerLocation.fire({ viewContainer, from, to }); - - const views = this.getViewsByContainer(viewContainer); - this._onDidChangeLocation.fire({ views, from, to }); - - // Need to skip when syncing multiple container movements - vscode#148363 - if (!skipDiskWrite) { - this.saveViewContainerLocationsToCache(); - } - } - } - moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation, requestedIndex?: number): void { - this.doMoveViewContainerToLocation(viewContainer, location, requestedIndex); + this.moveViewContainerToLocationWithoutSaving(viewContainer, location, requestedIndex); + this.saveViewCustomizations(); } + getViewContainerBadgeEnablementState(id: string): boolean { + return this.viewContainerBadgeEnablementStates.get(id) ?? true; + } + + setViewContainerBadgeEnablementState(id: string, badgesEnabled: boolean): void { + this.viewContainerBadgeEnablementStates.set(id, badgesEnabled); + this.saveViewCustomizations(); + } moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void { const container = this.registerGeneratedViewContainer(location); @@ -359,58 +326,59 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const to = viewContainer; if (from && to && from !== to) { - this.moveViews(views, from, to, visibilityState); - this.cleanUpViewContainer(from.id); + // Move views + this.moveViewsWithoutSaving(views, from, to, visibilityState); + this.cleanUpGeneratedViewContainer(from.id); + + // Save new locations + this.saveViewCustomizations(); + + // Log to telemetry + this.reportMovedViews(views, from, to); } } reset(): void { - this.viewContainers.forEach(viewContainer => { + for (const viewContainer of this.viewContainers) { const viewContainerModel = this.getViewContainerModel(viewContainer); - viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { + for (const viewDescriptor of viewContainerModel.allViewDescriptors) { const defaultContainer = this.getDefaultContainerById(viewDescriptor.id); const currentContainer = this.getViewContainerByViewId(viewDescriptor.id); - if (currentContainer && defaultContainer && currentContainer !== defaultContainer) { - this.moveViews([viewDescriptor], currentContainer, defaultContainer); + this.moveViewsWithoutSaving([viewDescriptor], currentContainer, defaultContainer); } - }); + } const defaultContainerLocation = this.getDefaultViewContainerLocation(viewContainer); const currentContainerLocation = this.getViewContainerLocation(viewContainer); if (defaultContainerLocation !== null && currentContainerLocation !== defaultContainerLocation) { - this.moveViewContainerToLocation(viewContainer, defaultContainerLocation); + this.moveViewContainerToLocationWithoutSaving(viewContainer, defaultContainerLocation); } - this.cleanUpViewContainer(viewContainer.id); - }); + this.cleanUpGeneratedViewContainer(viewContainer.id); + } - this.cachedViewContainerInfo.clear(); - this.saveViewContainerLocationsToCache(); - this.cachedViewInfo.clear(); - this.saveViewPositionsToCache(); + this.viewContainersCustomLocations.clear(); + this.viewDescriptorsCustomLocations.clear(); + this.saveViewCustomizations(); } isViewContainerRemovedPermanently(viewContainerId: string): boolean { - return this.isGeneratedContainerId(viewContainerId) && !this.cachedViewContainerInfo.has(viewContainerId); + return this.isGeneratedContainerId(viewContainerId) && !this.viewContainersCustomLocations.has(viewContainerId); } - private moveViews(views: IViewDescriptor[], from: ViewContainer, to: ViewContainer, visibilityState: ViewVisibilityState = ViewVisibilityState.Expand): void { - this.removeViews(from, views); - this.addViews(to, views, visibilityState); - - const oldLocation = this.getViewContainerLocation(from); - const newLocation = this.getViewContainerLocation(to); - - if (oldLocation !== newLocation) { - this._onDidChangeLocation.fire({ views, from: oldLocation, to: newLocation }); + private onDidChangeDefaultContainer(views: IViewDescriptor[], from: ViewContainer, to: ViewContainer): void { + const viewsToMove = views.filter(view => + !this.viewDescriptorsCustomLocations.has(view.id) // Move views which are not already moved + || (!this.viewContainers.includes(from) && this.viewDescriptorsCustomLocations.get(view.id) === from.id) // Move views which are moved from a removed container + ); + if (viewsToMove.length) { + this.moveViewsWithoutSaving(viewsToMove, from, to); } + } - this._onDidChangeContainer.fire({ views, from, to }); - - this.saveViewPositionsToCache(); - + private reportMovedViews(views: IViewDescriptor[], from: ViewContainer, to: ViewContainer): void { const containerToString = (container: ViewContainer): string => { if (container.id.startsWith(ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX)) { return 'custom'; @@ -423,7 +391,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return 'extension'; }; - // Log on cache update to avoid duplicate events in other windows + const oldLocation = this.getViewContainerLocation(from); + const newLocation = this.getViewContainerLocation(to); const viewCount = views.length; const fromContainer = containerToString(from); const toContainer = containerToString(to); @@ -451,7 +420,42 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.telemetryService.publicLog2('viewDescriptorService.moveViews', { viewCount, fromContainer, toContainer, fromLocation, toLocation }); } - private cleanUpViewContainer(viewContainerId: string): void { + private moveViewsWithoutSaving(views: IViewDescriptor[], from: ViewContainer, to: ViewContainer, visibilityState: ViewVisibilityState = ViewVisibilityState.Expand): void { + this.removeViews(from, views); + this.addViews(to, views, visibilityState); + + const oldLocation = this.getViewContainerLocation(from); + const newLocation = this.getViewContainerLocation(to); + + if (oldLocation !== newLocation) { + this._onDidChangeLocation.fire({ views, from: oldLocation, to: newLocation }); + } + + this._onDidChangeContainer.fire({ views, from, to }); + } + + private moveViewContainerToLocationWithoutSaving(viewContainer: ViewContainer, location: ViewContainerLocation, requestedIndex?: number): void { + const from = this.getViewContainerLocation(viewContainer); + const to = location; + if (from !== to) { + const isGeneratedViewContainer = this.isGeneratedContainerId(viewContainer.id); + const isDefaultViewContainerLocation = to === this.getDefaultViewContainerLocation(viewContainer); + if (isGeneratedViewContainer || !isDefaultViewContainerLocation) { + this.viewContainersCustomLocations.set(viewContainer.id, to); + } else { + this.viewContainersCustomLocations.delete(viewContainer.id); + } + this.getOrCreateDefaultViewContainerLocationContextKey(viewContainer).set(isGeneratedViewContainer || isDefaultViewContainerLocation); + + viewContainer.requestedIndex = requestedIndex; + this._onDidChangeContainerLocation.fire({ viewContainer, from, to }); + + const views = this.getViewsByContainer(viewContainer); + this._onDidChangeLocation.fire({ views, from, to }); + } + } + + private cleanUpGeneratedViewContainer(viewContainerId: string): void { // Skip if container is not generated if (!this.isGeneratedContainerId(viewContainerId)) { return; @@ -463,8 +467,8 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return; } - // Skip if container has views in the cache - if ([...this.cachedViewInfo.values()].some(({ containerId }) => containerId === viewContainerId)) { + // Skip if container has moved views + if ([...this.viewDescriptorsCustomLocations.values()].includes(viewContainerId)) { return; } @@ -473,9 +477,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor this.viewContainersRegistry.deregisterViewContainer(viewContainer); } + this.viewContainersCustomLocations.delete(viewContainerId); + this.viewContainerBadgeEnablementStates.delete(viewContainerId); + // Clean up caches of container - this.cachedViewContainerInfo.delete(viewContainerId); - this.cachedViewContainerLocationsValue = JSON.stringify([...this.cachedViewContainerInfo]); this.storageService.remove(getViewsStateStorageId(viewContainer?.storageId || getViewContainerStorageId(viewContainerId)), StorageScope.PROFILE); } @@ -484,17 +489,15 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor const container = this.viewContainersRegistry.registerViewContainer({ id, - ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]), + ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, { mergeViewWithContainerWhenSingleView: true }]), title: id, // we don't want to see this so using id icon: location === ViewContainerLocation.Sidebar ? defaultViewIcon : undefined, storageId: getViewContainerStorageId(id), hideIfEmpty: true - }, location, { donotRegisterOpenCommand: true }); + }, location, { doNotRegisterOpenCommand: true }); - const cachedInfo = this.cachedViewContainerInfo.get(container.id); - if (cachedInfo !== location) { - this.cachedViewContainerInfo.set(container.id, location); - this.saveViewContainerLocationsToCache(); + if (this.viewContainersCustomLocations.get(container.id) !== location) { + this.viewContainersCustomLocations.set(container.id, location); } this.getOrCreateDefaultViewContainerLocationContextKey(container).set(true); @@ -502,126 +505,80 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return container; } - private getCachedViewPositions(): Map { - const result = new Map(JSON.parse(this.cachedViewPositionsValue)); - - // Sanitize cache - for (const [viewId, containerInfo] of result.entries()) { - if (!containerInfo) { - result.delete(viewId); - continue; - } - - // Verify a view that is in a generated has cached container info - const generated = this.isGeneratedContainerId(containerInfo.containerId); - const missingCacheData = this.cachedViewContainerInfo.get(containerInfo.containerId) === undefined; - if (generated && missingCacheData) { - result.delete(viewId); - } - } - - return result; - } - - private getCachedViewContainerLocations(): Map { - return new Map(JSON.parse(this.cachedViewContainerLocationsValue)); - } - private onDidStorageChange(e: IStorageValueChangeEvent): void { - if (e.key === ViewDescriptorService.CACHED_VIEW_POSITIONS && e.scope === StorageScope.PROFILE - && this.cachedViewPositionsValue !== this.getStoredCachedViewPositionsValue() /* This checks if current window changed the value or not */) { - this.onDidCachedViewPositionsStorageChange(); - } - - if (e.key === ViewDescriptorService.CACHED_VIEW_CONTAINER_LOCATIONS && e.scope === StorageScope.PROFILE - && this.cachedViewContainerLocationsValue !== this.getStoredCachedViewContainerLocationsValue() /* This checks if current window changed the value or not */) { - this.onDidCachedViewContainerLocationsStorageChange(); + if (e.key === ViewDescriptorService.VIEWS_CUSTOMIZATIONS && e.scope === StorageScope.PROFILE + && JSON.stringify(this.viewCustomizations) !== this.getStoredViewCustomizationsValue() /* This checks if current window changed the value or not */) { + this.onDidViewCustomizationsStorageChange(); } } - private onDidCachedViewPositionsStorageChange(): void { - this._cachedViewPositionsValue = this.getStoredCachedViewPositionsValue(); + private onDidViewCustomizationsStorageChange(): void { + this._viewCustomizations = undefined; - const newCachedPositions = this.getCachedViewPositions(); + const newViewContainerCustomizations = new Map(Object.entries(this.viewCustomizations.viewContainerLocations)); + const newViewDescriptorCustomizations = new Map(Object.entries(this.viewCustomizations.viewLocations)); + const viewContainersToMove: [ViewContainer, ViewContainerLocation][] = []; const viewsToMove: { views: IViewDescriptor[]; from: ViewContainer; to: ViewContainer }[] = []; - for (const viewId of newCachedPositions.keys()) { - const viewDescriptor = this.getViewDescriptorById(viewId); - if (!viewDescriptor) { - continue; - } - - const prevViewContainer = this.getViewContainerByViewId(viewId); - const newViewContainerInfo = newCachedPositions.get(viewId)!; - // Verify if we need to create the destination container - if (!this.viewContainersRegistry.get(newViewContainerInfo.containerId)) { - const location = this.cachedViewContainerInfo.get(newViewContainerInfo.containerId); - if (location !== undefined) { - this.registerGeneratedViewContainer(location, newViewContainerInfo.containerId); - } - } - - // Try moving to the new container - const newViewContainer = this.viewContainersRegistry.get(newViewContainerInfo.containerId); - if (prevViewContainer && newViewContainer && newViewContainer !== prevViewContainer) { - const viewDescriptor = this.getViewDescriptorById(viewId); - if (viewDescriptor) { - viewsToMove.push({ views: [viewDescriptor], from: prevViewContainer, to: newViewContainer }); - } - } - } - - // If a value is not present in the cache, it must be reset to default - this.viewContainers.forEach(viewContainer => { - const viewContainerModel = this.getViewContainerModel(viewContainer); - viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { - if (!newCachedPositions.has(viewDescriptor.id)) { - const currentContainer = this.getViewContainerByViewId(viewDescriptor.id); - const defaultContainer = this.getDefaultContainerById(viewDescriptor.id); - if (currentContainer && defaultContainer && currentContainer !== defaultContainer) { - viewsToMove.push({ views: [viewDescriptor], from: currentContainer, to: defaultContainer }); - } - } - }); - }); - - this.cachedViewInfo = newCachedPositions; - for (const { views, from, to } of viewsToMove) { - this.moveViews(views, from, to); - } - } - - private onDidCachedViewContainerLocationsStorageChange(): void { - this._cachedViewContainerLocationsValue = this.getStoredCachedViewContainerLocationsValue(); - const newCachedLocations = this.getCachedViewContainerLocations(); - const viewContainersToMove: [ViewContainer, ViewContainerLocation][] = []; - - for (const [containerId, location] of newCachedLocations.entries()) { + for (const [containerId, location] of newViewContainerCustomizations.entries()) { const container = this.getViewContainerById(containerId); if (container) { if (location !== this.getViewContainerLocation(container)) { viewContainersToMove.push([container, location]); } } + // If the container is generated and not registered, we register it now + else if (this.isGeneratedContainerId(containerId)) { + this.registerGeneratedViewContainer(location, containerId); + } } - this.viewContainers.forEach(viewContainer => { - if (!newCachedLocations.has(viewContainer.id)) { + for (const viewContainer of this.viewContainers) { + if (!newViewContainerCustomizations.has(viewContainer.id)) { const currentLocation = this.getViewContainerLocation(viewContainer); const defaultLocation = this.getDefaultViewContainerLocation(viewContainer); - if (currentLocation !== defaultLocation) { viewContainersToMove.push([viewContainer, defaultLocation]); } } - }); - - // Execute View Container Movement - for (const [container, location] of viewContainersToMove) { - this.doMoveViewContainerToLocation(container, location, undefined, true); } - this.cachedViewContainerInfo = this.getCachedViewContainerLocations(); + for (const [viewId, viewContainerId] of newViewDescriptorCustomizations.entries()) { + const viewDescriptor = this.getViewDescriptorById(viewId); + if (viewDescriptor) { + const prevViewContainer = this.getViewContainerByViewId(viewId); + const newViewContainer = this.viewContainersRegistry.get(viewContainerId); + if (prevViewContainer && newViewContainer && newViewContainer !== prevViewContainer) { + viewsToMove.push({ views: [viewDescriptor], from: prevViewContainer, to: newViewContainer }); + } + } + } + + // If a value is not present in the cache, it must be reset to default + for (const viewContainer of this.viewContainers) { + const viewContainerModel = this.getViewContainerModel(viewContainer); + for (const viewDescriptor of viewContainerModel.allViewDescriptors) { + if (!newViewDescriptorCustomizations.has(viewDescriptor.id)) { + const currentContainer = this.getViewContainerByViewId(viewDescriptor.id); + const defaultContainer = this.getDefaultContainerById(viewDescriptor.id); + if (currentContainer && defaultContainer && currentContainer !== defaultContainer) { + viewsToMove.push({ views: [viewDescriptor], from: currentContainer, to: defaultContainer }); + } + } + } + } + + // Execute View Container Movements + for (const [container, location] of viewContainersToMove) { + this.moveViewContainerToLocationWithoutSaving(container, location); + } + // Execute View Movements + for (const { views, from, to } of viewsToMove) { + this.moveViewsWithoutSaving(views, from, to, ViewVisibilityState.Default); + } + + this.viewContainersCustomLocations = newViewContainerCustomizations; + this.viewDescriptorsCustomLocations = newViewDescriptorCustomizations; } // Generated Container Id Format @@ -632,64 +589,75 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return `${ViewDescriptorService.COMMON_CONTAINER_ID_PREFIX}.${ViewContainerLocationToString(location)}.${generateUuid()}`; } - private getStoredCachedViewPositionsValue(): string { - return this.storageService.get(ViewDescriptorService.CACHED_VIEW_POSITIONS, StorageScope.PROFILE, '[]'); - } + private saveViewCustomizations(): void { + const viewCustomizations: IViewsCustomizations = { viewContainerLocations: {}, viewLocations: {}, viewContainerBadgeEnablementStates: {} }; - private setStoredCachedViewPositionsValue(value: string): void { - this.storageService.store(ViewDescriptorService.CACHED_VIEW_POSITIONS, value, StorageScope.PROFILE, StorageTarget.USER); - } - - private getStoredCachedViewContainerLocationsValue(): string { - return this.storageService.get(ViewDescriptorService.CACHED_VIEW_CONTAINER_LOCATIONS, StorageScope.PROFILE, '[]'); - } - - private setStoredCachedViewContainerLocationsValue(value: string): void { - this.storageService.store(ViewDescriptorService.CACHED_VIEW_CONTAINER_LOCATIONS, value, StorageScope.PROFILE, StorageTarget.USER); - } - - private saveViewPositionsToCache(): void { - this.viewContainers.forEach(viewContainer => { - const viewContainerModel = this.getViewContainerModel(viewContainer); - viewContainerModel.allViewDescriptors.forEach(viewDescriptor => { - this.cachedViewInfo.set(viewDescriptor.id, { - containerId: viewContainer.id - }); - }); - }); - - // Do no save default positions to the cache - // so that default changes can be recognized - // https://github.com/microsoft/vscode/issues/90414 - for (const [viewId, containerInfo] of this.cachedViewInfo) { - const defaultContainer = this.getDefaultContainerById(viewId); - if (defaultContainer?.id === containerInfo.containerId) { - this.cachedViewInfo.delete(viewId); - } - } - - this.cachedViewPositionsValue = JSON.stringify([...this.cachedViewInfo]); - } - - private saveViewContainerLocationsToCache(): void { - for (const [containerId, location] of this.cachedViewContainerInfo) { + for (const [containerId, location] of this.viewContainersCustomLocations) { const container = this.getViewContainerById(containerId); - if (container && location === this.getDefaultViewContainerLocation(container) && !this.isGeneratedContainerId(containerId)) { - this.cachedViewContainerInfo.delete(containerId); + // Skip if the view container is not a generated container and in default location + if (container && !this.isGeneratedContainerId(containerId) && location === this.getDefaultViewContainerLocation(container)) { + continue; } + viewCustomizations.viewContainerLocations[containerId] = location; } - this.cachedViewContainerLocationsValue = JSON.stringify([...this.cachedViewContainerInfo]); + for (const [viewId, viewContainerId] of this.viewDescriptorsCustomLocations) { + const viewContainer = this.getViewContainerById(viewContainerId); + if (viewContainer) { + const defaultContainer = this.getDefaultContainerById(viewId); + // Skip if the view is at default location + // https://github.com/microsoft/vscode/issues/90414 + if (defaultContainer?.id === viewContainer.id) { + continue; + } + } + viewCustomizations.viewLocations[viewId] = viewContainerId; + } + + // Loop through viewContainerBadgeEnablementStates and save only the ones that are disabled + for (const [viewContainerId, badgeEnablementState] of this.viewContainerBadgeEnablementStates) { + if (badgeEnablementState === false) { + viewCustomizations.viewContainerBadgeEnablementStates[viewContainerId] = badgeEnablementState; + } + } + this.viewCustomizations = viewCustomizations; + } + + private _viewCustomizations: IViewsCustomizations | undefined; + private get viewCustomizations(): IViewsCustomizations { + if (!this._viewCustomizations) { + this._viewCustomizations = JSON.parse(this.getStoredViewCustomizationsValue()) as IViewsCustomizations; + this._viewCustomizations.viewContainerLocations = this._viewCustomizations.viewContainerLocations ?? {}; + this._viewCustomizations.viewLocations = this._viewCustomizations.viewLocations ?? {}; + this._viewCustomizations.viewContainerBadgeEnablementStates = this._viewCustomizations.viewContainerBadgeEnablementStates ?? {}; + } + return this._viewCustomizations; + } + + private set viewCustomizations(viewCustomizations: IViewsCustomizations) { + const value = JSON.stringify(viewCustomizations); + if (JSON.stringify(this.viewCustomizations) !== value) { + this._viewCustomizations = viewCustomizations; + this.setStoredViewCustomizationsValue(value); + } + } + + private getStoredViewCustomizationsValue(): string { + return this.storageService.get(ViewDescriptorService.VIEWS_CUSTOMIZATIONS, StorageScope.PROFILE, '{}'); + } + + private setStoredViewCustomizationsValue(value: string): void { + this.storageService.store(ViewDescriptorService.VIEWS_CUSTOMIZATIONS, value, StorageScope.PROFILE, StorageTarget.USER); } private getViewsByContainer(viewContainer: ViewContainer): IViewDescriptor[] { const result = this.viewsRegistry.getViews(viewContainer).filter(viewDescriptor => { - const cachedContainer = this.cachedViewInfo.get(viewDescriptor.id)?.containerId || viewContainer.id; - return cachedContainer === viewContainer.id; + const viewDescriptorViewContainerId = this.viewDescriptorsCustomLocations.get(viewDescriptor.id) ?? viewContainer.id; + return viewDescriptorViewContainerId === viewContainer.id; }); - for (const [viewId, containerInfo] of this.cachedViewInfo.entries()) { - if (!containerInfo || containerInfo.containerId !== viewContainer.id) { + for (const [viewId, viewContainerId] of this.viewDescriptorsCustomLocations.entries()) { + if (viewContainerId !== viewContainer.id) { continue; } @@ -726,21 +694,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor viewContainerModel.onDidAddVisibleViewDescriptors(added => this.onDidChangeVisibleViews({ added: added.map(({ viewDescriptor }) => viewDescriptor), removed: [] }), this, disposables); viewContainerModel.onDidRemoveVisibleViewDescriptors(removed => this.onDidChangeVisibleViews({ added: [], removed: removed.map(({ viewDescriptor }) => viewDescriptor) }), this, disposables); - this.registerViewsVisibilityActions(viewContainerModel); - disposables.add(Event.any( - viewContainerModel.onDidChangeActiveViewDescriptors, - viewContainerModel.onDidAddVisibleViewDescriptors, - viewContainerModel.onDidRemoveVisibleViewDescriptors, - viewContainerModel.onDidMoveVisibleViewDescriptors - )(e => this.registerViewsVisibilityActions(viewContainerModel!))); - disposables.add(toDisposable(() => { - this.viewsVisibilityActionDisposables.get(viewContainer)?.dispose(); - this.viewsVisibilityActionDisposables.delete(viewContainer); - })); + disposables.add(toDisposable(() => this.viewsVisibilityActionDisposables.deleteAndDispose(viewContainer))); disposables.add(this.registerResetViewContainerAction(viewContainer)); - this.viewContainerModels.set(viewContainer, { viewContainerModel: viewContainerModel, disposable: disposables }); + this.viewContainerModels.set(viewContainer, { viewContainerModel: viewContainerModel, disposables, dispose: () => disposables.dispose() }); // Register all views that were statically registered to this container // Potentially, this is registering something that was handled by another container @@ -761,11 +719,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private onDidDeregisterViewContainer(viewContainer: ViewContainer): void { - const viewContainerModelItem = this.viewContainerModels.get(viewContainer); - if (viewContainerModelItem) { - viewContainerModelItem.disposable.dispose(); - this.viewContainerModels.delete(viewContainer); - } + this.viewContainerModels.deleteAndDispose(viewContainer); } private onDidChangeActiveViews({ added, removed }: { added: ReadonlyArray; removed: ReadonlyArray }): void { @@ -782,16 +736,23 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor }); } - private registerViewsVisibilityActions(viewContainerModel: ViewContainerModel): void { - let disposables = this.viewsVisibilityActionDisposables.get(viewContainerModel.viewContainer); - if (!disposables) { - disposables = new DisposableStore(); - this.viewsVisibilityActionDisposables.set(viewContainerModel.viewContainer, disposables); + private registerViewsVisibilityActions(): void { + for (const [viewContainer, { viewContainerModel, disposables }] of this.viewContainerModels) { + this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)); + disposables.add(Event.any( + viewContainerModel.onDidChangeActiveViewDescriptors, + viewContainerModel.onDidAddVisibleViewDescriptors, + viewContainerModel.onDidRemoveVisibleViewDescriptors, + viewContainerModel.onDidMoveVisibleViewDescriptors + )(e => this.viewsVisibilityActionDisposables.set(viewContainer, this.registerViewsVisibilityActionsForContainer(viewContainerModel)))); } - disposables.clear(); + } + + private registerViewsVisibilityActionsForContainer(viewContainerModel: ViewContainerModel): IDisposable { + const disposables = new DisposableStore(); viewContainerModel.activeViewDescriptors.forEach((viewDescriptor, index) => { if (!viewDescriptor.remoteAuthority) { - disposables?.add(registerAction2(class extends ViewPaneContainerAction { + disposables.add(registerAction2(class extends ViewPaneContainerAction { constructor() { super({ id: `${viewDescriptor.id}.toggleVisibility`, @@ -828,7 +789,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor viewPaneContainer.toggleViewVisibility(viewDescriptor.id); } })); - disposables?.add(registerAction2(class extends ViewPaneContainerAction { + disposables.add(registerAction2(class extends ViewPaneContainerAction { constructor() { super({ id: `${viewDescriptor.id}.removeView`, @@ -852,6 +813,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor })); } }); + return disposables; } private registerResetViewContainerAction(viewContainer: ViewContainer): IDisposable { @@ -882,11 +844,15 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } private addViews(container: ViewContainer, views: IViewDescriptor[], visibilityState: ViewVisibilityState = ViewVisibilityState.Default): void { - // Update in memory cache this.contextKeyService.bufferChangeEvents(() => { views.forEach(view => { - this.cachedViewInfo.set(view.id, { containerId: container.id }); - this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainerById(view.id) === container); + const isDefaultContainer = this.getDefaultContainerById(view.id) === container; + this.getOrCreateDefaultViewLocationContextKey(view).set(isDefaultContainer); + if (isDefaultContainer) { + this.viewDescriptorsCustomLocations.delete(view.id); + } else { + this.viewDescriptorsCustomLocations.set(view.id, container.id); + } }); }); @@ -902,7 +868,12 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor private removeViews(container: ViewContainer, views: IViewDescriptor[]): void { // Set view default location keys to false this.contextKeyService.bufferChangeEvents(() => { - views.forEach(view => this.getOrCreateDefaultViewLocationContextKey(view).set(false)); + views.forEach(view => { + if (this.viewDescriptorsCustomLocations.get(view.id) === container.id) { + this.viewDescriptorsCustomLocations.delete(view.id); + } + this.getOrCreateDefaultViewLocationContextKey(view).set(false); + }); }); // Remove the views @@ -960,4 +931,4 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } } -registerSingleton(IViewDescriptorService, ViewDescriptorService); +registerSingleton(IViewDescriptorService, ViewDescriptorService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/views/common/treeViewsService.ts b/src/vs/workbench/services/views/common/treeViewsService.ts index 3b34bb3733..22fd843af7 100644 --- a/src/vs/workbench/services/views/common/treeViewsService.ts +++ b/src/vs/workbench/services/views/common/treeViewsService.ts @@ -3,36 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export interface ITreeViewsService { +export interface ITreeViewsService { readonly _serviceBrand: undefined; - removeDragOperationTransfer(uuid: string | undefined): Promise | undefined; - addDragOperationTransfer(uuid: string, transferPromise: Promise): void; - getRenderedTreeElement(node: U): V | undefined; addRenderedTreeItemElement(node: U, element: V): void; removeRenderedTreeItemElement(node: U): void; } -export class TreeviewsService implements ITreeViewsService { +export class TreeviewsService implements ITreeViewsService { _serviceBrand: undefined; - private _dragOperations: Map> = new Map(); private _renderedElements: Map = new Map(); - removeDragOperationTransfer(uuid: string | undefined): Promise | undefined { - if ((uuid && this._dragOperations.has(uuid))) { - const operation = this._dragOperations.get(uuid); - this._dragOperations.delete(uuid); - return operation; - } - return undefined; - } - - addDragOperationTransfer(uuid: string, transferPromise: Promise): void { - this._dragOperations.set(uuid, transferPromise); - } - - getRenderedTreeElement(node: U): V | undefined { if (this._renderedElements.has(node)) { return this._renderedElements.get(node); diff --git a/src/vs/workbench/services/views/common/viewContainerModel.ts b/src/vs/workbench/services/views/common/viewContainerModel.ts index 60819521cc..05b47e21b4 100644 --- a/src/vs/workbench/services/views/common/viewContainerModel.ts +++ b/src/vs/workbench/services/views/common/viewContainerModel.ts @@ -4,55 +4,51 @@ *--------------------------------------------------------------------------------------------*/ import { ViewContainer, IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions, IViewContainerModel, IAddedViewDescriptorRef, IViewDescriptorRef, IAddedViewDescriptorState, defaultViewIcon } from 'vs/workbench/common/views'; -import { IContextKeyService, IReadableSet } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { coalesce, move } from 'vs/base/common/arrays'; import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types'; -import { isEqual } from 'vs/base/common/resources'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { isEqual, joinPath } from 'vs/base/common/resources'; +import { ThemeIcon } from 'vs/base/common/themables'; import { IStringDictionary } from 'vs/base/common/collections'; -import { Extensions, IProfileStorageRegistry } from 'vs/workbench/services/userDataProfile/common/userDataProfileStorageRegistry'; import { localize } from 'vs/nls'; +import { ILogger, ILoggerService } from 'vs/platform/log/common/log'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { IOutputService } from 'vs/workbench/services/output/common/output'; +import { CounterSet } from 'vs/base/common/map'; + +const VIEWS_LOG_ID = 'viewsLog'; +const VIEWS_LOG_NAME = localize('views log', "Views"); +function getViewsLogFile(environmentService: IWorkbenchEnvironmentService): URI { return joinPath(environmentService.windowLogsPath, 'views.log'); } + +registerAction2(class extends Action2 { + constructor() { + super({ + id: '_workbench.output.showViewsLog', + title: { value: 'Show Views Log', original: 'Show Views Log' }, + category: Categories.Developer, + f1: true + }); + } + async run(servicesAccessor: ServicesAccessor): Promise { + const loggerService = servicesAccessor.get(ILoggerService); + const outputService = servicesAccessor.get(IOutputService); + const environmentService = servicesAccessor.get(IWorkbenchEnvironmentService); + loggerService.setVisibility(getViewsLogFile(environmentService), true); + outputService.showChannel(VIEWS_LOG_ID); + + } +}); export function getViewsStateStorageId(viewContainerStorageId: string): string { return `${viewContainerStorageId}.hidden`; } -class CounterSet implements IReadableSet { - - private map = new Map(); - - add(value: T): CounterSet { - this.map.set(value, (this.map.get(value) || 0) + 1); - return this; - } - - delete(value: T): boolean { - let counter = this.map.get(value) || 0; - - if (counter === 0) { - return false; - } - - counter--; - - if (counter === 0) { - this.map.delete(value); - } else { - this.map.set(value, counter); - } - - return true; - } - - has(value: T): boolean { - return this.map.has(value); - } -} - interface IStoredWorkspaceViewState { collapsed: boolean; isHidden: boolean; @@ -84,24 +80,25 @@ class ViewDescriptorsState extends Disposable { private _onDidChangeStoredState = this._register(new Emitter<{ id: string; visible: boolean }[]>()); readonly onDidChangeStoredState = this._onDidChangeStoredState.event; + private readonly logger: ILogger; + constructor( viewContainerStorageId: string, - viewContainerName: string, + private readonly viewContainerName: string, @IStorageService private readonly storageService: IStorageService, + @ILoggerService loggerService: ILoggerService, + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, ) { super(); + this.logger = loggerService.createLogger(getViewsLogFile(workbenchEnvironmentService), { id: VIEWS_LOG_ID, name: VIEWS_LOG_NAME, hidden: true }); + this.globalViewsStateStorageId = getViewsStateStorageId(viewContainerStorageId); this.workspaceViewsStateStorageId = viewContainerStorageId; this._register(this.storageService.onDidChangeValue(e => this.onDidStorageChange(e))); this.state = this.initialize(); - Registry.as(Extensions.ProfileStorageRegistry) - .registerKeys([{ - key: this.globalViewsStateStorageId, - description: localize('globalViewsStateStorageId', "Views visibility customizations in {0} view container", viewContainerName), - }]); } set(id: string, state: IViewDescriptorState): void { @@ -162,6 +159,9 @@ class ViewDescriptorsState extends Disposable { const state = this.get(id); if (state) { if (state.visibleGlobal !== !storedState.isHidden) { + if (!storedState.isHidden) { + this.logger.info(`View visibility state changed: ${id} is now visible`, this.viewContainerName); + } changedStates.push({ id, visible: !storedState.isHidden }); } } else { @@ -178,6 +178,14 @@ class ViewDescriptorsState extends Disposable { } if (changedStates.length) { this._onDidChangeStoredState.fire(changedStates); + // Update the in memory state after firing the event + // so that the views can update their state accordingly + for (const changedState of changedStates) { + const state = this.get(changedState.id); + if (state) { + state.visibleGlobal = changedState.visible; + } + } } } } @@ -344,25 +352,22 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode private _onDidMoveVisibleViewDescriptors = this._register(new Emitter<{ from: IViewDescriptorRef; to: IViewDescriptorRef }>()); readonly onDidMoveVisibleViewDescriptors: Event<{ from: IViewDescriptorRef; to: IViewDescriptorRef }> = this._onDidMoveVisibleViewDescriptors.event; + private readonly logger: ILogger; + constructor( readonly viewContainer: ViewContainer, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, + @ILoggerService loggerService: ILoggerService, + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, ) { super(); - this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(() => this.onDidChangeContext())); - this.viewDescriptorsState = this._register(instantiationService.createInstance(ViewDescriptorsState, viewContainer.storageId || `${viewContainer.id}.state`, viewContainer.title)); - this._register(this.viewDescriptorsState.onDidChangeStoredState(items => this.updateVisibility(items))); + this.logger = loggerService.createLogger(getViewsLogFile(workbenchEnvironmentService), { id: VIEWS_LOG_ID, name: VIEWS_LOG_NAME, hidden: true }); - this._register(Event.any( - this.onDidAddVisibleViewDescriptors, - this.onDidRemoveVisibleViewDescriptors, - this.onDidMoveVisibleViewDescriptors) - (() => { - this.viewDescriptorsState.updateState(this.allViewDescriptors); - this.updateContainerInfo(); - })); + this._register(Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys))(() => this.onDidChangeContext())); + this.viewDescriptorsState = this._register(instantiationService.createInstance(ViewDescriptorsState, viewContainer.storageId || `${viewContainer.id}.state`, typeof viewContainer.title === 'string' ? viewContainer.title : viewContainer.title.original)); + this._register(this.viewDescriptorsState.onDidChangeStoredState(items => this.updateVisibility(items))); this.updateContainerInfo(); } @@ -370,7 +375,7 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode private updateContainerInfo(): void { /* Use default container info if one of the visible view descriptors belongs to the current container by default */ const useDefaultContainerInfo = this.viewContainer.alwaysUseContainerInfo || this.visibleViewDescriptors.length === 0 || this.visibleViewDescriptors.some(v => Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(v.id) === this.viewContainer); - const title = useDefaultContainerInfo ? this.viewContainer.title : this.visibleViewDescriptors[0]?.containerTitle || this.visibleViewDescriptors[0]?.name || ''; + const title = useDefaultContainerInfo ? (typeof this.viewContainer.title === 'string' ? this.viewContainer.title : this.viewContainer.title.value) : this.visibleViewDescriptors[0]?.containerTitle || this.visibleViewDescriptors[0]?.name || ''; let titleChanged: boolean = false; if (this._title !== title) { this._title = title; @@ -464,6 +469,9 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode viewDescriptorItem.state.visibleWorkspace = visible; } else { viewDescriptorItem.state.visibleGlobal = visible; + if (visible) { + this.logger.info(`Showing view ${viewDescriptorItem.viewDescriptor.id} in the container ${this.viewContainer.id}`); + } } // return `true` only if visibility is changed @@ -509,10 +517,7 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode this.viewDescriptorItems[index].state.order = index; } - this._onDidMoveVisibleViewDescriptors.fire({ - from: { index: fromIndex, viewDescriptor: fromViewDescriptor.viewDescriptor }, - to: { index: toIndex, viewDescriptor: toViewDescriptor.viewDescriptor } - }); + this.broadCastMovedViewDescriptors({ index: fromIndex, viewDescriptor: fromViewDescriptor.viewDescriptor }, { index: toIndex, viewDescriptor: toViewDescriptor.viewDescriptor }); } add(addedViewDescriptorStates: IAddedViewDescriptorState[]): void { @@ -532,7 +537,11 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode if (viewDescriptor.workspace) { state.visibleWorkspace = isUndefinedOrNull(addedViewDescriptorState.visible) ? (isUndefinedOrNull(state.visibleWorkspace) ? !viewDescriptor.hideByDefault : state.visibleWorkspace) : addedViewDescriptorState.visible; } else { + const isVisible = state.visibleGlobal; state.visibleGlobal = isUndefinedOrNull(addedViewDescriptorState.visible) ? (isUndefinedOrNull(state.visibleGlobal) ? !viewDescriptor.hideByDefault : state.visibleGlobal) : addedViewDescriptorState.visible; + if (state.visibleGlobal && !isVisible) { + this.logger.info(`Added view ${viewDescriptor.id} in the container ${this.viewContainer.id} and showing it.`, `${isVisible}`, `${viewDescriptor.hideByDefault}`, `${addedViewDescriptorState.visible}`); + } } state.collapsed = isUndefinedOrNull(addedViewDescriptorState.collapsed) ? (isUndefinedOrNull(state.collapsed) ? !!viewDescriptor.collapsed : state.collapsed) : addedViewDescriptorState.collapsed; } else { @@ -657,15 +666,28 @@ export class ViewContainerModel extends Disposable implements IViewContainerMode private broadCastAddedVisibleViewDescriptors(added: IAddedViewDescriptorRef[]): void { if (added.length) { this._onDidAddVisibleViewDescriptors.fire(added.sort((a, b) => a.index - b.index)); + this.updateState(`Added views:${added.map(v => v.viewDescriptor.id).join(',')} in ${this.viewContainer.id}`); } } private broadCastRemovedVisibleViewDescriptors(removed: IViewDescriptorRef[]): void { if (removed.length) { this._onDidRemoveVisibleViewDescriptors.fire(removed.sort((a, b) => b.index - a.index)); + this.updateState(`Removed views:${removed.map(v => v.viewDescriptor.id).join(',')} from ${this.viewContainer.id}`); } } + private broadCastMovedViewDescriptors(from: IViewDescriptorRef, to: IViewDescriptorRef): void { + this._onDidMoveVisibleViewDescriptors.fire({ from, to }); + this.updateState(`Moved view ${from.viewDescriptor.id} to ${to.viewDescriptor.id} in ${this.viewContainer.id}`); + } + + private updateState(reason: string): void { + this.logger.info(reason); + this.viewDescriptorsState.updateState(this.allViewDescriptors); + this.updateContainerInfo(); + } + private isViewDescriptorVisible(viewDescriptorItem: IViewDescriptorItem): boolean { if (!viewDescriptorItem.state.active) { return false; diff --git a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts index daa981327e..d8850bd0b7 100644 --- a/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts @@ -18,6 +18,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { Event } from 'vs/base/common/event'; import { getViewsStateStorageId } from 'vs/workbench/services/views/common/viewContainerModel'; +import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; const ViewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); @@ -95,7 +96,7 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements.length, 0); }); - test('when contexts', async function () { + test('when contexts', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -137,9 +138,9 @@ suite('ViewContainerModel', () => { await new Promise(c => setTimeout(c, 30)); assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should not be there anymore'); assert.strictEqual(target.elements.length, 0); - }); + })); - test('when contexts - multiple', async function () { + test('when contexts - multiple', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -160,9 +161,9 @@ suite('ViewContainerModel', () => { assert.deepStrictEqual(target.elements, [view1, view2], 'both views should be visible'); ViewsRegistry.deregisterViews([view1, view2], container); - }); + })); - test('when contexts - multiple 2', async function () { + test('when contexts - multiple 2', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -183,7 +184,7 @@ suite('ViewContainerModel', () => { assert.deepStrictEqual(target.elements, [view1, view2], 'both views should be visible'); ViewsRegistry.deregisterViews([view1, view2], container); - }); + })); test('setVisible', () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); @@ -259,7 +260,7 @@ suite('ViewContainerModel', () => { assert.deepStrictEqual(target.elements, [view1, view2, view3]); }); - test('view states', async function () { + test('view states', () => runWithFakedTimers({ useFakeTimers: true }, async () => { storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.PROFILE, StorageTarget.MACHINE); container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); @@ -277,9 +278,9 @@ suite('ViewContainerModel', () => { ViewsRegistry.registerViews([viewDescriptor], container); assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should not appear since it was set not visible in view state'); assert.strictEqual(target.elements.length, 0); - }); + })); - test('view states and when contexts', async function () { + test('view states and when contexts', () => runWithFakedTimers({ useFakeTimers: true }, async () => { storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.PROFILE, StorageTarget.MACHINE); container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); @@ -307,9 +308,9 @@ suite('ViewContainerModel', () => { await new Promise(c => setTimeout(c, 30)); assert.strictEqual(testObject.visibleViewDescriptors.length, 0, 'view should still not appear since it was set not visible in view state'); assert.strictEqual(target.elements.length, 0); - }); + })); - test('view states and when contexts multiple views', async function () { + test('view states and when contexts multiple views', () => runWithFakedTimers({ useFakeTimers: true }, async () => { storageService.store(`${container.id}.state.hidden`, JSON.stringify([{ id: 'view1', isHidden: true }]), StorageScope.PROFILE, StorageTarget.MACHINE); container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); @@ -353,9 +354,9 @@ suite('ViewContainerModel', () => { await new Promise(c => setTimeout(c, 30)); assert.deepStrictEqual(testObject.visibleViewDescriptors, [view2], 'Only view2 should be visible'); assert.deepStrictEqual(target.elements, [view2]); - }); + })); - test('remove event is not triggered if view was hidden and removed', async function () { + test('remove event is not triggered if view was hidden and removed', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -383,9 +384,9 @@ suite('ViewContainerModel', () => { key.set(false); await new Promise(c => setTimeout(c, 30)); assert.ok(!targetEvent.called, 'remove event should not be called since it is already hidden'); - }); + })); - test('add event is not triggered if view was set visible (when visible) and not active', async function () { + test('add event is not triggered if view was set visible (when visible) and not active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -410,9 +411,9 @@ suite('ViewContainerModel', () => { assert.ok(!targetEvent.called, 'add event should not be called since it is already visible'); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); assert.strictEqual(target.elements.length, 0); - }); + })); - test('remove event is not triggered if view was hidden and not active', async function () { + test('remove event is not triggered if view was hidden and not active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -437,9 +438,9 @@ suite('ViewContainerModel', () => { assert.ok(!targetEvent.called, 'add event should not be called since it is disabled'); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); assert.strictEqual(target.elements.length, 0); - }); + })); - test('add event is not triggered if view was set visible (when not visible) and not active', async function () { + test('add event is not triggered if view was set visible (when not visible) and not active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -468,9 +469,9 @@ suite('ViewContainerModel', () => { assert.ok(!targetEvent.called, 'add event should not be called since it is disabled'); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); assert.strictEqual(target.elements.length, 0); - }); + })); - test('added view descriptors are in ascending order in the event', async function () { + test('added view descriptors are in ascending order in the event', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -519,9 +520,9 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements[2].id, 'view3'); assert.strictEqual(target.elements[3].id, 'view4'); assert.strictEqual(target.elements[4].id, 'view5'); - }); + })); - test('add event is triggered only once when view is set visible while it is set active', async function () { + test('add event is triggered only once when view is set visible while it is set active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -550,9 +551,9 @@ suite('ViewContainerModel', () => { assert.strictEqual(testObject.visibleViewDescriptors.length, 1); assert.strictEqual(target.elements.length, 1); assert.strictEqual(target.elements[0].id, 'view1'); - }); + })); - test('add event is not triggered only when view is set hidden while it is set active', async function () { + test('add event is not triggered only when view is set hidden while it is set active', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -579,9 +580,9 @@ suite('ViewContainerModel', () => { assert.strictEqual(targetEvent.callCount, 0); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); assert.strictEqual(target.elements.length, 0); - }); + })); - test('#142087: view descriptor visibility is not reset', async function () { + test('#142087: view descriptor visibility is not reset', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const viewDescriptor: IViewDescriptor = { @@ -602,9 +603,9 @@ suite('ViewContainerModel', () => { assert.strictEqual(testObject.isVisible(viewDescriptor.id), false); assert.strictEqual(testObject.activeViewDescriptors[0].id, viewDescriptor.id); assert.strictEqual(testObject.visibleViewDescriptors.length, 0); - }); + })); - test('remove event is triggered properly if mutliple views are hidden at the same time', async function () { + test('remove event is triggered properly if mutliple views are hidden at the same time', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -660,9 +661,9 @@ suite('ViewContainerModel', () => { }]); assert.strictEqual(target.elements.length, 1); assert.strictEqual(target.elements[0].id, viewDescriptor1.id); - }); + })); - test('add event is triggered properly if mutliple views are hidden at the same time', async function () { + test('add event is triggered properly if mutliple views are hidden at the same time', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -728,9 +729,9 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements[0].id, viewDescriptor1.id); assert.strictEqual(target.elements[1].id, viewDescriptor2.id); assert.strictEqual(target.elements[2].id, viewDescriptor3.id); - }); + })); - test('add and remove events are triggered properly if mutliple views are hidden and added at the same time', async function () { + test('add and remove events are triggered properly if mutliple views are hidden and added at the same time', () => runWithFakedTimers({ useFakeTimers: true }, async () => { container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); const testObject = viewDescriptorService.getViewContainerModel(container); const target = disposableStore.add(new ViewDescriptorSequence(testObject)); @@ -805,6 +806,35 @@ suite('ViewContainerModel', () => { assert.strictEqual(target.elements.length, 2); assert.strictEqual(target.elements[0].id, viewDescriptor1.id); assert.strictEqual(target.elements[1].id, viewDescriptor3.id); - }); + })); + + test('newly added view descriptor is hidden if it was toggled hidden in storage before adding', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + container = ViewContainerRegistry.registerViewContainer({ id: 'test', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const viewDescriptor: IViewDescriptor = { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canToggleVisibility: true + }; + storageService.store(getViewsStateStorageId('test.state'), JSON.stringify([{ + id: viewDescriptor.id, + isHidden: false, + order: undefined + }]), StorageScope.PROFILE, StorageTarget.USER); + + const testObject = viewDescriptorService.getViewContainerModel(container); + + storageService.store(getViewsStateStorageId('test.state'), JSON.stringify([{ + id: viewDescriptor.id, + isHidden: true, + order: undefined + }]), StorageScope.PROFILE, StorageTarget.USER); + + ViewsRegistry.registerViews([viewDescriptor], container); + + assert.strictEqual(testObject.isVisible(viewDescriptor.id), false); + assert.strictEqual(testObject.activeViewDescriptors[0].id, viewDescriptor.id); + assert.strictEqual(testObject.visibleViewDescriptors.length, 0); + })); }); diff --git a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts index 2e32f52fa1..a40f649488 100644 --- a/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts +++ b/src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, ViewContainer, ViewContainerLocationToString } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; @@ -14,37 +14,49 @@ import { assertIsDefined } from 'vs/base/common/types'; import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { generateUuid } from 'vs/base/common/uuid'; +import { compare } from 'vs/base/common/strings'; const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); -const sidebarContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testSidebar', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); -const panelContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ id: 'testPanel', title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Panel); +const ViewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); +const viewContainerIdPrefix = 'testViewContainer'; +const sidebarContainer = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); +const panelContainer = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Panel); suite('ViewDescriptorService', () => { - let disposables: DisposableStore; - let viewDescriptorService: IViewDescriptorService; + const disposables = new DisposableStore(); + let instantiationService: TestInstantiationService; setup(() => { - disposables = new DisposableStore(); - const instantiationService: TestInstantiationService = workbenchInstantiationService(undefined, disposables); + instantiationService = workbenchInstantiationService(undefined, disposables); instantiationService.stub(IContextKeyService, instantiationService.createInstance(ContextKeyService)); - viewDescriptorService = instantiationService.createInstance(ViewDescriptorService); }); teardown(() => { - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(sidebarContainer), sidebarContainer); - ViewsRegistry.deregisterViews(ViewsRegistry.getViews(panelContainer), panelContainer); - disposables.dispose(); + for (const viewContainer of ViewContainersRegistry.all) { + if (viewContainer.id.startsWith(viewContainerIdPrefix)) { + ViewsRegistry.deregisterViews(ViewsRegistry.getViews(viewContainer), viewContainer); + } + } + disposables.clear(); }); + function aViewDescriptorService(): ViewDescriptorService { + return disposables.add(instantiationService.createInstance(ViewDescriptorService)); + } + test('Empty Containers', function () { - const sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); - const panelViews = viewDescriptorService.getViewContainerModel(panelContainer); + const testObject = aViewDescriptorService(); + const sidebarViews = testObject.getViewContainerModel(sidebarContainer); + const panelViews = testObject.getViewContainerModel(panelContainer); assert.strictEqual(sidebarViews.allViewDescriptors.length, 0, 'The sidebar container should have no views yet.'); assert.strictEqual(panelViews.allViewDescriptors.length, 0, 'The panel container should have no views yet.'); }); test('Register/Deregister', () => { + const testObject = aViewDescriptorService(); const viewDescriptors: IViewDescriptor[] = [ { id: 'view1', @@ -66,13 +78,11 @@ suite('ViewDescriptorService', () => { } ]; - ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - - let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); - let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); + let sidebarViews = testObject.getViewContainerModel(sidebarContainer); + let panelViews = testObject.getViewContainerModel(panelContainer); assert.strictEqual(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views'); assert.strictEqual(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view'); @@ -80,15 +90,15 @@ suite('ViewDescriptorService', () => { ViewsRegistry.deregisterViews(viewDescriptors.slice(0, 2), sidebarContainer); ViewsRegistry.deregisterViews(viewDescriptors.slice(2), panelContainer); - - sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); - panelViews = viewDescriptorService.getViewContainerModel(panelContainer); + sidebarViews = testObject.getViewContainerModel(sidebarContainer); + panelViews = testObject.getViewContainerModel(panelContainer); assert.strictEqual(sidebarViews.activeViewDescriptors.length, 0, 'Sidebar should have no views'); assert.strictEqual(panelViews.activeViewDescriptors.length, 0, 'Panel should have no views'); }); test('move views to existing containers', async function () { + const testObject = aViewDescriptorService(); const viewDescriptors: IViewDescriptor[] = [ { id: 'view1', @@ -113,11 +123,11 @@ suite('ViewDescriptorService', () => { ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer); - viewDescriptorService.moveViewsToContainer(viewDescriptors.slice(0, 2), panelContainer); + testObject.moveViewsToContainer(viewDescriptors.slice(2), sidebarContainer); + testObject.moveViewsToContainer(viewDescriptors.slice(0, 2), panelContainer); - const sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); - const panelViews = viewDescriptorService.getViewContainerModel(panelContainer); + const sidebarViews = testObject.getViewContainerModel(sidebarContainer); + const panelViews = testObject.getViewContainerModel(panelContainer); assert.strictEqual(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); assert.strictEqual(panelViews.activeViewDescriptors.length, 2, 'Panel should have 1 view'); @@ -128,6 +138,7 @@ suite('ViewDescriptorService', () => { }); test('move views to generated containers', async function () { + const testObject = aViewDescriptorService(); const viewDescriptors: IViewDescriptor[] = [ { id: 'view1', @@ -152,41 +163,42 @@ suite('ViewDescriptorService', () => { ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); + testObject.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); + testObject.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - let sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); - let panelViews = viewDescriptorService.getViewContainerModel(panelContainer); + let sidebarViews = testObject.getViewContainerModel(sidebarContainer); + let panelViews = testObject.getViewContainerModel(panelContainer); assert.strictEqual(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar container should have 1 view'); assert.strictEqual(panelViews.activeViewDescriptors.length, 0, 'Panel container should have no views'); - const generatedPanel = assertIsDefined(viewDescriptorService.getViewContainerByViewId(viewDescriptors[0].id)); - const generatedSidebar = assertIsDefined(viewDescriptorService.getViewContainerByViewId(viewDescriptors[2].id)); + const generatedPanel = assertIsDefined(testObject.getViewContainerByViewId(viewDescriptors[0].id)); + const generatedSidebar = assertIsDefined(testObject.getViewContainerByViewId(viewDescriptors[2].id)); - assert.strictEqual(viewDescriptorService.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); - assert.strictEqual(viewDescriptorService.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); + assert.strictEqual(testObject.getViewContainerLocation(generatedPanel), ViewContainerLocation.Panel, 'Generated Panel should be in located in the panel'); + assert.strictEqual(testObject.getViewContainerLocation(generatedSidebar), ViewContainerLocation.Sidebar, 'Generated Sidebar should be in located in the sidebar'); - assert.strictEqual(viewDescriptorService.getViewContainerLocation(generatedPanel), viewDescriptorService.getViewLocationById(viewDescriptors[0].id), 'Panel view location and container location should match'); - assert.strictEqual(viewDescriptorService.getViewContainerLocation(generatedSidebar), viewDescriptorService.getViewLocationById(viewDescriptors[2].id), 'Sidebar view location and container location should match'); + assert.strictEqual(testObject.getViewContainerLocation(generatedPanel), testObject.getViewLocationById(viewDescriptors[0].id), 'Panel view location and container location should match'); + assert.strictEqual(testObject.getViewContainerLocation(generatedSidebar), testObject.getViewLocationById(viewDescriptors[2].id), 'Sidebar view location and container location should match'); - assert.strictEqual(viewDescriptorService.getDefaultContainerById(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`); - assert.strictEqual(viewDescriptorService.getDefaultContainerById(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`); + assert.strictEqual(testObject.getDefaultContainerById(viewDescriptors[2].id), panelContainer, `${viewDescriptors[2].name} has wrong default container`); + assert.strictEqual(testObject.getDefaultContainerById(viewDescriptors[0].id), sidebarContainer, `${viewDescriptors[0].name} has wrong default container`); - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); + testObject.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar); + testObject.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel); - sidebarViews = viewDescriptorService.getViewContainerModel(sidebarContainer); - panelViews = viewDescriptorService.getViewContainerModel(panelContainer); + sidebarViews = testObject.getViewContainerModel(sidebarContainer); + panelViews = testObject.getViewContainerModel(panelContainer); assert.strictEqual(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views'); assert.strictEqual(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view'); - assert.strictEqual(viewDescriptorService.getViewLocationById(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); - assert.strictEqual(viewDescriptorService.getViewLocationById(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); + assert.strictEqual(testObject.getViewLocationById(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar'); + assert.strictEqual(testObject.getViewLocationById(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel'); }); test('move view events', async function () { + const testObject = aViewDescriptorService(); const viewDescriptors: IViewDescriptor[] = [ { id: 'view1', @@ -208,7 +220,6 @@ suite('ViewDescriptorService', () => { } ]; - let expectedSequence = ''; let actualSequence = ''; const disposables = []; @@ -220,13 +231,13 @@ suite('ViewDescriptorService', () => { const locationMoveString = (view: IViewDescriptor, from: ViewContainerLocation, to: ViewContainerLocation) => { return `Moved ${view.id} from ${from === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'} to ${to === ViewContainerLocation.Sidebar ? 'Sidebar' : 'Panel'}\n`; }; - disposables.push(viewDescriptorService.onDidChangeContainer(({ views, from, to }) => { + disposables.push(testObject.onDidChangeContainer(({ views, from, to }) => { views.forEach(view => { actualSequence += containerMoveString(view, from, to); }); })); - disposables.push(viewDescriptorService.onDidChangeLocation(({ views, from, to }) => { + disposables.push(testObject.onDidChangeLocation(({ views, from, to }) => { views.forEach(view => { actualSequence += locationMoveString(view, from, to); }); @@ -236,37 +247,474 @@ suite('ViewDescriptorService', () => { ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, viewDescriptorService.getViewContainerByViewId(viewDescriptors[0].id)!); + testObject.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); + expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, testObject.getViewContainerByViewId(viewDescriptors[0].id)!); expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); - viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); - expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, viewDescriptorService.getViewContainerByViewId(viewDescriptors[2].id)!); - + testObject.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); + expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, testObject.getViewContainerByViewId(viewDescriptors[2].id)!); expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); - expectedSequence += containerMoveString(viewDescriptors[0], viewDescriptorService.getViewContainerByViewId(viewDescriptors[0].id)!, sidebarContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], sidebarContainer); + expectedSequence += containerMoveString(viewDescriptors[0], testObject.getViewContainerByViewId(viewDescriptors[0].id)!, sidebarContainer); + testObject.moveViewsToContainer([viewDescriptors[0]], sidebarContainer); expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); - expectedSequence += containerMoveString(viewDescriptors[2], viewDescriptorService.getViewContainerByViewId(viewDescriptors[2].id)!, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], panelContainer); + expectedSequence += containerMoveString(viewDescriptors[2], testObject.getViewContainerByViewId(viewDescriptors[2].id)!, panelContainer); + testObject.moveViewsToContainer([viewDescriptors[2]], panelContainer); expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], panelContainer); + testObject.moveViewsToContainer([viewDescriptors[0]], panelContainer); expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar); expectedSequence += containerMoveString(viewDescriptors[2], panelContainer, sidebarContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], sidebarContainer); + testObject.moveViewsToContainer([viewDescriptors[2]], sidebarContainer); expectedSequence += locationMoveString(viewDescriptors[1], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel); expectedSequence += containerMoveString(viewDescriptors[1], sidebarContainer, panelContainer); expectedSequence += containerMoveString(viewDescriptors[2], sidebarContainer, panelContainer); - viewDescriptorService.moveViewsToContainer([viewDescriptors[1], viewDescriptors[2]], panelContainer); + testObject.moveViewsToContainer([viewDescriptors[1], viewDescriptors[2]], panelContainer); assert.strictEqual(actualSequence, expectedSequence, 'Event sequence not matching expected sequence'); }); + test('reset', async function () { + const testObject = aViewDescriptorService(); + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true, + order: 1 + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true, + order: 2 + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true, + order: 3 + } + ]; + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 2), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(2), panelContainer); + + testObject.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Panel); + testObject.moveViewsToContainer([viewDescriptors[1]], panelContainer); + testObject.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Sidebar); + + const generatedPanel = assertIsDefined(testObject.getViewContainerByViewId(viewDescriptors[0].id)); + const generatedSidebar = assertIsDefined(testObject.getViewContainerByViewId(viewDescriptors[2].id)); + + testObject.reset(); + + const sidebarViews = testObject.getViewContainerModel(sidebarContainer); + assert.deepStrictEqual(sidebarViews.allViewDescriptors.map(v => v.id), ['view1', 'view2']); + const panelViews = testObject.getViewContainerModel(panelContainer); + assert.deepStrictEqual(panelViews.allViewDescriptors.map(v => v.id), ['view3']); + + const actual = JSON.parse(instantiationService.get(IStorageService).get('views.customizations', StorageScope.PROFILE)!); + assert.deepStrictEqual(actual, { viewContainerLocations: {}, viewLocations: {}, viewContainerBadgeEnablementStates: {} }); + + assert.deepStrictEqual(testObject.getViewContainerById(generatedPanel.id), null); + assert.deepStrictEqual(testObject.getViewContainerById(generatedSidebar.id), null); + }); + + test('initialize with custom locations', async function () { + const storageService = instantiationService.get(IStorageService); + const viewContainer1 = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const generateViewContainer1 = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; + const viewsCustomizations = { + viewContainerLocations: { + [generateViewContainer1]: ViewContainerLocation.Sidebar, + [viewContainer1.id]: ViewContainerLocation.AuxiliaryBar + }, + viewLocations: { + 'view1': generateViewContainer1 + } + }; + storageService.store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + }, + { + id: 'view4', + ctorDescriptor: null!, + name: 'Test View 4', + canMoveView: true + } + ]; + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 3), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(3), viewContainer1); + + const testObject = aViewDescriptorService(); + + const sidebarViews = testObject.getViewContainerModel(sidebarContainer); + assert.deepStrictEqual(sidebarViews.allViewDescriptors.map(v => v.id), ['view2', 'view3']); + + const generatedViewContainerViews = testObject.getViewContainerModel(testObject.getViewContainerById(generateViewContainer1)!); + assert.deepStrictEqual(generatedViewContainerViews.allViewDescriptors.map(v => v.id), ['view1']); + + const viewContainer1Views = testObject.getViewContainerModel(viewContainer1); + assert.deepStrictEqual(testObject.getViewContainerLocation(viewContainer1), ViewContainerLocation.AuxiliaryBar); + assert.deepStrictEqual(viewContainer1Views.allViewDescriptors.map(v => v.id), ['view4']); + }); + + test('storage change', async function () { + const testObject = aViewDescriptorService(); + + const viewContainer1 = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const generateViewContainer1 = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; + + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + }, + { + id: 'view4', + ctorDescriptor: null!, + name: 'Test View 4', + canMoveView: true + } + ]; + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 3), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(3), viewContainer1); + + const viewsCustomizations = { + viewContainerLocations: { + [generateViewContainer1]: ViewContainerLocation.Sidebar, + [viewContainer1.id]: ViewContainerLocation.AuxiliaryBar + }, + viewLocations: { + 'view1': generateViewContainer1 + } + }; + instantiationService.get(IStorageService).store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + + const sidebarViews = testObject.getViewContainerModel(sidebarContainer); + assert.deepStrictEqual(sidebarViews.allViewDescriptors.map(v => v.id), ['view2', 'view3']); + + const generatedViewContainerViews = testObject.getViewContainerModel(testObject.getViewContainerById(generateViewContainer1)!); + assert.deepStrictEqual(generatedViewContainerViews.allViewDescriptors.map(v => v.id), ['view1']); + + const viewContainer1Views = testObject.getViewContainerModel(viewContainer1); + assert.deepStrictEqual(testObject.getViewContainerLocation(viewContainer1), ViewContainerLocation.AuxiliaryBar); + assert.deepStrictEqual(viewContainer1Views.allViewDescriptors.map(v => v.id), ['view4']); + }); + + test('orphan views', async function () { + const storageService = instantiationService.get(IStorageService); + const viewsCustomizations = { + viewContainerLocations: {}, + viewLocations: { + 'view1': `${viewContainerIdPrefix}-${generateUuid()}` + } + }; + storageService.store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true, + order: 1 + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true, + order: 2 + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true, + order: 3 + } + ]; + + ViewsRegistry.registerViews(viewDescriptors, sidebarContainer); + + const testObject = aViewDescriptorService(); + + const sidebarViews = testObject.getViewContainerModel(sidebarContainer); + assert.deepStrictEqual(sidebarViews.allViewDescriptors.map(v => v.id), ['view2', 'view3']); + + testObject.whenExtensionsRegistered(); + assert.deepStrictEqual(sidebarViews.allViewDescriptors.map(v => v.id), ['view1', 'view2', 'view3']); + }); + + test('orphan view containers', async function () { + const storageService = instantiationService.get(IStorageService); + const generatedViewContainerId = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; + const viewsCustomizations = { + viewContainerLocations: { + [generatedViewContainerId]: ViewContainerLocation.Sidebar + }, + viewLocations: {} + }; + storageService.store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true, + order: 1 + } + ]; + + ViewsRegistry.registerViews(viewDescriptors, sidebarContainer); + + const testObject = aViewDescriptorService(); + testObject.whenExtensionsRegistered(); + + assert.deepStrictEqual(testObject.getViewContainerById(generatedViewContainerId), null); + assert.deepStrictEqual(testObject.isViewContainerRemovedPermanently(generatedViewContainerId), true); + + const actual = JSON.parse(storageService.get('views.customizations', StorageScope.PROFILE)!); + assert.deepStrictEqual(actual, { viewContainerLocations: {}, viewLocations: {}, viewContainerBadgeEnablementStates: {} }); + }); + + test('custom locations take precedence when default view container of views change', async function () { + const storageService = instantiationService.get(IStorageService); + const viewContainer1 = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const generateViewContainer1 = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; + const viewsCustomizations = { + viewContainerLocations: { + [generateViewContainer1]: ViewContainerLocation.Sidebar, + [viewContainer1.id]: ViewContainerLocation.AuxiliaryBar + }, + viewLocations: { + 'view1': generateViewContainer1 + } + }; + storageService.store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + }, + { + id: 'view3', + ctorDescriptor: null!, + name: 'Test View 3', + canMoveView: true + }, + { + id: 'view4', + ctorDescriptor: null!, + name: 'Test View 4', + canMoveView: true + } + ]; + + ViewsRegistry.registerViews(viewDescriptors.slice(0, 3), sidebarContainer); + ViewsRegistry.registerViews(viewDescriptors.slice(3), viewContainer1); + + const testObject = aViewDescriptorService(); + ViewsRegistry.moveViews([viewDescriptors[0], viewDescriptors[1]], panelContainer); + + const sidebarViews = testObject.getViewContainerModel(sidebarContainer); + assert.deepStrictEqual(sidebarViews.allViewDescriptors.map(v => v.id), ['view3']); + + const panelViews = testObject.getViewContainerModel(panelContainer); + assert.deepStrictEqual(panelViews.allViewDescriptors.map(v => v.id), ['view2']); + + const generatedViewContainerViews = testObject.getViewContainerModel(testObject.getViewContainerById(generateViewContainer1)!); + assert.deepStrictEqual(generatedViewContainerViews.allViewDescriptors.map(v => v.id), ['view1']); + + const viewContainer1Views = testObject.getViewContainerModel(viewContainer1); + assert.deepStrictEqual(testObject.getViewContainerLocation(viewContainer1), ViewContainerLocation.AuxiliaryBar); + assert.deepStrictEqual(viewContainer1Views.allViewDescriptors.map(v => v.id), ['view4']); + }); + + test('view containers with not existing views are not removed from customizations', async function () { + const storageService = instantiationService.get(IStorageService); + const viewContainer1 = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const generateViewContainer1 = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.Sidebar)}.${generateUuid()}`; + const viewsCustomizations = { + viewContainerLocations: { + [generateViewContainer1]: ViewContainerLocation.Sidebar, + [viewContainer1.id]: ViewContainerLocation.AuxiliaryBar + }, + viewLocations: { + 'view5': generateViewContainer1 + } + }; + storageService.store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + } + ]; + + ViewsRegistry.registerViews(viewDescriptors, viewContainer1); + + const testObject = aViewDescriptorService(); + testObject.whenExtensionsRegistered(); + + const viewContainer1Views = testObject.getViewContainerModel(viewContainer1); + assert.deepStrictEqual(testObject.getViewContainerLocation(viewContainer1), ViewContainerLocation.AuxiliaryBar); + assert.deepStrictEqual(viewContainer1Views.allViewDescriptors.map(v => v.id), ['view1']); + + const actual = JSON.parse(storageService.get('views.customizations', StorageScope.PROFILE)!); + assert.deepStrictEqual(actual, viewsCustomizations); + }); + + test('storage change also updates locations even if views do not exists and views are registered later', async function () { + const storageService = instantiationService.get(IStorageService); + const testObject = aViewDescriptorService(); + + const generateViewContainerId = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar)}.${generateUuid()}`; + const viewsCustomizations = { + viewContainerLocations: { + [generateViewContainerId]: ViewContainerLocation.AuxiliaryBar, + }, + viewLocations: { + 'view1': generateViewContainerId + } + }; + storageService.store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + + const viewContainer = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + } + ]; + ViewsRegistry.registerViews(viewDescriptors, viewContainer); + + testObject.whenExtensionsRegistered(); + + const viewContainer1Views = testObject.getViewContainerModel(viewContainer); + assert.deepStrictEqual(viewContainer1Views.allViewDescriptors.map(v => v.id), ['view2']); + + const generateViewContainer = testObject.getViewContainerById(generateViewContainerId)!; + assert.deepStrictEqual(testObject.getViewContainerLocation(generateViewContainer), ViewContainerLocation.AuxiliaryBar); + const generatedViewContainerModel = testObject.getViewContainerModel(generateViewContainer); + assert.deepStrictEqual(generatedViewContainerModel.allViewDescriptors.map(v => v.id), ['view1']); + }); + + test('storage change move views and retain visibility state', async function () { + const storageService = instantiationService.get(IStorageService); + const testObject = aViewDescriptorService(); + + const viewContainer = ViewContainersRegistry.registerViewContainer({ id: `${viewContainerIdPrefix}-${generateUuid()}`, title: 'test', ctorDescriptor: new SyncDescriptor({}) }, ViewContainerLocation.Sidebar); + const viewDescriptors: IViewDescriptor[] = [ + { + id: 'view1', + ctorDescriptor: null!, + name: 'Test View 1', + canMoveView: true, + canToggleVisibility: true + }, + { + id: 'view2', + ctorDescriptor: null!, + name: 'Test View 2', + canMoveView: true + } + ]; + ViewsRegistry.registerViews(viewDescriptors, viewContainer); + + testObject.whenExtensionsRegistered(); + + const viewContainer1Views = testObject.getViewContainerModel(viewContainer); + viewContainer1Views.setVisible('view1', false); + + const generateViewContainerId = `workbench.views.service.${ViewContainerLocationToString(ViewContainerLocation.AuxiliaryBar)}.${generateUuid()}`; + const viewsCustomizations = { + viewContainerLocations: { + [generateViewContainerId]: ViewContainerLocation.AuxiliaryBar, + }, + viewLocations: { + 'view1': generateViewContainerId + } + }; + storageService.store('views.customizations', JSON.stringify(viewsCustomizations), StorageScope.PROFILE, StorageTarget.USER); + + const generateViewContainer = testObject.getViewContainerById(generateViewContainerId)!; + const generatedViewContainerModel = testObject.getViewContainerModel(generateViewContainer); + + assert.deepStrictEqual(viewContainer1Views.allViewDescriptors.map(v => v.id), ['view2']); + assert.deepStrictEqual(testObject.getViewContainerLocation(generateViewContainer), ViewContainerLocation.AuxiliaryBar); + assert.deepStrictEqual(generatedViewContainerModel.allViewDescriptors.map(v => v.id), ['view1']); + + storageService.store('views.customizations', JSON.stringify({}), StorageScope.PROFILE, StorageTarget.USER); + + assert.deepStrictEqual(viewContainer1Views.allViewDescriptors.map(v => v.id).sort((a, b) => compare(a, b)), ['view1', 'view2']); + assert.deepStrictEqual(viewContainer1Views.visibleViewDescriptors.map(v => v.id), ['view2']); + assert.deepStrictEqual(generatedViewContainerModel.allViewDescriptors.map(v => v.id), []); + }); + }); diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts index df6c484aa5..f963f83a7e 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupService.ts @@ -7,7 +7,7 @@ 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 { WorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { joinPath } from 'vs/base/common/resources'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -29,7 +29,7 @@ export class BrowserWorkingCopyBackupService extends WorkingCopyBackupService { } // Register Service -registerSingleton(IWorkingCopyBackupService, BrowserWorkingCopyBackupService); +registerSingleton(IWorkingCopyBackupService, BrowserWorkingCopyBackupService, InstantiationType.Eager); // Register Backup Tracker Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(BrowserWorkingCopyBackupTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts index a076eb4c1d..44860b5558 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyBackupTracker.ts @@ -32,27 +32,27 @@ export class BrowserWorkingCopyBackupTracker extends WorkingCopyBackupTracker im protected onFinalBeforeShutdown(reason: ShutdownReason): boolean { // Web: we cannot perform long running in the shutdown phase - // As such we need to check sync if there are any dirty working + // As such we need to check sync if there are any modified working // copies that have not been backed up yet and then prevent the // shutdown if that is the case. - const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; - if (!dirtyWorkingCopies.length) { - return false; // no dirty: no veto + const modifiedWorkingCopies = this.workingCopyService.modifiedWorkingCopies; + if (!modifiedWorkingCopies.length) { + return false; // nothing modified: no veto } if (!this.filesConfigurationService.isHotExitEnabled) { - return true; // dirty without backup: veto + return true; // modified without backup: veto } - for (const dirtyWorkingCopy of dirtyWorkingCopies) { - if (!this.workingCopyBackupService.hasBackupSync(dirtyWorkingCopy, this.getContentVersion(dirtyWorkingCopy))) { + for (const modifiedWorkingCopy of modifiedWorkingCopies) { + if (!this.workingCopyBackupService.hasBackupSync(modifiedWorkingCopy, this.getContentVersion(modifiedWorkingCopy))) { this.logService.warn('Unload veto: pending backups'); - return true; // dirty without backup: veto + return true; // modified without backup: veto } } - return false; // dirty with backups: no veto + return false; // modified and backed up: no veto } } diff --git a/src/vs/workbench/services/workingCopy/browser/workingCopyHistoryService.ts b/src/vs/workbench/services/workingCopy/browser/workingCopyHistoryService.ts index b15859d256..8a148b1747 100644 --- a/src/vs/workbench/services/workingCopy/browser/workingCopyHistoryService.ts +++ b/src/vs/workbench/services/workingCopy/browser/workingCopyHistoryService.ts @@ -11,7 +11,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkingCopyHistoryModelOptions, WorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistoryService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistory'; export class BrowserWorkingCopyHistoryService extends WorkingCopyHistoryService { @@ -34,4 +34,4 @@ export class BrowserWorkingCopyHistoryService extends WorkingCopyHistoryService } // Register Service -registerSingleton(IWorkingCopyHistoryService, BrowserWorkingCopyHistoryService, true); +registerSingleton(IWorkingCopyHistoryService, BrowserWorkingCopyHistoryService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts index 2bdac4b94f..b7d4231444 100644 --- a/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/fileWorkingCopyManager.ts @@ -11,7 +11,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable } from 'vs/base/common/lifecycle'; import { toLocalResource, joinPath, isEqual, basename, dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { IFileDialogService, IDialogService, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; +import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { ISaveOptions, SaveSourceRegistry } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -148,7 +148,7 @@ export class FileWorkingCopyManager { - const confirm: IConfirmation = { + const { confirmed } = await this.dialogService.confirm({ + type: 'warning', message: localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), detail: localize('irreversible', "A file or folder with the name '{0}' already exists in the folder '{1}'. Replacing it will overwrite its current contents.", basename(resource), basename(dirname(resource))), - primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; + primaryButton: localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace") + }); - const result = await this.dialogService.confirm(confirm); - return result.confirmed; + return confirmed; } private async suggestSavePath(resource: URI): Promise { diff --git a/src/vs/workbench/services/workingCopy/common/resourceWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/resourceWorkingCopy.ts index 2391a4a801..9a27cc16e8 100644 --- a/src/vs/workbench/services/workingCopy/common/resourceWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/resourceWorkingCopy.ts @@ -143,6 +143,13 @@ export abstract class ResourceWorkingCopy extends Disposable implements IResourc //#endregion + //#region Modified Tracking + + isModified(): boolean { + return this.isDirty(); + } + + //#endregion //#region Abstract diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts index bd8d79567b..3b3490aa94 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopy.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { ETAG_DISABLED, FileOperationError, FileOperationResult, FileSystemProviderCapabilities, IFileService, IFileStatWithMetadata, IFileStreamContent, IWriteFileOptions, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; +import { ETAG_DISABLED, FileOperationError, FileOperationResult, IFileService, IFileStatWithMetadata, IFileStreamContent, IWriteFileOptions, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; import { ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { IWorkingCopyBackup, IWorkingCopyBackupMeta, IWorkingCopySaveEvent, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; @@ -20,7 +20,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura import { IWorkingCopyBackupService, IResolvedWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { hash } from 'vs/base/common/hash'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { isErrorWithActions, toErrorMessage } from 'vs/base/common/errorMessage'; import { IAction, toAction } from 'vs/base/common/actions'; import { isWindows } from 'vs/base/common/platform'; import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; @@ -117,9 +117,9 @@ export interface IStoredFileWorkingCopy e resolve(options?: IStoredFileWorkingCopyResolveOptions): Promise; /** - * Explicitly sets the working copy to be dirty. + * Explicitly sets the working copy to be modified. */ - markDirty(): void; + markModified(): void; /** * Whether the stored file working copy is in the provided `state` @@ -332,6 +332,12 @@ export class StoredFileWorkingCopy extend // Make known to working copy service this._register(workingCopyService.registerWorkingCopy(this)); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.filesConfigurationService.onReadonlyChange(() => this._onDidChangeReadonly.fire())); } //#region Dirty @@ -343,8 +349,8 @@ export class StoredFileWorkingCopy extend return this.dirty; } - markDirty(): void { - this.setDirty(true); + markModified(): void { + this.setDirty(true); // stored file working copy tracks modified via dirty } private setDirty(dirty: boolean): void { @@ -400,7 +406,7 @@ export class StoredFileWorkingCopy extend private lastResolvedFileStat: IFileStatWithMetadata | undefined; - isResolved(): this is IResolvedStoredFileWorkingCopy { + isResolved(): any { // {{SQL CARBON EDIT}} - remove "this is IResolvedStoredFileWorkingCopy" due to build error return !!this.model; } @@ -484,7 +490,8 @@ export class StoredFileWorkingCopy extend size, etag, value: buffer, - readonly: false + readonly: false, + locked: false }, true /* dirty (resolved from buffer) */); } @@ -524,7 +531,8 @@ export class StoredFileWorkingCopy extend size: backup.meta ? backup.meta.size : 0, etag: backup.meta ? backup.meta.etag : ETAG_DISABLED, // etag disabled if unknown! value: backup.value, - readonly: false + readonly: false, + locked: false }, true /* dirty (resolved from backup) */); // Restore orphaned flag based on state @@ -616,6 +624,7 @@ export class StoredFileWorkingCopy extend size: content.size, etag: content.etag, readonly: content.readonly, + locked: content.locked, isFile: true, isDirectory: false, isSymbolicLink: false, @@ -623,7 +632,7 @@ export class StoredFileWorkingCopy extend }); // Update existing model if we had been resolved - if ((this as StoredFileWorkingCopy).isResolved()) { // {{SQL CARBON EDIT}} cast to avoid compile errors with type guard assert + if (this.isResolved()) { await this.doUpdateModel(content.value); } @@ -781,6 +790,8 @@ export class StoredFileWorkingCopy extend private readonly saveSequentializer = new TaskSequentializer(); + private ignoreSaveFromSaveParticipants = false; + async save(options: IStoredFileWorkingCopySaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { return false; @@ -817,6 +828,15 @@ export class StoredFileWorkingCopy extend let versionId = this.versionId; this.trace(`doSave(${versionId}) - enter with versionId ${versionId}`); + // Return early if saved from within save participant to break recursion + // + // Scenario: a save participant triggers a save() on the working copy + if (this.ignoreSaveFromSaveParticipants) { + this.trace(`doSave(${versionId}) - exit - refusing to save() recursively from save participant`); + + return; + } + // Lookup any running pending save for this versionId and return it if found // // Scenario: user invoked the save action multiple times quickly for the same contents @@ -902,7 +922,12 @@ export class StoredFileWorkingCopy extend // Run save participants unless save was cancelled meanwhile if (!saveCancellation.token.isCancellationRequested) { - await this.workingCopyFileService.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + this.ignoreSaveFromSaveParticipants = true; + try { + await this.workingCopyFileService.runSaveParticipants(this, { reason: options.reason ?? SaveReason.EXPLICIT }, saveCancellation.token); + } finally { + this.ignoreSaveFromSaveParticipants = false; + } } } catch (error) { this.logService.error(`[stored file working copy] runSaveParticipants(${versionId}) - resulted in an error: ${error.toString()}`, this.resource.toString(), this.typeId); @@ -1049,10 +1074,15 @@ export class StoredFileWorkingCopy extend // Any other save error else { const isWriteLocked = fileOperationError.fileOperationResult === FileOperationResult.FILE_WRITE_LOCKED; - const triedToUnlock = isWriteLocked && fileOperationError.options?.unlock; + const triedToUnlock = isWriteLocked && (fileOperationError.options as IWriteFileOptions | undefined)?.unlock; const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED; const canSaveElevated = this.elevatedFileService.isSupported(this.resource); + // Error with Actions + if (isErrorWithActions(error)) { + primaryActions.push(...error.actions); + } + // Save Elevated if (canSaveElevated && (isPermissionDenied || triedToUnlock)) { primaryActions.push(toAction({ @@ -1215,7 +1245,7 @@ export class StoredFileWorkingCopy extend //#region Utilities isReadonly(): boolean { - return this.lastResolvedFileStat?.readonly || this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); + return this.filesConfigurationService.isReadonly(this.resource, this.lastResolvedFileStat); } private trace(msg: string): void { @@ -1233,6 +1263,9 @@ export class StoredFileWorkingCopy extend this.inConflictMode = false; this.inErrorMode = false; + // Free up model for GC + this._model = undefined; + super.dispose(); } diff --git a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts index c327a8171d..eb695f36d0 100644 --- a/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/storedFileWorkingCopyManager.ts @@ -207,23 +207,24 @@ export class StoredFileWorkingCopyManager this._register(this.workingCopyFileService.onDidRunWorkingCopyFileOperation(e => this.onDidRunWorkingCopyFileOperation(e))); // Lifecycle - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(), 'veto.fileWorkingCopyManager')); - this.lifecycleService.onWillShutdown(event => event.join(this.onWillShutdown(), { id: 'join.fileWorkingCopyManager', label: localize('join.fileWorkingCopyManager', "Saving working copies") })); + if (isWeb) { + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdownWeb(), 'veto.fileWorkingCopyManager')); + } else { + this.lifecycleService.onWillShutdown(event => event.join(this.onWillShutdownDesktop(), { id: 'join.fileWorkingCopyManager', label: localize('join.fileWorkingCopyManager', "Saving working copies") })); + } } - private onBeforeShutdown(): boolean { - if (isWeb) { - if (this.workingCopies.some(workingCopy => workingCopy.hasState(StoredFileWorkingCopyState.PENDING_SAVE))) { - // stored file working copies are pending to be saved: - // veto because web does not support long running shutdown - return true; - } + private onBeforeShutdownWeb(): boolean { + if (this.workingCopies.some(workingCopy => workingCopy.hasState(StoredFileWorkingCopyState.PENDING_SAVE))) { + // stored file working copies are pending to be saved: + // veto because web does not support long running shutdown + return true; } return false; } - private async onWillShutdown(): Promise { + private async onWillShutdownDesktop(): Promise { let pendingSavedWorkingCopies: IStoredFileWorkingCopy[]; // As long as stored file working copies are pending to be saved, we prolong the shutdown @@ -370,15 +371,16 @@ export class StoredFileWorkingCopyManager if (workingCopiesToRestore) { this.mapCorrelationIdToWorkingCopiesToRestore.delete(e.correlationId); - workingCopiesToRestore.forEach(workingCopy => { + for (const workingCopy of workingCopiesToRestore) { - // Snapshot presence means this working copy used to be dirty and so we restore that + // Snapshot presence means this working copy used to be modified and so we restore that // flag. we do NOT have to restore the content because the working copy was only soft - // reverted and did not loose its original dirty contents. + // reverted and did not loose its original modified contents. + if (workingCopy.snapshot) { - this.get(workingCopy.source)?.markDirty(); + this.get(workingCopy.source)?.markModified(); } - }); + } } } } diff --git a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts index 431c0275b9..e1505ea3e4 100644 --- a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts +++ b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopy.ts @@ -37,9 +37,9 @@ export interface IUntitledFileWorkingCopyModelContentChangedEvent { /** * Flag that indicates that the content change should - * clear the dirty flag, e.g. because the contents are + * clear the dirty/modified flags, e.g. because the contents are * back to being empty or back to an initial state that - * should not be considered as dirty. + * should not be considered as modified. */ readonly isInitial: boolean; } @@ -82,17 +82,17 @@ export interface IUntitledFileWorkingCopyInitialContents { /** * If not provided, the untitled file working copy will be marked - * dirty by default given initial contents are provided. + * modified by default given initial contents are provided. * * Note: if the untitled file working copy has an associated path - * the dirty state will always be set. + * the modified state will always be set. */ - readonly markDirty?: boolean; + readonly markModified?: boolean; } export class UntitledFileWorkingCopy extends Disposable implements IUntitledFileWorkingCopy { - readonly capabilities = WorkingCopyCapabilities.Untitled; + readonly capabilities = this.isScratchpad ? WorkingCopyCapabilities.Untitled | WorkingCopyCapabilities.Scratchpad : WorkingCopyCapabilities.Untitled; private _model: M | undefined = undefined; get model(): M | undefined { return this._model; } @@ -121,6 +121,7 @@ export class UntitledFileWorkingCopy ex readonly resource: URI, readonly name: string, readonly hasAssociatedFilePath: boolean, + private readonly isScratchpad: boolean, private readonly initialContents: IUntitledFileWorkingCopyInitialContents | undefined, private readonly modelFactory: IUntitledFileWorkingCopyModelFactory, private readonly saveDelegate: IUntitledFileWorkingCopySaveDelegate, @@ -134,21 +135,27 @@ export class UntitledFileWorkingCopy ex this._register(workingCopyService.registerWorkingCopy(this)); } - //#region Dirty + //#region Dirty/Modified - private dirty = this.hasAssociatedFilePath || Boolean(this.initialContents && this.initialContents.markDirty !== false); + private modified = this.hasAssociatedFilePath || Boolean(this.initialContents && this.initialContents.markModified !== false); isDirty(): boolean { - return this.dirty; + return this.modified && !this.isScratchpad; // Scratchpad working copies are never dirty } - private setDirty(dirty: boolean): void { - if (this.dirty === dirty) { + isModified(): boolean { + return this.modified; + } + + private setModified(modified: boolean): void { + if (this.modified === modified) { return; } - this.dirty = dirty; - this._onDidChangeDirty.fire(); + this.modified = modified; + if (!this.isScratchpad) { + this._onDidChangeDirty.fire(); + } } //#endregion @@ -189,8 +196,8 @@ export class UntitledFileWorkingCopy ex // Create model await this.doCreateModel(untitledContents); - // Untitled associated to file path are dirty right away as well as untitled with content - this.setDirty(this.hasAssociatedFilePath || !!backup || Boolean(this.initialContents && this.initialContents.markDirty !== false)); + // Untitled associated to file path are modified right away as well as untitled with content + this.setModified(this.hasAssociatedFilePath || !!backup || Boolean(this.initialContents && this.initialContents.markModified !== false)); // If we have initial contents, make sure to emit this // as the appropriate events to the outside. @@ -220,16 +227,16 @@ export class UntitledFileWorkingCopy ex private onModelContentChanged(e: IUntitledFileWorkingCopyModelContentChangedEvent): void { - // Mark the untitled file working copy as non-dirty once its + // Mark the untitled file working copy as non-modified once its // in case provided by the change event and in case we do not // have an associated path set if (!this.hasAssociatedFilePath && e.isInitial) { - this.setDirty(false); + this.setModified(false); } - // Turn dirty otherwise + // Turn modified otherwise else { - this.setDirty(true); + this.setModified(true); } // Emit as general content change event @@ -287,8 +294,8 @@ export class UntitledFileWorkingCopy ex async revert(): Promise { this.trace('revert()'); - // No longer dirty - this.setDirty(false); + // No longer modified + this.setModified(false); // Emit as event this._onDidRevert.fire(); diff --git a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts index 0ea574a7b1..efdc9a806a 100644 --- a/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts +++ b/src/vs/workbench/services/workingCopy/common/untitledFileWorkingCopyManager.ts @@ -91,6 +91,12 @@ export interface INewOrExistingUntitledFileWorkingCopyOptions extends INewUntitl * Note: the resource will not be used unless the scheme is `untitled`. */ untitledResource: URI; + + /** + * A flag that will prevent the working copy from appearing dirty in the UI + * and not show a confirmation dialog when closed with unsaved content. + */ + isScratchpad?: boolean; } type IInternalUntitledFileWorkingCopyOptions = INewUntitledFileWorkingCopyOptions & INewUntitledFileWorkingCopyWithAssociatedResourceOptions & INewOrExistingUntitledFileWorkingCopyOptions; @@ -165,8 +171,11 @@ export class UntitledFileWorkingCopyManager(); // A map of scheduled pending backup operations for working copies - protected readonly pendingBackupOperations = new Map(); + // Given https://github.com/microsoft/vscode/issues/158038, we explicitly + // do not store `IWorkingCopy` but the identifier in the map, since it + // looks like GC is not runnin for the working copy otherwise. + protected readonly pendingBackupOperations = new Map(); private suspended = false; @@ -112,7 +114,7 @@ export abstract class WorkingCopyBackupTracker extends Disposable { return; } - if (workingCopy.isDirty()) { + if (workingCopy.isModified()) { this.scheduleBackup(workingCopy); } } @@ -157,8 +159,8 @@ export abstract class WorkingCopyBackupTracker extends Disposable { return; } - // Schedule backup if dirty - if (workingCopy.isDirty()) { + // Schedule backup for modified working copies + if (workingCopy.isModified()) { // this listener will make sure that the backup is // pushed out for as long as the user is still changing // the content of the working copy. @@ -174,14 +176,15 @@ export abstract class WorkingCopyBackupTracker extends Disposable { this.logService.trace(`[backup tracker] scheduling backup`, workingCopy.resource.toString(), workingCopy.typeId); // Schedule new backup + const workingCopyIdentifier = { resource: workingCopy.resource, typeId: workingCopy.typeId }; const cts = new CancellationTokenSource(); const handle = setTimeout(async () => { if (cts.token.isCancellationRequested) { return; } - // Backup if dirty - if (workingCopy.isDirty()) { + // Backup if modified + if (workingCopy.isModified()) { this.logService.trace(`[backup tracker] creating backup`, workingCopy.resource.toString(), workingCopy.typeId); try { @@ -190,7 +193,7 @@ export abstract class WorkingCopyBackupTracker extends Disposable { return; } - if (workingCopy.isDirty()) { + if (workingCopy.isModified()) { this.logService.trace(`[backup tracker] storing backup`, workingCopy.resource.toString(), workingCopy.typeId); await this.workingCopyBackupService.backup(workingCopy, backup.content, this.getContentVersion(workingCopy), backup.meta, cts.token); @@ -203,12 +206,12 @@ export abstract class WorkingCopyBackupTracker extends Disposable { // Clear disposable unless we got canceled which would // indicate another operation has started meanwhile if (!cts.token.isCancellationRequested) { - this.pendingBackupOperations.delete(workingCopy); + this.pendingBackupOperations.delete(workingCopyIdentifier); } }, this.getBackupScheduleDelay(workingCopy)); // Keep in map for disposal as needed - this.pendingBackupOperations.set(workingCopy, toDisposable(() => { + this.pendingBackupOperations.set(workingCopyIdentifier, toDisposable(() => { this.logService.trace(`[backup tracker] clearing pending backup creation`, workingCopy.resource.toString(), workingCopy.typeId); cts.dispose(true); @@ -235,35 +238,54 @@ export abstract class WorkingCopyBackupTracker extends Disposable { this.cancelBackupOperation(workingCopy); // Schedule backup discard asap + const workingCopyIdentifier = { resource: workingCopy.resource, typeId: workingCopy.typeId }; const cts = new CancellationTokenSource(); - (async () => { - this.logService.trace(`[backup tracker] discarding backup`, workingCopy.resource.toString(), workingCopy.typeId); - - // Discard backup - try { - await this.workingCopyBackupService.discardBackup(workingCopy, cts.token); - } catch (error) { - this.logService.error(error); - } - - // Clear disposable unless we got canceled which would - // indicate another operation has started meanwhile - if (!cts.token.isCancellationRequested) { - this.pendingBackupOperations.delete(workingCopy); - } - })(); + this.doDiscardBackup(workingCopyIdentifier, cts); // Keep in map for disposal as needed - this.pendingBackupOperations.set(workingCopy, toDisposable(() => { + this.pendingBackupOperations.set(workingCopyIdentifier, toDisposable(() => { this.logService.trace(`[backup tracker] clearing pending backup discard`, workingCopy.resource.toString(), workingCopy.typeId); cts.dispose(true); })); } + private async doDiscardBackup(workingCopyIdentifier: IWorkingCopyIdentifier, cts: CancellationTokenSource) { + this.logService.trace(`[backup tracker] discarding backup`, workingCopyIdentifier.resource.toString(), workingCopyIdentifier.typeId); + + // Discard backup + try { + await this.workingCopyBackupService.discardBackup(workingCopyIdentifier, cts.token); + } catch (error) { + this.logService.error(error); + } + + // Clear disposable unless we got canceled which would + // indicate another operation has started meanwhile + if (!cts.token.isCancellationRequested) { + this.pendingBackupOperations.delete(workingCopyIdentifier); + } + } + private cancelBackupOperation(workingCopy: IWorkingCopy): void { - dispose(this.pendingBackupOperations.get(workingCopy)); - this.pendingBackupOperations.delete(workingCopy); + + // Given a working copy we want to find the matching + // identifier in our pending operations map because + // we cannot use the working copy directly, as the + // identifier might have different object identity. + + let workingCopyIdentifier: IWorkingCopyIdentifier | undefined = undefined; + for (const [identifier] of this.pendingBackupOperations) { + if (identifier.resource.toString() === workingCopy.resource.toString() && identifier.typeId === workingCopy.typeId) { + workingCopyIdentifier = identifier; + break; + } + } + + if (workingCopyIdentifier) { + dispose(this.pendingBackupOperations.get(workingCopyIdentifier)); + this.pendingBackupOperations.delete(workingCopyIdentifier); + } } protected cancelBackupOperations(): void { @@ -318,7 +340,7 @@ export abstract class WorkingCopyBackupTracker extends Disposable { // associated editor. const restoredBackups = new Set(); for (const unrestoredBackup of this.unrestoredBackups) { - const canHandleUnrestoredBackup = handler.handles(unrestoredBackup); + const canHandleUnrestoredBackup = await handler.handles(unrestoredBackup); if (!canHandleUnrestoredBackup) { continue; } @@ -351,8 +373,7 @@ export abstract class WorkingCopyBackupTracker extends Disposable { options: { pinned: true, preserveFocus: true, - inactive: true, - override: EditorResolution.DISABLED // very important to disable overrides because the editor input we got is proper + inactive: true } }))); @@ -362,7 +383,7 @@ export abstract class WorkingCopyBackupTracker extends Disposable { } // Then, resolve each opened editor to make sure the working copy - // is loaded and the dirty editor appears properly + // is loaded and the modified editor appears properly. // We only do that for editors that are not active in a group // already to prevent calling `resolve` twice! await Promises.settled([...openedEditorsForBackups].map(async openedEditorForBackup => { diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts index d7c206343f..8aaddef5a4 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyEditorService.ts @@ -5,7 +5,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { EditorsOrder, IEditorIdentifier } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IWorkingCopy, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; @@ -20,7 +20,7 @@ export interface IWorkingCopyEditorHandler { * Whether the handler is capable of opening the specific backup in * an editor. */ - handles(workingCopy: IWorkingCopyIdentifier): boolean; + handles(workingCopy: IWorkingCopyIdentifier): boolean | Promise; /** * Whether the provided working copy is opened in the provided editor. @@ -87,7 +87,7 @@ export class WorkingCopyEditorService extends Disposable implements IWorkingCopy private isOpen(workingCopy: IWorkingCopy, editor: EditorInput): boolean { for (const handler of this.handlers) { - if (handler.handles(workingCopy) && handler.isOpen(workingCopy, editor)) { + if (handler.isOpen(workingCopy, editor)) { return true; } } @@ -97,4 +97,4 @@ export class WorkingCopyEditorService extends Disposable implements IWorkingCopy } // Register Service -registerSingleton(IWorkingCopyEditorService, WorkingCopyEditorService); +registerSingleton(IWorkingCopyEditorService, WorkingCopyEditorService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts index a840cd2ad6..4bfaa488ea 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyFileService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, AsyncEmitter, IWaitUntil } from 'vs/base/common/event'; import { Promises } from 'vs/base/common/async'; import { insert } from 'vs/base/common/arrays'; @@ -525,4 +525,4 @@ export class WorkingCopyFileService extends Disposable implements IWorkingCopyFi //#endregion } -registerSingleton(IWorkingCopyFileService, WorkingCopyFileService, true); +registerSingleton(IWorkingCopyFileService, WorkingCopyFileService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts index ac5d9f56cb..0c93787d3d 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyHistoryService.ts @@ -4,23 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { Emitter } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { assertIsDefined } from 'vs/base/common/types'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { ILifecycleService, LifecyclePhase, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { WorkingCopyHistoryTracker } from 'vs/workbench/services/workingCopy/common/workingCopyHistoryTracker'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkingCopyHistoryEntry, IWorkingCopyHistoryEntryDescriptor, IWorkingCopyHistoryEvent, IWorkingCopyHistoryService, MAX_PARALLEL_HISTORY_IO_OPS } from 'vs/workbench/services/workingCopy/common/workingCopyHistory'; import { FileOperationError, FileOperationResult, IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { URI } from 'vs/base/common/uri'; -import { DeferredPromise, Limiter } from 'vs/base/common/async'; +import { DeferredPromise, Limiter, RunOnceScheduler } from 'vs/base/common/async'; import { dirname, extname, isEqual, joinPath } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { hash } from 'vs/base/common/hash'; import { indexOfPath, randomPath } from 'vs/base/common/extpath'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ResourceMap } from 'vs/base/common/map'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { ILabelService } from 'vs/platform/label/common/label'; @@ -29,6 +29,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { SaveSource, SaveSourceRegistry } from 'vs/workbench/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { lastOrDefault } from 'vs/base/common/arrays'; +import { escapeRegExpCharacters } from 'vs/base/common/strings'; interface ISerializedWorkingCopyHistoryModel { readonly version: number; @@ -103,7 +104,7 @@ export class WorkingCopyHistoryModel { this.workingCopyResource = workingCopyResource; this.workingCopyName = this.labelService.getUriBasenameLabel(workingCopyResource); - this.historyEntriesNameMatcher = new RegExp(`[A-Za-z0-9]{4}${extname(workingCopyResource)}`); + this.historyEntriesNameMatcher = new RegExp(`[A-Za-z0-9]{4}${escapeRegExpCharacters(extname(workingCopyResource))}`); // Update locations this.historyEntriesFolder = this.toHistoryEntriesFolder(this.historyHome, workingCopyResource); @@ -785,5 +786,83 @@ export abstract class WorkingCopyHistoryService extends Disposable implements IW } +export class NativeWorkingCopyHistoryService extends WorkingCopyHistoryService { + + private static readonly STORE_ALL_INTERVAL = 5 * 60 * 1000; // 5min + + private readonly isRemotelyStored = typeof this.environmentService.remoteAuthority === 'string'; + + private readonly storeAllCts = this._register(new CancellationTokenSource()); + private readonly storeAllScheduler = this._register(new RunOnceScheduler(() => this.storeAll(this.storeAllCts.token), NativeWorkingCopyHistoryService.STORE_ALL_INTERVAL)); + + constructor( + @IFileService fileService: IFileService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + @ILabelService labelService: ILabelService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @ILogService logService: ILogService, + @IConfigurationService configurationService: IConfigurationService + ) { + super(fileService, remoteAgentService, environmentService, uriIdentityService, labelService, logService, configurationService); + + this.registerListeners(); + } + + private registerListeners(): void { + if (!this.isRemotelyStored) { + + // Local: persist all on shutdown + this.lifecycleService.onWillShutdown(e => this.onWillShutdown(e)); + + // Local: schedule persist on change + this._register(Event.any(this.onDidAddEntry, this.onDidChangeEntry, this.onDidReplaceEntry, this.onDidRemoveEntry)(() => this.onDidChangeModels())); + } + } + + protected getModelOptions(): IWorkingCopyHistoryModelOptions { + return { flushOnChange: this.isRemotelyStored /* because the connection might drop anytime */ }; + } + + private onWillShutdown(e: WillShutdownEvent): void { + + // Dispose the scheduler... + this.storeAllScheduler.dispose(); + this.storeAllCts.dispose(true); + + // ...because we now explicitly store all models + e.join(this.storeAll(e.token), { id: 'join.workingCopyHistory', label: localize('join.workingCopyHistory', "Saving local history") }); + } + + private onDidChangeModels(): void { + if (!this.storeAllScheduler.isScheduled()) { + this.storeAllScheduler.schedule(); + } + } + + private async storeAll(token: CancellationToken): Promise { + const limiter = new Limiter(MAX_PARALLEL_HISTORY_IO_OPS); + const promises = []; + + const models = Array.from(this.models.values()); + for (const model of models) { + promises.push(limiter.queue(async () => { + if (token.isCancellationRequested) { + return; + } + + try { + await model.store(token); + } catch (error) { + this.logService.trace(error); + } + })); + } + + await Promise.all(promises); + } +} + // Register History Tracker Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WorkingCopyHistoryTracker, LifecyclePhase.Restored); diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts index d8cb061b00..b96d029b6d 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; import { Disposable, IDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; @@ -70,6 +70,18 @@ export interface IWorkingCopyService { */ readonly dirtyWorkingCopies: readonly IWorkingCopy[]; + /** + * The number of modified working copies that are registered, + * including scratchpads, which are never dirty. + */ + readonly modifiedCount: number; + + /** + * All working copies with unsaved changes, + * including scratchpads, which are never dirty. + */ + readonly modifiedWorkingCopies: readonly IWorkingCopy[]; + /** * Whether there is any registered working copy that is dirty. */ @@ -272,6 +284,22 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic return this.workingCopies.filter(workingCopy => workingCopy.isDirty()); } + get modifiedCount(): number { + let totalModifiedCount = 0; + + for (const workingCopy of this._workingCopies) { + if (workingCopy.isModified()) { + totalModifiedCount++; + } + } + + return totalModifiedCount; + } + + get modifiedWorkingCopies(): IWorkingCopy[] { + return this.workingCopies.filter(workingCopy => workingCopy.isModified()); + } + isDirty(resource: URI, typeId?: string): boolean { const workingCopies = this.mapResourceToWorkingCopies.get(resource); if (workingCopies) { @@ -297,4 +325,4 @@ export class WorkingCopyService extends Disposable implements IWorkingCopyServic //#endregion } -registerSingleton(IWorkingCopyService, WorkingCopyService, true); +registerSingleton(IWorkingCopyService, WorkingCopyService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts index 38f47a50c6..a582481ca8 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { WorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; import { URI } from 'vs/base/common/uri'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; @@ -39,7 +39,7 @@ export class NativeWorkingCopyBackupService extends WorkingCopyBackupService { } // Register Service -registerSingleton(IWorkingCopyBackupService, NativeWorkingCopyBackupService); +registerSingleton(IWorkingCopyBackupService, NativeWorkingCopyBackupService, InstantiationType.Eager); // Register Backup Tracker Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeWorkingCopyBackupTracker, LifecyclePhase.Starting); diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index ba52561926..5296cb6c72 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -11,11 +11,10 @@ import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/wo import { IWorkingCopy, IWorkingCopyIdentifier, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; 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'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { INativeHostService } from 'vs/platform/native/common/native'; import { WorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/common/workingCopyBackupTracker'; import { ILogService } from 'vs/platform/log/common/log'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -50,64 +49,67 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp protected async onFinalBeforeShutdown(reason: ShutdownReason): Promise { - // Important: we are about to shutdown and handle dirty working copies + // Important: we are about to shutdown and handle modified working copies // and backups. We do not want any pending backup ops to interfer with // this because there is a risk of a backup being scheduled after we have // acknowledged to shutdown and then might end up with partial backups // written to disk, or even empty backups or deletes after writes. // (https://github.com/microsoft/vscode/issues/138055) + this.cancelBackupOperations(); // For the duration of the shutdown handling, suspend backup operations // and only resume after we have handled backups. Similar to above, we // do not want to trigger backup tracking during our shutdown handling // but we must resume, in case of a veto afterwards. + const { resume } = this.suspendBackupOperations(); try { - // Dirty working copies need treatment on shutdown - const dirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; - if (dirtyWorkingCopies.length) { - return await this.onBeforeShutdownWithDirty(reason, dirtyWorkingCopies); + // Modified working copies need treatment on shutdown + const modifiedWorkingCopies = this.workingCopyService.modifiedWorkingCopies; + if (modifiedWorkingCopies.length) { + return await this.onBeforeShutdownWithModified(reason, modifiedWorkingCopies); } - // No dirty working copies + // No modified working copies else { - return await this.onBeforeShutdownWithoutDirty(); + return await this.onBeforeShutdownWithoutModified(); } } finally { resume(); } } - protected async onBeforeShutdownWithDirty(reason: ShutdownReason, dirtyWorkingCopies: readonly IWorkingCopy[]): Promise { + protected async onBeforeShutdownWithModified(reason: ShutdownReason, modifiedWorkingCopies: readonly IWorkingCopy[]): Promise { // If auto save is enabled, save all non-untitled working copies - // and then check again for dirty copies + // and then check again for modified copies + if (this.filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF) { - // Save all dirty working copies + // Save all modified working copies try { await this.doSaveAllBeforeShutdown(false /* not untitled */, SaveReason.AUTO); } catch (error) { - this.logService.error(`[backup tracker] error saving dirty working copies: ${error}`); // guard against misbehaving saves, we handle remaining dirty below + this.logService.error(`[backup tracker] error saving modified working copies: ${error}`); // guard against misbehaving saves, we handle remaining modified below } - // If we still have dirty working copies, we either have untitled ones or working copies that cannot be saved - const remainingDirtyWorkingCopies = this.workingCopyService.dirtyWorkingCopies; - if (remainingDirtyWorkingCopies.length) { - return this.handleDirtyBeforeShutdown(remainingDirtyWorkingCopies, reason); + // If we still have modified working copies, we either have untitled ones or working copies that cannot be saved + const remainingModifiedWorkingCopies = this.workingCopyService.modifiedWorkingCopies; + if (remainingModifiedWorkingCopies.length) { + return this.handleModifiedBeforeShutdown(remainingModifiedWorkingCopies, reason); } - return false; // no veto (there are no remaining dirty working copies) + return this.noVeto([...modifiedWorkingCopies]); // no veto (modified auto-saved) } // Auto save is not enabled - return this.handleDirtyBeforeShutdown(dirtyWorkingCopies, reason); + return this.handleModifiedBeforeShutdown(modifiedWorkingCopies, reason); } - private async handleDirtyBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[], reason: ShutdownReason): Promise { + private async handleModifiedBeforeShutdown(modifiedWorkingCopies: readonly IWorkingCopy[], reason: ShutdownReason): Promise { // Trigger backup if configured and enabled for shutdown reason let backups: IWorkingCopy[] = []; @@ -115,11 +117,11 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp const backup = await this.shouldBackupBeforeShutdown(reason); if (backup) { try { - const backupResult = await this.backupBeforeShutdown(dirtyWorkingCopies); + const backupResult = await this.backupBeforeShutdown(modifiedWorkingCopies); backups = backupResult.backups; backupError = backupResult.error; - if (backups.length === dirtyWorkingCopies.length) { + if (backups.length === modifiedWorkingCopies.length) { return false; // no veto (backup was successful for all working copies) } } catch (error) { @@ -127,7 +129,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp } } - const remainingDirtyWorkingCopies = dirtyWorkingCopies.filter(workingCopy => !backups.includes(workingCopy)); + const remainingModifiedWorkingCopies = modifiedWorkingCopies.filter(workingCopy => !backups.includes(workingCopy)); // We ran a backup but received an error that we show to the user if (backupError) { @@ -137,23 +139,24 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp return false; // do not block shutdown during extension development (https://github.com/microsoft/vscode/issues/115028) } - this.showErrorDialog(localize('backupTrackerBackupFailed', "The following editors with unsaved changes could not be saved to the back up location."), remainingDirtyWorkingCopies, backupError); + this.showErrorDialog(localize('backupTrackerBackupFailed', "The following editors with unsaved changes could not be saved to the back up location."), remainingModifiedWorkingCopies, backupError); return true; // veto (the backup failed) } // Since a backup did not happen, we have to confirm for // the working copies that did not successfully backup + try { - return await this.confirmBeforeShutdown(remainingDirtyWorkingCopies); + return await this.confirmBeforeShutdown(remainingModifiedWorkingCopies); } catch (error) { if (this.environmentService.isExtensionDevelopment) { - this.logService.error(`[backup tracker] error saving or reverting dirty working copies: ${error}`); + this.logService.error(`[backup tracker] error saving or reverting modified working copies: ${error}`); return false; // do not block shutdown during extension development (https://github.com/microsoft/vscode/issues/115028) } - this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following editors with unsaved changes could not be saved or reverted."), remainingDirtyWorkingCopies, error); + this.showErrorDialog(localize('backupTrackerConfirmFailed', "The following editors with unsaved changes could not be saved or reverted."), remainingModifiedWorkingCopies, error); return true; // veto (save or revert failed) } @@ -205,27 +208,27 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp } private showErrorDialog(msg: string, workingCopies: readonly IWorkingCopy[], error?: Error): void { - const dirtyWorkingCopies = workingCopies.filter(workingCopy => workingCopy.isDirty()); + const modifiedWorkingCopies = workingCopies.filter(workingCopy => workingCopy.isModified()); const advice = localize('backupErrorDetails', "Try saving or reverting the editors with unsaved changes first and then try again."); - const detail = dirtyWorkingCopies.length - ? getFileNamesMessage(dirtyWorkingCopies.map(x => x.name)) + '\n' + advice + const detail = modifiedWorkingCopies.length + ? getFileNamesMessage(modifiedWorkingCopies.map(x => x.name)) + '\n' + advice : advice; - this.dialogService.show(Severity.Error, msg, undefined, { detail }); + this.dialogService.error(msg, detail); this.logService.error(error ? `[backup tracker] ${msg}: ${error}` : `[backup tracker] ${msg}`); } - private async backupBeforeShutdown(dirtyWorkingCopies: readonly IWorkingCopy[]): Promise<{ backups: IWorkingCopy[]; error?: Error }> { + private async backupBeforeShutdown(modifiedWorkingCopies: readonly IWorkingCopy[]): Promise<{ backups: IWorkingCopy[]; error?: Error }> { const backups: IWorkingCopy[] = []; let error: Error | undefined = undefined; await this.withProgressAndCancellation(async token => { - // Perform a backup of all dirty working copies unless a backup already exists + // Perform a backup of all modified working copies unless a backup already exists try { - await Promises.settled(dirtyWorkingCopies.map(async workingCopy => { + await Promises.settled(modifiedWorkingCopies.map(async workingCopy => { // Backup exists const contentVersion = this.getContentVersion(workingCopy); @@ -259,46 +262,46 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp return { backups, error }; } - private async confirmBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[]): Promise { + private async confirmBeforeShutdown(modifiedWorkingCopies: IWorkingCopy[]): Promise { // Save - const confirm = await this.fileDialogService.showSaveConfirm(dirtyWorkingCopies.map(workingCopy => workingCopy.name)); + const confirm = await this.fileDialogService.showSaveConfirm(modifiedWorkingCopies.map(workingCopy => workingCopy.name)); if (confirm === ConfirmResult.SAVE) { - const dirtyCountBeforeSave = this.workingCopyService.dirtyCount; + const modifiedCountBeforeSave = this.workingCopyService.modifiedCount; try { - await this.doSaveAllBeforeShutdown(dirtyWorkingCopies, SaveReason.EXPLICIT); + await this.doSaveAllBeforeShutdown(modifiedWorkingCopies, SaveReason.EXPLICIT); } catch (error) { - this.logService.error(`[backup tracker] error saving dirty working copies: ${error}`); // guard against misbehaving saves, we handle remaining dirty below + this.logService.error(`[backup tracker] error saving modified working copies: ${error}`); // guard against misbehaving saves, we handle remaining modified below } - const savedWorkingCopies = dirtyCountBeforeSave - this.workingCopyService.dirtyCount; - if (savedWorkingCopies < dirtyWorkingCopies.length) { + const savedWorkingCopies = modifiedCountBeforeSave - this.workingCopyService.modifiedCount; + if (savedWorkingCopies < modifiedWorkingCopies.length) { return true; // veto (save failed or was canceled) } - return this.noVeto(dirtyWorkingCopies); // no veto (dirty saved) + return this.noVeto(modifiedWorkingCopies); // no veto (modified saved) } // Don't Save else if (confirm === ConfirmResult.DONT_SAVE) { try { - await this.doRevertAllBeforeShutdown(dirtyWorkingCopies); + await this.doRevertAllBeforeShutdown(modifiedWorkingCopies); } catch (error) { - this.logService.error(`[backup tracker] error reverting dirty working copies: ${error}`); // do not block the shutdown on errors from revert + this.logService.error(`[backup tracker] error reverting modified working copies: ${error}`); // do not block the shutdown on errors from revert } - return this.noVeto(dirtyWorkingCopies); // no veto (dirty reverted) + return this.noVeto(modifiedWorkingCopies); // no veto (modified reverted) } // Cancel return true; // veto (user canceled) } - private doSaveAllBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[], reason: SaveReason): Promise; + private doSaveAllBeforeShutdown(modifiedWorkingCopies: IWorkingCopy[], reason: SaveReason): Promise; private doSaveAllBeforeShutdown(includeUntitled: boolean, reason: SaveReason): Promise; private doSaveAllBeforeShutdown(arg1: IWorkingCopy[] | boolean, reason: SaveReason): Promise { - const dirtyWorkingCopies = Array.isArray(arg1) ? arg1 : this.workingCopyService.dirtyWorkingCopies.filter(workingCopy => { + const modifiedWorkingCopies = Array.isArray(arg1) ? arg1 : this.workingCopyService.modifiedWorkingCopies.filter(workingCopy => { if (arg1 === false && (workingCopy.capabilities & WorkingCopyCapabilities.Untitled)) { return false; // skip untitled unless explicitly included } @@ -312,48 +315,40 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp const saveOptions = { skipSaveParticipants: true, reason }; // First save through the editor service if we save all to benefit - // from some extras like switching to untitled dirty editors before saving. + // from some extras like switching to untitled modified editors before saving. + let result: boolean | undefined = undefined; - if (typeof arg1 === 'boolean' || dirtyWorkingCopies.length === this.workingCopyService.dirtyCount) { - result = await this.editorService.saveAll({ includeUntitled: typeof arg1 === 'boolean' ? arg1 : true, ...saveOptions }); + if (typeof arg1 === 'boolean' || modifiedWorkingCopies.length === this.workingCopyService.modifiedCount) { + result = (await this.editorService.saveAll({ includeUntitled: typeof arg1 === 'boolean' ? arg1 : true, ...saveOptions })).success; } - // If we still have dirty working copies, save those directly + // If we still have modified working copies, save those directly // unless the save was not successful (e.g. cancelled) if (result !== false) { - await Promises.settled(dirtyWorkingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.save(saveOptions) : Promise.resolve(true))); + await Promises.settled(modifiedWorkingCopies.map(workingCopy => workingCopy.isModified() ? workingCopy.save(saveOptions) : Promise.resolve(true))); } }, localize('saveBeforeShutdown', "Saving editors with unsaved changes is taking a bit longer...")); } - private doRevertAllBeforeShutdown(dirtyWorkingCopies: IWorkingCopy[]): Promise { + private doRevertAllBeforeShutdown(modifiedWorkingCopies: IWorkingCopy[]): Promise { return this.withProgressAndCancellation(async () => { // Soft revert is good enough on shutdown const revertOptions = { soft: true }; // First revert through the editor service if we revert all - if (dirtyWorkingCopies.length === this.workingCopyService.dirtyCount) { + if (modifiedWorkingCopies.length === this.workingCopyService.modifiedCount) { await this.editorService.revertAll(revertOptions); } - // If we still have dirty working copies, revert those directly - await Promises.settled(dirtyWorkingCopies.map(workingCopy => workingCopy.isDirty() ? workingCopy.revert(revertOptions) : Promise.resolve())); + // If we still have modified working copies, revert those directly + await Promises.settled(modifiedWorkingCopies.map(workingCopy => workingCopy.isModified() ? workingCopy.revert(revertOptions) : Promise.resolve())); }, localize('revertBeforeShutdown', "Reverting editors with unsaved changes is taking a bit longer...")); } - private async noVeto(backupsToDiscard: IWorkingCopyIdentifier[]): Promise { + private onBeforeShutdownWithoutModified(): Promise { - // Discard backups from working copies the - // user either saved or reverted - await this.discardBackupsBeforeShutdown(backupsToDiscard); - - return false; // no veto (no dirty) - } - - private async onBeforeShutdownWithoutDirty(): Promise { - - // We are about to shutdown without dirty editors + // We are about to shutdown without modified editors // and will discard any backups that are still // around that have not been handled depending // on the window state. @@ -370,32 +365,45 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp // future. Since we do not restore workspace/folder // windows with backups, this is fine. - await this.discardBackupsBeforeShutdown({ except: this.contextService.getWorkbenchState() === WorkbenchState.EMPTY ? [] : Array.from(this.unrestoredBackups) }); + return this.noVeto({ except: this.contextService.getWorkbenchState() === WorkbenchState.EMPTY ? [] : Array.from(this.unrestoredBackups) }); + } - return false; // no veto (no dirty) + private noVeto(backupsToDiscard: IWorkingCopyIdentifier[]): Promise; + private noVeto(backupsToKeep: { except: IWorkingCopyIdentifier[] }): Promise; + private async noVeto(arg1: IWorkingCopyIdentifier[] | { except: IWorkingCopyIdentifier[] }): Promise { + + // Discard backups from working copies the + // user either saved or reverted + + await this.discardBackupsBeforeShutdown(arg1); + + return false; // no veto (no modified) } private discardBackupsBeforeShutdown(backupsToDiscard: IWorkingCopyIdentifier[]): Promise; private discardBackupsBeforeShutdown(backupsToKeep: { except: IWorkingCopyIdentifier[] }): Promise; + private discardBackupsBeforeShutdown(backupsToDiscardOrKeep: IWorkingCopyIdentifier[] | { except: IWorkingCopyIdentifier[] }): Promise; private async discardBackupsBeforeShutdown(arg1: IWorkingCopyIdentifier[] | { except: IWorkingCopyIdentifier[] }): Promise { // We never discard any backups before we are ready // and have resolved all backups that exist. This // is important to not loose backups that have not // been handled. + if (!this.isReady) { return; } await this.withProgressAndCancellation(async () => { - // When we shutdown either with no dirty working copies left + // When we shutdown either with no modified working copies left // or with some handled, we start to discard these backups // to free them up. This helps to get rid of stale backups // as reported in https://github.com/microsoft/vscode/issues/92962 // // However, we never want to discard backups that we know // were not restored in the session. + try { if (Array.isArray(arg1)) { await Promises.settled(arg1.map(workingCopy => this.workingCopyBackupService.discardBackup(workingCopy))); diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService.ts index 15d1ed5bbf..fcd4dc48cd 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService.ts @@ -3,99 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize } from 'vs/nls'; -import { Event } from 'vs/base/common/event'; -import { Limiter, RunOnceScheduler } from 'vs/base/common/async'; -import { ILifecycleService, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkingCopyHistoryModelOptions, WorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistoryService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkingCopyHistoryService, MAX_PARALLEL_HISTORY_IO_OPS } from 'vs/workbench/services/workingCopy/common/workingCopyHistory'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; - -export class NativeWorkingCopyHistoryService extends WorkingCopyHistoryService { - - private static readonly STORE_ALL_INTERVAL = 5 * 60 * 1000; // 5min - - private readonly isRemotelyStored = typeof this.environmentService.remoteAuthority === 'string'; - - private readonly storeAllCts = this._register(new CancellationTokenSource()); - private readonly storeAllScheduler = this._register(new RunOnceScheduler(() => this.storeAll(this.storeAllCts.token), NativeWorkingCopyHistoryService.STORE_ALL_INTERVAL)); - - constructor( - @IFileService fileService: IFileService, - @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IUriIdentityService uriIdentityService: IUriIdentityService, - @ILabelService labelService: ILabelService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @ILogService logService: ILogService, - @IConfigurationService configurationService: IConfigurationService - ) { - super(fileService, remoteAgentService, environmentService, uriIdentityService, labelService, logService, configurationService); - - this.registerListeners(); - } - - private registerListeners(): void { - if (!this.isRemotelyStored) { - - // Local: persist all on shutdown - this.lifecycleService.onWillShutdown(e => this.onWillShutdown(e)); - - // Local: schedule persist on change - this._register(Event.any(this.onDidAddEntry, this.onDidChangeEntry, this.onDidReplaceEntry, this.onDidRemoveEntry)(() => this.onDidChangeModels())); - } - } - - protected getModelOptions(): IWorkingCopyHistoryModelOptions { - return { flushOnChange: this.isRemotelyStored /* because the connection might drop anytime */ }; - } - - private onWillShutdown(e: WillShutdownEvent): void { - - // Dispose the scheduler... - this.storeAllScheduler.dispose(); - this.storeAllCts.dispose(true); - - // ...because we now explicitly store all models - e.join(this.storeAll(e.token), { id: 'join.workingCopyHistory', label: localize('join.workingCopyHistory', "Saving local history") }); - } - - private onDidChangeModels(): void { - if (!this.storeAllScheduler.isScheduled()) { - this.storeAllScheduler.schedule(); - } - } - - private async storeAll(token: CancellationToken): Promise { - const limiter = new Limiter(MAX_PARALLEL_HISTORY_IO_OPS); - const promises = []; - - const models = Array.from(this.models.values()); - for (const model of models) { - promises.push(limiter.queue(async () => { - if (token.isCancellationRequested) { - return; - } - - try { - await model.store(token); - } catch (error) { - this.logService.trace(error); - } - })); - } - - await Promise.all(promises); - } -} +import { NativeWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistoryService'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistory'; // Register Service -registerSingleton(IWorkingCopyHistoryService, NativeWorkingCopyHistoryService, true); +registerSingleton(IWorkingCopyHistoryService, NativeWorkingCopyHistoryService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts index c53f87676c..734d585140 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts @@ -15,7 +15,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { basename } from 'vs/base/common/resources'; import { FileChangesEvent, FileChangeType, FileOperationError, FileOperationResult, NotModifiedSinceFileOperationError } from 'vs/platform/files/common/files'; import { SaveReason, SaveSourceRegistry } from 'vs/workbench/common/editor'; -import { Promises } from 'vs/base/common/async'; +import { Promises, timeout } from 'vs/base/common/async'; import { consumeReadable, consumeStream, isReadableStream } from 'vs/base/common/stream'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; @@ -146,7 +146,8 @@ suite('StoredFileWorkingCopy', function () { }); }); - test('dirty', async () => { + test('dirty / modified', async () => { + assert.strictEqual(workingCopy.isModified(), false); assert.strictEqual(workingCopy.isDirty(), false); assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.DIRTY), false); @@ -172,12 +173,14 @@ suite('StoredFileWorkingCopy', function () { workingCopy.model?.updateContents('hello dirty'); assert.strictEqual(contentChangeCounter, 1); + assert.strictEqual(workingCopy.isModified(), true); assert.strictEqual(workingCopy.isDirty(), true); assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.DIRTY), true); assert.strictEqual(changeDirtyCounter, 1); await workingCopy.save(); + assert.strictEqual(workingCopy.isModified(), false); assert.strictEqual(workingCopy.isDirty(), false); assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.DIRTY), false); assert.strictEqual(changeDirtyCounter, 2); @@ -187,25 +190,29 @@ suite('StoredFileWorkingCopy', function () { await workingCopy.resolve({ contents: bufferToStream(VSBuffer.fromString('hello dirty stream')) }); assert.strictEqual(contentChangeCounter, 2); // content of model did not change + assert.strictEqual(workingCopy.isModified(), true); assert.strictEqual(workingCopy.isDirty(), true); assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.DIRTY), true); assert.strictEqual(changeDirtyCounter, 3); await workingCopy.revert({ soft: true }); + assert.strictEqual(workingCopy.isModified(), false); assert.strictEqual(workingCopy.isDirty(), false); assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.DIRTY), false); assert.strictEqual(changeDirtyCounter, 4); - // Dirty from: API - workingCopy.markDirty(); + // Modified from: API + workingCopy.markModified(); + assert.strictEqual(workingCopy.isModified(), true); assert.strictEqual(workingCopy.isDirty(), true); assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.DIRTY), true); assert.strictEqual(changeDirtyCounter, 5); await workingCopy.revert(); + assert.strictEqual(workingCopy.isModified(), false); assert.strictEqual(workingCopy.isDirty(), false); assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.DIRTY), false); assert.strictEqual(changeDirtyCounter, 6); @@ -752,6 +759,39 @@ suite('StoredFileWorkingCopy', function () { assert.strictEqual(participationCounter, 1); }); + test('Save Participant, calling save from within is unsupported but does not explode (sync save)', async function () { + await workingCopy.resolve(); + + await testSaveFromSaveParticipant(workingCopy, false); + }); + + test('Save Participant, calling save from within is unsupported but does not explode (async save)', async function () { + await workingCopy.resolve(); + + await testSaveFromSaveParticipant(workingCopy, true); + }); + + async function testSaveFromSaveParticipant(workingCopy: StoredFileWorkingCopy, async: boolean): Promise { + + assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, false); + + const disposable = accessor.workingCopyFileService.addSaveParticipant({ + participate: async () => { + if (async) { + await timeout(10); + } + + await workingCopy.save({ force: true }); + } + }); + + assert.strictEqual(accessor.workingCopyFileService.hasSaveParticipants, true); + + await workingCopy.save({ force: true }); + + disposable.dispose(); + } + test('revert', async () => { await workingCopy.resolve(); workingCopy.model?.updateContents('hello revert'); diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopyManager.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopyManager.test.ts index 4db2ded0bf..69c080a25b 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopyManager.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopyManager.test.ts @@ -16,6 +16,7 @@ import { TestStoredFileWorkingCopyModel, TestStoredFileWorkingCopyModelFactory } import { CancellationToken } from 'vs/base/common/cancellation'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { isWeb } from 'vs/base/common/platform'; suite('StoredFileWorkingCopyManager', () => { @@ -617,7 +618,7 @@ suite('StoredFileWorkingCopyManager', () => { assert.strictEqual(canDispose2, true); }); - test('pending saves join on shutdown', async () => { + (isWeb ? test.skip : test)('pending saves join on shutdown', async () => { const resource1 = URI.file('/path/index_something1.txt'); const resource2 = URI.file('/path/index_something2.txt'); diff --git a/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopy.test.ts index 3af1213259..1420d575b8 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopy.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopy.test.ts @@ -102,6 +102,7 @@ suite('UntitledFileWorkingCopy', () => { uri, basename(uri), hasAssociatedFilePath, + false, initialValue.length > 0 ? { value: bufferToStream(VSBuffer.fromString(initialValue)) } : undefined, factory, async workingCopy => { await workingCopy.revert(); return true; }, diff --git a/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts b/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts index ae8468e3b3..5e6758e878 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopyManager.test.ts @@ -89,20 +89,24 @@ suite('UntitledFileWorkingCopyManager', () => { for (const workingCopy of [workingCopy1, workingCopy2]) { assert.strictEqual(workingCopy.capabilities, WorkingCopyCapabilities.Untitled); assert.strictEqual(workingCopy.isDirty(), false); + assert.strictEqual(workingCopy.isModified(), false); assert.ok(workingCopy.model); } workingCopy1.model?.updateContents('Hello World'); assert.strictEqual(workingCopy1.isDirty(), true); + assert.strictEqual(workingCopy1.isModified(), true); assert.strictEqual(dirtyCounter, 1); - workingCopy1.model?.updateContents(''); // change to empty clears dirty flag + workingCopy1.model?.updateContents(''); // change to empty clears dirty/modified flags assert.strictEqual(workingCopy1.isDirty(), false); + assert.strictEqual(workingCopy1.isModified(), false); assert.strictEqual(dirtyCounter, 2); workingCopy2.model?.fireContentChangeEvent({ isInitial: false }); assert.strictEqual(workingCopy2.isDirty(), true); + assert.strictEqual(workingCopy2.isModified(), true); assert.strictEqual(dirtyCounter, 3); workingCopy1.dispose(); @@ -118,6 +122,33 @@ suite('UntitledFileWorkingCopyManager', () => { assert.strictEqual(disposeCounter, 2); }); + test('dirty - scratchpads are never dirty', async () => { + let dirtyCounter = 0; + manager.untitled.onDidChangeDirty(e => { + dirtyCounter++; + }); + + const workingCopy1 = await manager.resolve({ + untitledResource: URI.from({ scheme: Schemas.untitled, path: `/myscratchpad` }), + isScratchpad: true + }); + + assert.strictEqual(workingCopy1.resource.scheme, Schemas.untitled); + assert.strictEqual(manager.untitled.workingCopies.length, 1); + + workingCopy1.model?.updateContents('contents'); + assert.strictEqual(workingCopy1.isDirty(), false); + assert.strictEqual(workingCopy1.isModified(), true); + + workingCopy1.model?.fireContentChangeEvent({ isInitial: true }); + assert.strictEqual(workingCopy1.isDirty(), false); + assert.strictEqual(workingCopy1.isModified(), false); + + assert.strictEqual(dirtyCounter, 0); + + workingCopy1.dispose(); + }); + test('resolve - with initial value', async () => { let dirtyCounter = 0; manager.untitled.onDidChangeDirty(e => { @@ -126,14 +157,16 @@ suite('UntitledFileWorkingCopyManager', () => { const workingCopy1 = await manager.untitled.resolve({ contents: { value: bufferToStream(VSBuffer.fromString('Hello World')) } }); + assert.strictEqual(workingCopy1.isModified(), true); assert.strictEqual(workingCopy1.isDirty(), true); assert.strictEqual(dirtyCounter, 1); assert.strictEqual(workingCopy1.model?.contents, 'Hello World'); workingCopy1.dispose(); - const workingCopy2 = await manager.untitled.resolve({ contents: { value: bufferToStream(VSBuffer.fromString('Hello World')), markDirty: true } }); + const workingCopy2 = await manager.untitled.resolve({ contents: { value: bufferToStream(VSBuffer.fromString('Hello World')), markModified: true } }); + assert.strictEqual(workingCopy2.isModified(), true); assert.strictEqual(workingCopy2.isDirty(), true); assert.strictEqual(dirtyCounter, 2); assert.strictEqual(workingCopy2.model?.contents, 'Hello World'); @@ -147,8 +180,9 @@ suite('UntitledFileWorkingCopyManager', () => { dirtyCounter++; }); - const workingCopy = await manager.untitled.resolve({ contents: { value: bufferToStream(VSBuffer.fromString('Hello World')), markDirty: false } }); + const workingCopy = await manager.untitled.resolve({ contents: { value: bufferToStream(VSBuffer.fromString('Hello World')), markModified: false } }); + assert.strictEqual(workingCopy.isModified(), false); assert.strictEqual(workingCopy.isDirty(), false); assert.strictEqual(dirtyCounter, 0); assert.strictEqual(workingCopy.model?.contents, 'Hello World'); diff --git a/src/vs/workbench/services/workingCopy/test/browser/untitledScratchpadWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/untitledScratchpadWorkingCopy.test.ts new file mode 100644 index 0000000000..e345daed47 --- /dev/null +++ b/src/vs/workbench/services/workingCopy/test/browser/untitledScratchpadWorkingCopy.test.ts @@ -0,0 +1,273 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { VSBufferReadableStream, VSBuffer, streamToBuffer, bufferToStream, readableToBuffer, VSBufferReadable } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; +import { basename } from 'vs/base/common/resources'; +import { consumeReadable, consumeStream, isReadable, isReadableStream } from 'vs/base/common/stream'; +import { URI } from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IUntitledFileWorkingCopyModelFactory, UntitledFileWorkingCopy } from 'vs/workbench/services/workingCopy/common/untitledFileWorkingCopy'; +import { TestUntitledFileWorkingCopyModel } from 'vs/workbench/services/workingCopy/test/browser/untitledFileWorkingCopy.test'; +import { TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; + +export class TestUntitledFileWorkingCopyModelFactory implements IUntitledFileWorkingCopyModelFactory { + + async createModel(resource: URI, contents: VSBufferReadableStream, token: CancellationToken): Promise { + return new TestUntitledFileWorkingCopyModel(resource, (await streamToBuffer(contents)).toString()); + } +} + +suite('UntitledScratchpadWorkingCopy', () => { + + const factory = new TestUntitledFileWorkingCopyModelFactory(); + + let disposables: DisposableStore; + const resource = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); + let instantiationService: IInstantiationService; + let accessor: TestServiceAccessor; + let workingCopy: UntitledFileWorkingCopy; + + function createWorkingCopy(uri: URI = resource, hasAssociatedFilePath = false, initialValue = '') { + return new UntitledFileWorkingCopy( + 'testUntitledWorkingCopyType', + uri, + basename(uri), + hasAssociatedFilePath, + true, + initialValue.length > 0 ? { value: bufferToStream(VSBuffer.fromString(initialValue)) } : undefined, + factory, + async workingCopy => { await workingCopy.revert(); return true; }, + accessor.workingCopyService, + accessor.workingCopyBackupService, + accessor.logService + ); + } + + setup(() => { + disposables = new DisposableStore(); + instantiationService = workbenchInstantiationService(undefined, disposables); + accessor = instantiationService.createInstance(TestServiceAccessor); + + workingCopy = createWorkingCopy(); + }); + + teardown(() => { + workingCopy.dispose(); + disposables.dispose(); + }); + + test('registers with working copy service', async () => { + assert.strictEqual(accessor.workingCopyService.workingCopies.length, 1); + + workingCopy.dispose(); + + assert.strictEqual(accessor.workingCopyService.workingCopies.length, 0); + }); + + test('modified - not dirty', async () => { + assert.strictEqual(workingCopy.isDirty(), false); + + let changeDirtyCounter = 0; + workingCopy.onDidChangeDirty(() => { + changeDirtyCounter++; + }); + + let contentChangeCounter = 0; + workingCopy.onDidChangeContent(() => { + contentChangeCounter++; + }); + + await workingCopy.resolve(); + assert.strictEqual(workingCopy.isResolved(), true); + + // Modified from: Model content change + workingCopy.model?.updateContents('hello modified'); + assert.strictEqual(contentChangeCounter, 1); + + assert.strictEqual(workingCopy.isDirty(), false); + assert.strictEqual(workingCopy.isModified(), true); + assert.strictEqual(changeDirtyCounter, 0); + + await workingCopy.save(); + + assert.strictEqual(workingCopy.isDirty(), false); + assert.strictEqual(changeDirtyCounter, 0); + }); + + test('modified - cleared when content event signals isEmpty', async () => { + assert.strictEqual(workingCopy.isModified(), false); + + await workingCopy.resolve(); + + workingCopy.model?.updateContents('hello modified'); + + assert.strictEqual(workingCopy.isModified(), true); + + workingCopy.model?.fireContentChangeEvent({ isInitial: true }); + + assert.strictEqual(workingCopy.isModified(), false); + }); + + test('modified - not cleared when content event signals isEmpty when associated resource', async () => { + workingCopy.dispose(); + workingCopy = createWorkingCopy(resource, true); + + await workingCopy.resolve(); + + workingCopy.model?.updateContents('hello modified'); + assert.strictEqual(workingCopy.isModified(), true); + + workingCopy.model?.fireContentChangeEvent({ isInitial: true }); + + assert.strictEqual(workingCopy.isModified(), true); + }); + + test('revert', async () => { + let revertCounter = 0; + workingCopy.onDidRevert(() => { + revertCounter++; + }); + + let disposeCounter = 0; + workingCopy.onWillDispose(() => { + disposeCounter++; + }); + + await workingCopy.resolve(); + + workingCopy.model?.updateContents('hello modified'); + assert.strictEqual(workingCopy.isModified(), true); + + await workingCopy.revert(); + + assert.strictEqual(revertCounter, 1); + assert.strictEqual(disposeCounter, 1); + assert.strictEqual(workingCopy.isModified(), false); + }); + + test('dispose', async () => { + let disposeCounter = 0; + workingCopy.onWillDispose(() => { + disposeCounter++; + }); + + await workingCopy.resolve(); + workingCopy.dispose(); + + assert.strictEqual(disposeCounter, 1); + }); + + test('backup', async () => { + assert.strictEqual((await workingCopy.backup(CancellationToken.None)).content, undefined); + + await workingCopy.resolve(); + + workingCopy.model?.updateContents('Hello Backup'); + const backup = await workingCopy.backup(CancellationToken.None); + + let backupContents: string | undefined = undefined; + if (isReadableStream(backup.content)) { + backupContents = (await consumeStream(backup.content, chunks => VSBuffer.concat(chunks))).toString(); + } else if (backup.content) { + backupContents = consumeReadable(backup.content, chunks => VSBuffer.concat(chunks)).toString(); + } + + assert.strictEqual(backupContents, 'Hello Backup'); + }); + + test('resolve - without contents', async () => { + assert.strictEqual(workingCopy.isResolved(), false); + assert.strictEqual(workingCopy.hasAssociatedFilePath, false); + assert.strictEqual(workingCopy.model, undefined); + + await workingCopy.resolve(); + + assert.strictEqual(workingCopy.isResolved(), true); + assert.ok(workingCopy.model); + }); + + test('resolve - with initial contents', async () => { + workingCopy.dispose(); + + workingCopy = createWorkingCopy(resource, false, 'Hello Initial'); + + let contentChangeCounter = 0; + workingCopy.onDidChangeContent(() => { + contentChangeCounter++; + }); + + assert.strictEqual(workingCopy.isModified(), true); + + await workingCopy.resolve(); + + assert.strictEqual(workingCopy.isModified(), true); + assert.strictEqual(workingCopy.model?.contents, 'Hello Initial'); + assert.strictEqual(contentChangeCounter, 1); + + workingCopy.model.updateContents('Changed contents'); + + await workingCopy.resolve(); // second resolve should be ignored + assert.strictEqual(workingCopy.model?.contents, 'Changed contents'); + }); + + test('backup - with initial contents uses those even if unresolved', async () => { + workingCopy.dispose(); + + workingCopy = createWorkingCopy(resource, false, 'Hello Initial'); + + assert.strictEqual(workingCopy.isModified(), true); + + const backup = (await workingCopy.backup(CancellationToken.None)).content; + if (isReadableStream(backup)) { + const value = await streamToBuffer(backup as VSBufferReadableStream); + assert.strictEqual(value.toString(), 'Hello Initial'); + } else if (isReadable(backup)) { + const value = readableToBuffer(backup as VSBufferReadable); + assert.strictEqual(value.toString(), 'Hello Initial'); + } else { + assert.fail('Missing untitled backup'); + } + }); + + + test('resolve - with associated resource', async () => { + workingCopy.dispose(); + workingCopy = createWorkingCopy(resource, true); + + await workingCopy.resolve(); + + assert.strictEqual(workingCopy.isModified(), true); + assert.strictEqual(workingCopy.hasAssociatedFilePath, true); + }); + + test('resolve - with backup', async () => { + await workingCopy.resolve(); + workingCopy.model?.updateContents('Hello Backup'); + + const backup = await workingCopy.backup(CancellationToken.None); + await accessor.workingCopyBackupService.backup(workingCopy, backup.content, undefined, backup.meta); + + assert.strictEqual(accessor.workingCopyBackupService.hasBackupSync(workingCopy), true); + + workingCopy.dispose(); + + workingCopy = createWorkingCopy(); + + let contentChangeCounter = 0; + workingCopy.onDidChangeContent(() => { + contentChangeCounter++; + }); + + await workingCopy.resolve(); + + assert.strictEqual(workingCopy.isModified(), true); + assert.strictEqual(workingCopy.model?.contents, 'Hello Backup'); + assert.strictEqual(contentChangeCounter, 1); + }); +}); diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts index 4a4daf8729..6cc7f57fe0 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyBackupTracker.test.ts @@ -31,7 +31,6 @@ import { isWindows } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; suite('WorkingCopyBackupTracker (browser)', function () { let accessor: TestServiceAccessor; @@ -70,7 +69,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { return this.unrestoredBackups; } - override async restoreBackups(handler: IWorkingCopyEditorHandler): Promise { + async testRestoreBackups(handler: IWorkingCopyEditorHandler): Promise { return super.restoreBackups(handler); } } @@ -243,7 +242,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { let isOpenCounter = 0; let createEditorCounter = 0; - await tracker.restoreBackups({ + await tracker.testRestoreBackups({ handles: workingCopy => { handlesCounter++; @@ -280,7 +279,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { test('Restore backups (basics, none handled)', async function () { const [tracker, accessor, disposables] = await restoreBackupsInit(); - await tracker.restoreBackups({ + await tracker.testRestoreBackups({ handles: workingCopy => false, isOpen: (workingCopy, editor) => { throw new Error('unexpected'); }, createEditor: workingCopy => { throw new Error('unexpected'); } @@ -296,7 +295,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { const [tracker, , disposables] = await restoreBackupsInit(); try { - await tracker.restoreBackups({ + await tracker.testRestoreBackups({ handles: workingCopy => true, isOpen: (workingCopy, editor) => { throw new Error('unexpected'); }, createEditor: workingCopy => { throw new Error('unexpected'); } @@ -313,7 +312,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { test('Restore backups (multiple handlers)', async function () { const [tracker, accessor, disposables] = await restoreBackupsInit(); - const firstHandler = tracker.restoreBackups({ + const firstHandler = tracker.testRestoreBackups({ handles: workingCopy => { return workingCopy.typeId === 'testBackupTypeId'; }, @@ -325,7 +324,7 @@ suite('WorkingCopyBackupTracker (browser)', function () { } }); - const secondHandler = tracker.restoreBackups({ + const secondHandler = tracker.testRestoreBackups({ handles: workingCopy => { return workingCopy.typeId.length === 0; }, @@ -362,12 +361,12 @@ suite('WorkingCopyBackupTracker (browser)', function () { const editor1 = accessor.instantiationService.createInstance(TestUntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); const editor2 = accessor.instantiationService.createInstance(TestUntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); - await accessor.editorService.openEditors([{ editor: editor1, options: { override: EditorResolution.DISABLED } }, { editor: editor2, options: { override: EditorResolution.DISABLED } }]); + await accessor.editorService.openEditors([{ editor: editor1 }, { editor: editor2 }]); editor1.resolved = false; editor2.resolved = false; - await tracker.restoreBackups({ + await tracker.testRestoreBackups({ handles: workingCopy => { handlesCounter++; diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyEditorService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyEditorService.test.ts index b2619139d7..12a75cf832 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyEditorService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyEditorService.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { EditorResolution } from 'vs/platform/editor/common/editor'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -78,7 +77,7 @@ suite('WorkingCopyEditorService', () => { const editor1 = instantiationService.createInstance(UntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); const editor2 = instantiationService.createInstance(UntitledTextEditorInput, accessor.untitledTextEditorService.create({ initialValue: 'foo' })); - await editorService.openEditors([{ editor: editor1, options: { override: EditorResolution.DISABLED } }, { editor: editor2, options: { override: EditorResolution.DISABLED } }]); + await editorService.openEditors([{ editor: editor1 }, { editor: editor2 }]); assert.ok(service.findEditor(testWorkingCopy)); diff --git a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts index 4b200c93d5..22d7b180e6 100644 --- a/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/browser/workingCopyFileService.test.ts @@ -8,7 +8,7 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { toResource } from 'vs/base/test/common/utils'; -import { workbenchInstantiationService, TestServiceAccessor, TestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; +import { workbenchInstantiationService, TestServiceAccessor, ITestTextFileEditorModelManager } from 'vs/workbench/test/browser/workbenchTestServices'; import { URI } from 'vs/base/common/uri'; import { FileOperation } from 'vs/platform/files/common/files'; import { TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; @@ -57,7 +57,7 @@ suite('WorkingCopyFileService', () => { test('move - source identical to target', async function () { const sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(sourceModel.resource, sourceModel); + (accessor.textFileService.files).add(sourceModel.resource, sourceModel); const eventCounter = await testEventsMoveOrCopy([{ file: { source: sourceModel.resource, target: sourceModel.resource }, overwrite: true }], true); @@ -69,9 +69,9 @@ suite('WorkingCopyFileService', () => { const sourceModel1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file1.txt'), 'utf8', undefined); const sourceModel2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file2.txt'), 'utf8', undefined); const targetModel2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target2.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(sourceModel1.resource, sourceModel1); - (accessor.textFileService.files).add(sourceModel2.resource, sourceModel2); - (accessor.textFileService.files).add(targetModel2.resource, targetModel2); + (accessor.textFileService.files).add(sourceModel1.resource, sourceModel1); + (accessor.textFileService.files).add(sourceModel2.resource, sourceModel2); + (accessor.textFileService.files).add(targetModel2.resource, targetModel2); const eventCounter = await testEventsMoveOrCopy([ { file: { source: sourceModel1.resource, target: sourceModel1.resource }, overwrite: true }, @@ -101,7 +101,7 @@ suite('WorkingCopyFileService', () => { test('copy - source identical to target', async function () { const sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(sourceModel.resource, sourceModel); + (accessor.textFileService.files).add(sourceModel.resource, sourceModel); const eventCounter = await testEventsMoveOrCopy([{ file: { source: sourceModel.resource, target: sourceModel.resource }, overwrite: true }]); @@ -113,9 +113,9 @@ suite('WorkingCopyFileService', () => { const sourceModel1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file1.txt'), 'utf8', undefined); const sourceModel2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file2.txt'), 'utf8', undefined); const targetModel2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file_target2.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(sourceModel1.resource, sourceModel1); - (accessor.textFileService.files).add(sourceModel2.resource, sourceModel2); - (accessor.textFileService.files).add(targetModel2.resource, targetModel2); + (accessor.textFileService.files).add(sourceModel1.resource, sourceModel1); + (accessor.textFileService.files).add(sourceModel2.resource, sourceModel2); + (accessor.textFileService.files).add(targetModel2.resource, targetModel2); const eventCounter = await testEventsMoveOrCopy([ { file: { source: sourceModel1.resource, target: sourceModel1.resource }, overwrite: true }, @@ -142,10 +142,10 @@ suite('WorkingCopyFileService', () => { test('getDirty', async function () { const model1 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-1.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model1.resource, model1); + (accessor.textFileService.files).add(model1.resource, model1); const model2 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-2.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model2.resource, model2); + (accessor.textFileService.files).add(model2.resource, model2); let dirty = accessor.workingCopyFileService.getDirty(model1.resource); assert.strictEqual(dirty.length, 0); @@ -173,7 +173,7 @@ suite('WorkingCopyFileService', () => { test('registerWorkingCopyProvider', async function () { const model1 = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/file-1.txt'), 'utf8', undefined); - (accessor.textFileService.files).add(model1.resource, model1); + (accessor.textFileService.files).add(model1.resource, model1); await model1.resolve(); model1.textEditorModel!.setValue('foo'); @@ -328,8 +328,8 @@ suite('WorkingCopyFileService', () => { const models = await Promise.all(files.map(async ({ source, target }, i) => { const sourceModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, source, 'utf8', undefined); const targetModel: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, target, 'utf8', undefined); - (accessor.textFileService.files).add(sourceModel.resource, sourceModel); - (accessor.textFileService.files).add(targetModel.resource, targetModel); + (accessor.textFileService.files).add(sourceModel.resource, sourceModel); + (accessor.textFileService.files).add(targetModel.resource, targetModel); await sourceModel.resolve(); sourceModel.textEditorModel!.setValue('foo' + i); @@ -422,7 +422,7 @@ suite('WorkingCopyFileService', () => { const models = await Promise.all(resources.map(async resource => { const model = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); + (accessor.textFileService.files).add(model.resource, model); await model.resolve(); model!.textEditorModel!.setValue('foo'); @@ -482,7 +482,7 @@ suite('WorkingCopyFileService', () => { async function testCreate(resource: URI, contents: VSBuffer) { const model = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8', undefined); - (accessor.textFileService.files).add(model.resource, model); + (accessor.textFileService.files).add(model.resource, model); await model.resolve(); model!.textEditorModel!.setValue('foo'); diff --git a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts index 20d4a7bb34..17781f30c7 100644 --- a/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/common/workingCopyService.test.ts @@ -48,6 +48,7 @@ suite('WorkingCopyService', () => { assert.strictEqual(onDidRegister.length, 1); assert.strictEqual(onDidRegister[0], copy1); assert.strictEqual(service.dirtyCount, 0); + assert.strictEqual(service.modifiedCount, 0); assert.strictEqual(service.isDirty(resource1), false); assert.strictEqual(service.has(resource1), true); assert.strictEqual(service.has(copy1), true); @@ -65,6 +66,9 @@ suite('WorkingCopyService', () => { assert.strictEqual(service.dirtyCount, 1); assert.strictEqual(service.dirtyWorkingCopies.length, 1); assert.strictEqual(service.dirtyWorkingCopies[0], copy1); + assert.strictEqual(service.modifiedCount, 1); + assert.strictEqual(service.modifiedWorkingCopies.length, 1); + assert.strictEqual(service.modifiedWorkingCopies[0], copy1); assert.strictEqual(service.workingCopies.length, 1); assert.strictEqual(service.workingCopies[0], copy1); assert.strictEqual(service.isDirty(resource1), true); diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/fixtures/binary.txt b/src/vs/workbench/services/workingCopy/test/electron-browser/fixtures/binary.txt deleted file mode 100644 index fc30693d79..0000000000 Binary files a/src/vs/workbench/services/workingCopy/test/electron-browser/fixtures/binary.txt and /dev/null differ diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts similarity index 64% rename from src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts index 547db40365..db52fe5966 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupService.test.ts @@ -5,65 +5,99 @@ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; -import { tmpdir } from 'os'; -import { createHash } from 'crypto'; import { insert } from 'vs/base/common/arrays'; import { hash } from 'vs/base/common/hash'; -import { isEqual } from 'vs/base/common/resources'; -import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; -import { dirname, join } from 'vs/base/common/path'; -import { Promises, readdirSync } from 'vs/base/node/pfs'; +import { isEqual, joinPath, dirname } from 'vs/base/common/resources'; +import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { WorkingCopyBackupsModel, hashIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; import { createTextModel } from 'vs/editor/test/common/testTextModel'; -import { flakySuite, getPathFromAmdModule, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { Schemas } from 'vs/base/common/network'; 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 { LogLevel, NullLogService } from 'vs/platform/log/common/log'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { toBufferOrReadable } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; import { NativeWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService'; import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; import { bufferToReadable, bufferToStream, streamToBuffer, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; -import { TestNativeWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; import { TestLifecycleService, toTypedWorkingCopyId, toUntypedWorkingCopyId } from 'vs/workbench/test/browser/workbenchTestServices'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { consumeStream } from 'vs/base/common/stream'; import { TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { generateUuid } from 'vs/base/common/uuid'; +import { INativeWindowConfiguration } from 'vs/platform/window/common/window'; +import product from 'vs/platform/product/common/product'; -class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { +const homeDir = URI.file('home').with({ scheme: Schemas.inMemory }); +const tmpDir = URI.file('tmp').with({ scheme: Schemas.inMemory }); +const NULL_PROFILE = { + name: '', + id: '', + shortName: '', + isDefault: false, + location: homeDir, + settingsResource: joinPath(homeDir, 'settings.json'), + globalStorageHome: joinPath(homeDir, 'globalStorage'), + keybindingsResource: joinPath(homeDir, 'keybindings.json'), + tasksResource: joinPath(homeDir, 'tasks.json'), + snippetsHome: joinPath(homeDir, 'snippets'), + extensionsResource: joinPath(homeDir, 'extensions.json'), + cacheHome: joinPath(homeDir, 'cache') +}; - constructor(testDir: string, backupPath: string) { - super({ ...TestNativeWindowConfiguration, backupPath, 'user-data-dir': testDir }, TestProductService); +const TestNativeWindowConfiguration: INativeWindowConfiguration = { + windowId: 0, + machineId: 'testMachineId', + logLevel: LogLevel.Error, + loggers: { global: [], window: [] }, + mainPid: 0, + appRoot: '', + userEnv: {}, + execPath: process.execPath, + perfMarks: [], + colorScheme: { dark: true, highContrast: false }, + os: { release: 'unknown', hostname: 'unknown' }, + product, + homeDir: homeDir.fsPath, + tmpDir: tmpDir.fsPath, + userDataDir: joinPath(homeDir, product.nameShort).fsPath, + profiles: { profile: NULL_PROFILE, all: [NULL_PROFILE], home: homeDir }, + _: [] +}; + +export class TestNativeWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { + + constructor(testDir: URI, backupPath: URI) { + super({ ...TestNativeWindowConfiguration, backupPath: backupPath.fsPath, 'user-data-dir': testDir.fsPath }, TestProductService); } } export class NodeTestWorkingCopyBackupService extends NativeWorkingCopyBackupService { - override readonly fileService: IFileService; - private backupResourceJoiners: Function[]; private discardBackupJoiners: Function[]; discardedBackups: IWorkingCopyIdentifier[]; discardedAllBackups: boolean; private pendingBackupsArr: Promise[]; - private diskFileSystemProvider: DiskFileSystemProvider; - constructor(testDir: string, workspaceBackupPath: string) { - const environmentService = new TestWorkbenchEnvironmentService(testDir, workspaceBackupPath); + readonly _fileService: IFileService; + + constructor(testDir: URI, workspaceBackupPath: URI) { + const environmentService = new TestNativeWorkbenchEnvironmentService(testDir, workspaceBackupPath); const logService = new NullLogService(); const fileService = new FileService(logService); const lifecycleService = new TestLifecycleService(); super(environmentService, fileService, logService, lifecycleService); - this.diskFileSystemProvider = new DiskFileSystemProvider(logService); - fileService.registerProvider(Schemas.file, this.diskFileSystemProvider); - fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, this.diskFileSystemProvider, Schemas.vscodeUserData, logService)); + const fsp = new InMemoryFileSystemProvider(); + fileService.registerProvider(Schemas.inMemory, fsp); + fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, fsp, Schemas.vscodeUserData, logService)); + + this._fileService = fileService; - this.fileService = fileService; this.backupResourceJoiners = []; this.discardBackupJoiners = []; this.discardedBackups = []; @@ -71,6 +105,10 @@ export class NodeTestWorkingCopyBackupService extends NativeWorkingCopyBackupSer this.discardedAllBackups = false; } + testGetFileService(): IFileService { + return this.fileService; + } + async waitForAllBackups(): Promise { await Promise.all(this.pendingBackupsArr); } @@ -120,20 +158,17 @@ export class NodeTestWorkingCopyBackupService extends NativeWorkingCopyBackupSer return fileContents.value.toString(); } - - dispose() { - this.diskFileSystemProvider.dispose(); - } } -flakySuite('WorkingCopyBackupService', () => { +suite('WorkingCopyBackupService', () => { - let testDir: string; - let backupHome: string; - let workspacesJsonPath: string; - let workspaceBackupPath: string; + let testDir: URI; + let backupHome: URI; + let workspacesJsonPath: URI; + let workspaceBackupPath: URI; let service: NodeTestWorkingCopyBackupService; + let fileService: IFileService; const workspaceResource = URI.file(isWindows ? 'c:\\workspace' : '/workspace'); const fooFile = URI.file(isWindows ? 'c:\\Foo' : '/Foo'); @@ -144,21 +179,17 @@ flakySuite('WorkingCopyBackupService', () => { const untitledFile = URI.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); setup(async () => { - testDir = getRandomTestPath(tmpdir(), 'vsctests', 'workingcopybackupservice'); - backupHome = join(testDir, 'Backups'); - workspacesJsonPath = join(backupHome, 'workspaces.json'); - workspaceBackupPath = join(backupHome, hash(workspaceResource.fsPath).toString(16)); + testDir = URI.file(join(generateUuid(), 'vsctests', 'workingcopybackupservice')).with({ scheme: Schemas.inMemory }); + backupHome = joinPath(testDir, 'Backups'); + workspacesJsonPath = joinPath(backupHome, 'workspaces.json'); + workspaceBackupPath = joinPath(backupHome, hash(workspaceResource.fsPath).toString(16)); service = new NodeTestWorkingCopyBackupService(testDir, workspaceBackupPath); + fileService = service._fileService; - await Promises.mkdir(backupHome, { recursive: true }); + await fileService.createFolder(backupHome); - return Promises.writeFile(workspacesJsonPath, ''); - }); - - teardown(() => { - service.dispose(); - return Promises.rm(testDir); + return fileService.writeFile(workspacesJsonPath, VSBuffer.fromString('')); }); suite('hashIdentifier', () => { @@ -275,13 +306,13 @@ flakySuite('WorkingCopyBackupService', () => { // No Type ID let backupId = toUntypedWorkingCopyId(backupResource); let filePathHash = hashIdentifier(backupId); - let expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.vscodeUserData }).toString(); + let expectedPath = joinPath(backupHome, workspaceHash, Schemas.file, filePathHash).with({ scheme: Schemas.vscodeUserData }).toString(); assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); // With Type ID backupId = toTypedWorkingCopyId(backupResource); filePathHash = hashIdentifier(backupId); - expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.file, filePathHash)).with({ scheme: Schemas.vscodeUserData }).toString(); + expectedPath = joinPath(backupHome, workspaceHash, Schemas.file, filePathHash).with({ scheme: Schemas.vscodeUserData }).toString(); assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); }); @@ -294,13 +325,13 @@ flakySuite('WorkingCopyBackupService', () => { // No Type ID let backupId = toUntypedWorkingCopyId(backupResource); let filePathHash = hashIdentifier(backupId); - let expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.vscodeUserData }).toString(); + let expectedPath = joinPath(backupHome, workspaceHash, Schemas.untitled, filePathHash).with({ scheme: Schemas.vscodeUserData }).toString(); assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); // With Type ID backupId = toTypedWorkingCopyId(backupResource); filePathHash = hashIdentifier(backupId); - expectedPath = URI.file(join(backupHome, workspaceHash, Schemas.untitled, filePathHash)).with({ scheme: Schemas.vscodeUserData }).toString(); + expectedPath = joinPath(backupHome, workspaceHash, Schemas.untitled, filePathHash).with({ scheme: Schemas.vscodeUserData }).toString(); assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); }); @@ -313,13 +344,13 @@ flakySuite('WorkingCopyBackupService', () => { // No Type ID let backupId = toUntypedWorkingCopyId(backupResource); let filePathHash = hashIdentifier(backupId); - let expectedPath = URI.file(join(backupHome, workspaceHash, 'custom', filePathHash)).with({ scheme: Schemas.vscodeUserData }).toString(); + let expectedPath = joinPath(backupHome, workspaceHash, 'custom', filePathHash).with({ scheme: Schemas.vscodeUserData }).toString(); assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); // With Type ID backupId = toTypedWorkingCopyId(backupResource); filePathHash = hashIdentifier(backupId); - expectedPath = URI.file(join(backupHome, workspaceHash, 'custom', filePathHash)).with({ scheme: Schemas.vscodeUserData }).toString(); + expectedPath = joinPath(backupHome, workspaceHash, 'custom', filePathHash).with({ scheme: Schemas.vscodeUserData }).toString(); assert.strictEqual(service.toBackupResource(backupId).toString(), expectedPath); }); }); @@ -341,111 +372,111 @@ flakySuite('WorkingCopyBackupService', () => { service.joinBackups().then(() => backupJoined = true); const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); const backupPromise = service.backup(identifier); assert.strictEqual(backupJoined, false); await backupPromise; assert.strictEqual(backupJoined, true); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier)); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier)); assert.ok(service.hasBackupSync(identifier)); }); test('no text', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await service.backup(identifier); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier)); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier)); assert.ok(service.hasBackupSync(identifier)); }); test('text file', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, 'test')); assert.ok(service.hasBackupSync(identifier)); }); test('text file (with version)', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test')), 666); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, 'test')); assert.ok(!service.hasBackupSync(identifier, 555)); assert.ok(service.hasBackupSync(identifier, 666)); }); test('text file (with meta)', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); const meta = { etag: '678', orphaned: true }; await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test')), undefined, meta); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test', meta)); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, 'test', meta)); assert.ok(service.hasBackupSync(identifier)); }); test('text file with whitespace in name and type (with meta)', async () => { const fileWithSpace = URI.file(isWindows ? 'c:\\Foo \n Bar' : '/Foo \n Bar'); const identifier = toTypedWorkingCopyId(fileWithSpace, ' test id \n'); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); const meta = { etag: '678 \n k', orphaned: true }; await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test')), undefined, meta); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test', meta)); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, 'test', meta)); assert.ok(service.hasBackupSync(identifier)); }); test('text file with unicode character in name and type (with meta)', async () => { const fileWithUnicode = URI.file(isWindows ? 'c:\\soð’€…meà „' : '/soð’€…meà „'); const identifier = toTypedWorkingCopyId(fileWithUnicode, ' test soð’€…meà „ id \n'); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); const meta = { etag: '678soð’€…meà „', orphaned: true }; await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test')), undefined, meta); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test', meta)); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, 'test', meta)); assert.ok(service.hasBackupSync(identifier)); }); test('untitled file', async () => { const identifier = toUntypedWorkingCopyId(untitledFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'untitled'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, 'test')); assert.ok(service.hasBackupSync(identifier)); }); test('text file (readable)', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); const model = createTextModel('test'); await service.backup(identifier, toBufferOrReadable(model.createSnapshot())); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, 'test')); assert.ok(service.hasBackupSync(identifier)); model.dispose(); @@ -453,13 +484,13 @@ flakySuite('WorkingCopyBackupService', () => { test('untitled file (readable)', async () => { const identifier = toUntypedWorkingCopyId(untitledFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); const model = createTextModel('test'); await service.backup(identifier, toBufferOrReadable(model.createSnapshot())); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, 'test')); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'untitled'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, 'test')); model.dispose(); }); @@ -481,25 +512,25 @@ flakySuite('WorkingCopyBackupService', () => { async function testLargeTextFile(largeString: string, buffer: VSBufferReadable | VSBufferReadableStream) { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await service.backup(identifier, buffer, undefined, { largeTest: true }); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, largeString, { largeTest: true })); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, largeString, { largeTest: true })); assert.ok(service.hasBackupSync(identifier)); } test('untitled file (large file, readable)', async () => { const identifier = toUntypedWorkingCopyId(untitledFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); const largeString = (new Array(30 * 1024)).join('Large String\n'); const model = createTextModel(largeString); await service.backup(identifier, toBufferOrReadable(model.createSnapshot())); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier, largeString)); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'untitled'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier, largeString)); assert.ok(service.hasBackupSync(identifier)); model.dispose(); @@ -507,20 +538,20 @@ flakySuite('WorkingCopyBackupService', () => { test('cancellation', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); const cts = new CancellationTokenSource(); const promise = service.backup(identifier, undefined, undefined, undefined, cts.token); cts.cancel(); await promise; - assert.strictEqual(existsSync(backupPath), false); + assert.strictEqual((await fileService.exists(backupPath)), false); assert.ok(!service.hasBackupSync(identifier)); }); test('multiple', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await Promise.all([ service.backup(identifier), @@ -529,9 +560,9 @@ flakySuite('WorkingCopyBackupService', () => { service.backup(identifier) ]); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readFileSync(backupPath).toString(), toExpectedPreamble(identifier)); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.readFile(backupPath)).value.toString(), toExpectedPreamble(identifier)); assert.ok(service.hasBackupSync(identifier)); }); @@ -546,12 +577,12 @@ flakySuite('WorkingCopyBackupService', () => { service.backup(backupId3) ]); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 3); for (const backupId of [backupId1, backupId2, backupId3]) { - const fooBackupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), toExpectedPreamble(backupId)); + const fooBackupPath = joinPath(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + assert.strictEqual((await fileService.exists(fooBackupPath)), true); + assert.strictEqual((await fileService.readFile(fooBackupPath)).value.toString(), toExpectedPreamble(backupId)); assert.ok(service.hasBackupSync(backupId)); } }); @@ -561,10 +592,10 @@ flakySuite('WorkingCopyBackupService', () => { test('joining', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); assert.ok(service.hasBackupSync(identifier)); let backupJoined = false; @@ -575,35 +606,35 @@ flakySuite('WorkingCopyBackupService', () => { await discardBackupPromise; assert.strictEqual(backupJoined, true); - assert.strictEqual(existsSync(backupPath), false); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 0); + assert.strictEqual((await fileService.exists(backupPath)), false); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 0); assert.ok(!service.hasBackupSync(identifier)); }); test('text file', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); assert.ok(service.hasBackupSync(identifier)); await service.discardBackup(identifier); - assert.strictEqual(existsSync(backupPath), false); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 0); + assert.strictEqual((await fileService.exists(backupPath)), false); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 0); assert.ok(!service.hasBackupSync(identifier)); }); test('untitled file', async () => { const identifier = toUntypedWorkingCopyId(untitledFile); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); await service.backup(identifier, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'untitled'))).children?.length, 1); await service.discardBackup(identifier); - assert.strictEqual(existsSync(backupPath), false); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 0); + assert.strictEqual((await fileService.exists(backupPath)), false); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'untitled'))).children?.length, 0); }); test('multiple same resource, different type id', async () => { @@ -617,14 +648,14 @@ flakySuite('WorkingCopyBackupService', () => { service.backup(backupId3) ]); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 3); for (const backupId of [backupId1, backupId2, backupId3]) { - const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + const backupPath = joinPath(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); await service.discardBackup(backupId); - assert.strictEqual(existsSync(backupPath), false); + assert.strictEqual((await fileService.exists(backupPath)), false); } - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 0); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 0); }); }); @@ -635,39 +666,39 @@ flakySuite('WorkingCopyBackupService', () => { const backupId3 = toTypedWorkingCopyId(barFile); await service.backup(backupId1, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); await service.backup(backupId2, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 2); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 2); await service.backup(backupId3, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 3); await service.discardBackups(); for (const backupId of [backupId1, backupId2, backupId3]) { - const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); - assert.strictEqual(existsSync(backupPath), false); + const backupPath = joinPath(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + assert.strictEqual((await fileService.exists(backupPath)), false); } - assert.strictEqual(existsSync(join(workspaceBackupPath, 'file')), false); + assert.strictEqual((await fileService.exists(joinPath(workspaceBackupPath, 'file'))), false); }); test('untitled file', async () => { const backupId = toUntypedWorkingCopyId(untitledFile); - const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + const backupPath = joinPath(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); await service.backup(backupId, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'untitled'))).children?.length, 1); await service.discardBackups(); - assert.strictEqual(existsSync(backupPath), false); - assert.strictEqual(existsSync(join(workspaceBackupPath, 'untitled')), false); + assert.strictEqual((await fileService.exists(backupPath)), false); + assert.strictEqual((await fileService.exists(joinPath(workspaceBackupPath, 'untitled'))), false); }); test('can backup after discarding all', async () => { await service.discardBackups(); await service.backup(toUntypedWorkingCopyId(untitledFile), bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(existsSync(workspaceBackupPath), true); + assert.strictEqual((await fileService.exists(workspaceBackupPath)), true); }); }); @@ -678,43 +709,43 @@ flakySuite('WorkingCopyBackupService', () => { const backupId3 = toTypedWorkingCopyId(barFile); await service.backup(backupId1, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 1); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 1); await service.backup(backupId2, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 2); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 2); await service.backup(backupId3, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'file')).length, 3); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'file'))).children?.length, 3); await service.discardBackups({ except: [backupId2, backupId3] }); - let backupPath = join(workspaceBackupPath, backupId1.resource.scheme, hashIdentifier(backupId1)); - assert.strictEqual(existsSync(backupPath), false); + let backupPath = joinPath(workspaceBackupPath, backupId1.resource.scheme, hashIdentifier(backupId1)); + assert.strictEqual((await fileService.exists(backupPath)), false); - backupPath = join(workspaceBackupPath, backupId2.resource.scheme, hashIdentifier(backupId2)); - assert.strictEqual(existsSync(backupPath), true); + backupPath = joinPath(workspaceBackupPath, backupId2.resource.scheme, hashIdentifier(backupId2)); + assert.strictEqual((await fileService.exists(backupPath)), true); - backupPath = join(workspaceBackupPath, backupId3.resource.scheme, hashIdentifier(backupId3)); - assert.strictEqual(existsSync(backupPath), true); + backupPath = joinPath(workspaceBackupPath, backupId3.resource.scheme, hashIdentifier(backupId3)); + assert.strictEqual((await fileService.exists(backupPath)), true); await service.discardBackups({ except: [backupId1] }); for (const backupId of [backupId1, backupId2, backupId3]) { - const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); - assert.strictEqual(existsSync(backupPath), false); + const backupPath = joinPath(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + assert.strictEqual((await fileService.exists(backupPath)), false); } }); test('untitled file', async () => { const backupId = toUntypedWorkingCopyId(untitledFile); - const backupPath = join(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); + const backupPath = joinPath(workspaceBackupPath, backupId.resource.scheme, hashIdentifier(backupId)); await service.backup(backupId, bufferToReadable(VSBuffer.fromString('test'))); - assert.strictEqual(existsSync(backupPath), true); - assert.strictEqual(readdirSync(join(workspaceBackupPath, 'untitled')).length, 1); + assert.strictEqual((await fileService.exists(backupPath)), true); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, 'untitled'))).children?.length, 1); await service.discardBackups({ except: [backupId] }); - assert.strictEqual(existsSync(backupPath), true); + assert.strictEqual((await fileService.exists(backupPath)), true); }); }); @@ -1014,14 +1045,14 @@ flakySuite('WorkingCopyBackupService', () => { await service.backup(identifier, bufferToReadable(VSBuffer.fromString(contents)), 1, meta); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); - const fileContents = readFileSync(backupPath).toString(); + const fileContents = (await fileService.readFile(backupPath)).value.toString(); assert.strictEqual(fileContents.indexOf(identifier.resource.toString()), 0); const metaIndex = fileContents.indexOf('{'); const newFileContents = fileContents.substring(0, metaIndex) + '{{' + fileContents.substr(metaIndex); - writeFileSync(backupPath, newFileContents); + await fileService.writeFile(backupPath, VSBuffer.fromString(newFileContents)); const backup = await service.resolve(identifier); assert.ok(backup); @@ -1051,7 +1082,7 @@ flakySuite('WorkingCopyBackupService', () => { await service.backup(identifier, bufferToReadable(VSBuffer.fromString(contents)), 1, meta); - const backupPath = join(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); + const backupPath = joinPath(workspaceBackupPath, identifier.resource.scheme, hashIdentifier(identifier)); // Simulate the condition of the backups model loading initially without // meta data information and then getting the meta data updated on the @@ -1061,15 +1092,15 @@ flakySuite('WorkingCopyBackupService', () => { // This is not really something that would happen in real life because any // backup that is made via backup service will update the model accordingly. - const originalFileContents = readFileSync(backupPath).toString(); - writeFileSync(backupPath, originalFileContents.replace(meta.etag, updatedMeta.etag)); + const originalFileContents = (await fileService.readFile(backupPath)).value.toString(); + await fileService.writeFile(backupPath, VSBuffer.fromString(originalFileContents.replace(meta.etag, updatedMeta.etag))); await service.resolve(identifier); assert.strictEqual(service.hasBackupSync(identifier, undefined, meta), false); assert.strictEqual(service.hasBackupSync(identifier, undefined, updatedMeta), true); - writeFileSync(backupPath, originalFileContents); + await fileService.writeFile(backupPath, VSBuffer.fromString(originalFileContents)); await service.getBackups(); @@ -1085,7 +1116,7 @@ flakySuite('WorkingCopyBackupService', () => { let backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); assert.ok(backup); - await service.fileService.writeFile(service.toBackupResource(toUntypedWorkingCopyId(fooFile)), VSBuffer.fromString('')); + await service.testGetFileService().writeFile(service.toBackupResource(toUntypedWorkingCopyId(fooFile)), VSBuffer.fromString('')); backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); assert.ok(!backup); @@ -1099,7 +1130,7 @@ flakySuite('WorkingCopyBackupService', () => { let backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); assert.ok(backup); - await service.fileService.writeFile(service.toBackupResource(toUntypedWorkingCopyId(fooFile)), VSBuffer.fromString(contents)); + await service.testGetFileService().writeFile(service.toBackupResource(toUntypedWorkingCopyId(fooFile)), VSBuffer.fromString(contents)); backup = await service.resolve(toUntypedWorkingCopyId(fooFile)); assert.ok(!backup); @@ -1108,10 +1139,9 @@ flakySuite('WorkingCopyBackupService', () => { test('file with binary data', async () => { const identifier = toUntypedWorkingCopyId(fooFile); - const sourceDir = getPathFromAmdModule(require, './fixtures'); - - const buffer = await Promises.readFile(join(sourceDir, 'binary.txt')); - const hash = createHash('md5').update(buffer).digest('base64'); + const buffer = Uint8Array.from([ + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 73, 0, 0, 0, 67, 8, 2, 0, 0, 0, 95, 138, 191, 237, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 4, 103, 65, 77, 65, 0, 0, 177, 143, 11, 252, 97, 5, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 14, 195, 0, 0, 14, 195, 1, 199, 111, 168, 100, 0, 0, 0, 71, 116, 69, 88, 116, 83, 111, 117, 114, 99, 101, 0, 83, 104, 111, 116, 116, 121, 32, 118, 50, 46, 48, 46, 50, 46, 50, 49, 54, 32, 40, 67, 41, 32, 84, 104, 111, 109, 97, 115, 32, 66, 97, 117, 109, 97, 110, 110, 32, 45, 32, 104, 116, 116, 112, 58, 47, 47, 115, 104, 111, 116, 116, 121, 46, 100, 101, 118, 115, 45, 111, 110, 46, 110, 101, 116, 44, 132, 21, 213, 0, 0, 0, 84, 73, 68, 65, 84, 120, 218, 237, 207, 65, 17, 0, 0, 12, 2, 32, 211, 217, 63, 146, 37, 246, 218, 65, 3, 210, 191, 226, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 230, 118, 100, 169, 4, 173, 8, 44, 248, 184, 40, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 + ]); await service.backup(identifier, bufferToReadable(VSBuffer.wrap(buffer)), undefined, { binaryTest: 'true' }); @@ -1120,17 +1150,13 @@ flakySuite('WorkingCopyBackupService', () => { const backupBuffer = await consumeStream(backup.value, chunks => VSBuffer.concat(chunks)); assert.strictEqual(backupBuffer.buffer.byteLength, buffer.byteLength); - - const backupHash = createHash('md5').update(backupBuffer.buffer).digest('base64'); - - assert.strictEqual(hash, backupHash); }); }); suite('WorkingCopyBackupsModel', () => { test('simple', async () => { - const model = await WorkingCopyBackupsModel.create(URI.file(workspaceBackupPath), service.fileService); + const model = await WorkingCopyBackupsModel.create(workspaceBackupPath, service.testGetFileService()); const resource1 = URI.file('test.html'); @@ -1194,16 +1220,16 @@ flakySuite('WorkingCopyBackupService', () => { }); test('create', async () => { - const fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashIdentifier(toUntypedWorkingCopyId(fooFile))); - await Promises.mkdir(dirname(fooBackupPath), { recursive: true }); - writeFileSync(fooBackupPath, 'foo'); - const model = await WorkingCopyBackupsModel.create(URI.file(workspaceBackupPath), service.fileService); + const fooBackupPath = joinPath(workspaceBackupPath, fooFile.scheme, hashIdentifier(toUntypedWorkingCopyId(fooFile))); + await fileService.createFolder(dirname(fooBackupPath)); + await fileService.writeFile(fooBackupPath, VSBuffer.fromString('foo')); + const model = await WorkingCopyBackupsModel.create(workspaceBackupPath, service.testGetFileService()); - assert.strictEqual(model.has(URI.file(fooBackupPath)), true); + assert.strictEqual(model.has(fooBackupPath), true); }); test('get', async () => { - const model = await WorkingCopyBackupsModel.create(URI.file(workspaceBackupPath), service.fileService); + const model = await WorkingCopyBackupsModel.create(workspaceBackupPath, service.testGetFileService()); assert.deepStrictEqual(model.get(), []); @@ -1226,19 +1252,19 @@ flakySuite('WorkingCopyBackupService', () => { const untitledBackupId = toUntypedWorkingCopyId(untitledFile); const customBackupId = toUntypedWorkingCopyId(customFile); - const fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); - const untitledBackupPath = join(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); - const customFileBackupPath = join(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); + const fooBackupPath = joinPath(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); + const untitledBackupPath = joinPath(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); + const customFileBackupPath = joinPath(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); // Prepare backups of the old MD5 hash format - mkdirSync(join(workspaceBackupPath, fooFile.scheme), { recursive: true }); - mkdirSync(join(workspaceBackupPath, untitledFile.scheme), { recursive: true }); - mkdirSync(join(workspaceBackupPath, customFile.scheme), { recursive: true }); - writeFileSync(join(workspaceBackupPath, fooFile.scheme, '8a8589a2f1c9444b89add38166f50229'), `${fooFile.toString()}\ntest file`); - writeFileSync(join(workspaceBackupPath, untitledFile.scheme, '13264068d108c6901b3592ea654fcd57'), `${untitledFile.toString()}\ntest untitled`); - writeFileSync(join(workspaceBackupPath, customFile.scheme, 'bf018572af7b38746b502893bd0adf6c'), `${customFile.toString()}\ntest custom`); + await fileService.createFolder(joinPath(workspaceBackupPath, fooFile.scheme)); + await fileService.createFolder(joinPath(workspaceBackupPath, untitledFile.scheme)); + await fileService.createFolder(joinPath(workspaceBackupPath, customFile.scheme)); + await fileService.writeFile(joinPath(workspaceBackupPath, fooFile.scheme, '8a8589a2f1c9444b89add38166f50229'), VSBuffer.fromString(`${fooFile.toString()}\ntest file`)); + await fileService.writeFile(joinPath(workspaceBackupPath, untitledFile.scheme, '13264068d108c6901b3592ea654fcd57'), VSBuffer.fromString(`${untitledFile.toString()}\ntest untitled`)); + await fileService.writeFile(joinPath(workspaceBackupPath, customFile.scheme, 'bf018572af7b38746b502893bd0adf6c'), VSBuffer.fromString(`${customFile.toString()}\ntest custom`)); - service.reinitialize(URI.file(workspaceBackupPath)); + service.reinitialize(workspaceBackupPath); const backups = await service.getBackups(); assert.strictEqual(backups.length, 3); @@ -1246,19 +1272,19 @@ flakySuite('WorkingCopyBackupService', () => { assert.ok(backups.some(backup => isEqual(backup.resource, untitledFile))); assert.ok(backups.some(backup => isEqual(backup.resource, customFile))); - assert.strictEqual(readdirSync(join(workspaceBackupPath, fooFile.scheme)).length, 1); - assert.strictEqual(existsSync(fooBackupPath), true); - assert.strictEqual(readFileSync(fooBackupPath).toString(), `${fooFile.toString()}\ntest file`); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, fooFile.scheme))).children?.length, 1); + assert.strictEqual((await fileService.exists(fooBackupPath)), true); + assert.strictEqual((await fileService.readFile(fooBackupPath)).value.toString(), `${fooFile.toString()}\ntest file`); assert.ok(service.hasBackupSync(fooBackupId)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, untitledFile.scheme)).length, 1); - assert.strictEqual(existsSync(untitledBackupPath), true); - assert.strictEqual(readFileSync(untitledBackupPath).toString(), `${untitledFile.toString()}\ntest untitled`); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, untitledFile.scheme))).children?.length, 1); + assert.strictEqual((await fileService.exists(untitledBackupPath)), true); + assert.strictEqual((await fileService.readFile(untitledBackupPath)).value.toString(), `${untitledFile.toString()}\ntest untitled`); assert.ok(service.hasBackupSync(untitledBackupId)); - assert.strictEqual(readdirSync(join(workspaceBackupPath, customFile.scheme)).length, 1); - assert.strictEqual(existsSync(customFileBackupPath), true); - assert.strictEqual(readFileSync(customFileBackupPath).toString(), `${customFile.toString()}\ntest custom`); + assert.strictEqual((await fileService.resolve(joinPath(workspaceBackupPath, customFile.scheme))).children?.length, 1); + assert.strictEqual((await fileService.exists(customFileBackupPath)), true); + assert.strictEqual((await fileService.readFile(customFileBackupPath)).value.toString(), `${customFile.toString()}\ntest custom`); assert.ok(service.hasBackupSync(customBackupId)); }); }); @@ -1270,19 +1296,19 @@ flakySuite('WorkingCopyBackupService', () => { const untitledBackupId = toUntypedWorkingCopyId(untitledFile); const customBackupId = toUntypedWorkingCopyId(customFile); - const fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); - const untitledBackupPath = join(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); - const customFileBackupPath = join(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); + const fooBackupPath = joinPath(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); + const untitledBackupPath = joinPath(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); + const customFileBackupPath = joinPath(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); // Prepare backups of the old format without meta - mkdirSync(join(workspaceBackupPath, fooFile.scheme), { recursive: true }); - mkdirSync(join(workspaceBackupPath, untitledFile.scheme), { recursive: true }); - mkdirSync(join(workspaceBackupPath, customFile.scheme), { recursive: true }); - writeFileSync(fooBackupPath, `${fooFile.toString()}\ntest file`); - writeFileSync(untitledBackupPath, `${untitledFile.toString()}\ntest untitled`); - writeFileSync(customFileBackupPath, `${customFile.toString()}\ntest custom`); + await fileService.createFolder(joinPath(workspaceBackupPath, fooFile.scheme)); + await fileService.createFolder(joinPath(workspaceBackupPath, untitledFile.scheme)); + await fileService.createFolder(joinPath(workspaceBackupPath, customFile.scheme)); + await fileService.writeFile(fooBackupPath, VSBuffer.fromString(`${fooFile.toString()}\ntest file`)); + await fileService.writeFile(untitledBackupPath, VSBuffer.fromString(`${untitledFile.toString()}\ntest untitled`)); + await fileService.writeFile(customFileBackupPath, VSBuffer.fromString(`${customFile.toString()}\ntest custom`)); - service.reinitialize(URI.file(workspaceBackupPath)); + service.reinitialize(workspaceBackupPath); const backups = await service.getBackups(); assert.strictEqual(backups.length, 3); @@ -1297,19 +1323,19 @@ flakySuite('WorkingCopyBackupService', () => { const untitledBackupId = toUntypedWorkingCopyId(untitledFile); const customBackupId = toUntypedWorkingCopyId(customFile); - const fooBackupPath = join(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); - const untitledBackupPath = join(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); - const customFileBackupPath = join(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); + const fooBackupPath = joinPath(workspaceBackupPath, fooFile.scheme, hashIdentifier(fooBackupId)); + const untitledBackupPath = joinPath(workspaceBackupPath, untitledFile.scheme, hashIdentifier(untitledBackupId)); + const customFileBackupPath = joinPath(workspaceBackupPath, customFile.scheme, hashIdentifier(customBackupId)); // Prepare backups of the old format without meta - mkdirSync(join(workspaceBackupPath, fooFile.scheme), { recursive: true }); - mkdirSync(join(workspaceBackupPath, untitledFile.scheme), { recursive: true }); - mkdirSync(join(workspaceBackupPath, customFile.scheme), { recursive: true }); - writeFileSync(fooBackupPath, `${fooFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest file`); - writeFileSync(untitledBackupPath, `${untitledFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest untitled`); - writeFileSync(customFileBackupPath, `${customFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest custom`); + await fileService.createFolder(joinPath(workspaceBackupPath, fooFile.scheme)); + await fileService.createFolder(joinPath(workspaceBackupPath, untitledFile.scheme)); + await fileService.createFolder(joinPath(workspaceBackupPath, customFile.scheme)); + await fileService.writeFile(fooBackupPath, VSBuffer.fromString(`${fooFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest file`)); + await fileService.writeFile(untitledBackupPath, VSBuffer.fromString(`${untitledFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest untitled`)); + await fileService.writeFile(customFileBackupPath, VSBuffer.fromString(`${customFile.toString()} ${JSON.stringify({ foo: 'bar' })}\ntest custom`)); - service.reinitialize(URI.file(workspaceBackupPath)); + service.reinitialize(workspaceBackupPath); const backups = await service.getBackups(); assert.strictEqual(backups.length, 3); diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupTracker.test.ts similarity index 87% rename from src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupTracker.test.ts index 7e1c8195a9..71c7af2815 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyBackupTracker.test.ts @@ -5,11 +5,8 @@ import * as assert from 'assert'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; -import { tmpdir } from 'os'; import { join } from 'vs/base/common/path'; -import { Promises } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; -import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { hash } from 'vs/base/common/hash'; import { NativeWorkingCopyBackupTracker } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -18,7 +15,6 @@ 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 { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; -import { NodeTestWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { toResource } from 'vs/base/test/common/utils'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -28,12 +24,11 @@ import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { ShutdownReason, ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IFileDialogService, ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { workbenchInstantiationService, TestServiceAccessor } from 'vs/workbench/test/electron-browser/workbenchTestServices'; +import { INativeHostService } from 'vs/platform/native/common/native'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createEditorPart, registerTestFileEditor, TestBeforeShutdownEvent, TestFilesConfigurationService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { createEditorPart, registerTestFileEditor, TestBeforeShutdownEvent, TestEnvironmentService, TestFilesConfigurationService, TestFileService } 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'; @@ -42,10 +37,16 @@ import { IProgressService } from 'vs/platform/progress/common/progress'; import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; import { TestContextService, TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { Event, Emitter } from 'vs/base/common/event'; +import { generateUuid } from 'vs/base/common/uuid'; +import { Schemas } from 'vs/base/common/network'; +import { joinPath } from 'vs/base/common/resources'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/electron-sandbox/workbenchTestServices'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -flakySuite('WorkingCopyBackupTracker (native)', function () { +suite('WorkingCopyBackupTracker (native)', function () { class TestWorkingCopyBackupTracker extends NativeWorkingCopyBackupTracker { @@ -107,9 +108,9 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { } } - let testDir: string; - let backupHome: string; - let workspaceBackupPath: string; + let testDir: URI; + let backupHome: URI; + let workspaceBackupPath: URI; let accessor: TestServiceAccessor; let disposables: DisposableStore; @@ -117,35 +118,31 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { setup(async () => { disposables = new DisposableStore(); - testDir = getRandomTestPath(tmpdir(), 'vsctests', 'backuprestorer'); - backupHome = join(testDir, 'Backups'); - const workspacesJsonPath = join(backupHome, 'workspaces.json'); + testDir = URI.file(join(generateUuid(), 'vsctests', 'workingcopybackuptracker')).with({ scheme: Schemas.inMemory }); + backupHome = joinPath(testDir, 'Backups'); + const workspacesJsonPath = joinPath(backupHome, 'workspaces.json'); - const workspaceResource = URI.file(isWindows ? 'c:\\workspace' : '/workspace'); - workspaceBackupPath = join(backupHome, hash(workspaceResource.fsPath).toString(16)); + const workspaceResource = URI.file(isWindows ? 'c:\\workspace' : '/workspace').with({ scheme: Schemas.inMemory }); + workspaceBackupPath = joinPath(backupHome, hash(workspaceResource.toString()).toString(16)); - const instantiationService = workbenchInstantiationService(disposables); + const instantiationService = workbenchInstantiationService(undefined, disposables); accessor = instantiationService.createInstance(TestServiceAccessor); disposables.add((accessor.textFileService.files)); disposables.add(registerTestFileEditor()); - await Promises.mkdir(backupHome, { recursive: true }); - await Promises.mkdir(workspaceBackupPath, { recursive: true }); + await accessor.fileService.createFolder(backupHome); + await accessor.fileService.createFolder(workspaceBackupPath); - return Promises.writeFile(workspacesJsonPath, ''); + return accessor.fileService.writeFile(workspacesJsonPath, VSBuffer.fromString('')); }); teardown(async () => { disposables.dispose(); - - return Promises.rm(testDir); }); async function createTracker(autoSaveEnabled = false): Promise<{ accessor: TestServiceAccessor; part: EditorPart; tracker: TestWorkingCopyBackupTracker; instantiationService: IInstantiationService; cleanup: () => Promise }> { - const workingCopyBackupService = new NodeTestWorkingCopyBackupService(testDir, workspaceBackupPath); - const instantiationService = workbenchInstantiationService(disposables); - instantiationService.stub(IWorkingCopyBackupService, workingCopyBackupService); + const instantiationService = workbenchInstantiationService(undefined, disposables); const configurationService = new TestConfigurationService(); if (autoSaveEnabled) { @@ -156,7 +153,10 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { instantiationService.stub(IFilesConfigurationService, new TestFilesConfigurationService( instantiationService.createInstance(MockContextKeyService), configurationService, - new TestContextService(TestWorkspace) + new TestContextService(TestWorkspace), + TestEnvironmentService, + new UriIdentityService(new TestFileService()), + new TestFileService() )); const part = await createEditorPart(instantiationService, disposables); @@ -234,7 +234,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { const model = accessor.textFileService.files.get(resource); accessor.fileDialogService.setConfirmResult(ConfirmResult.CANCEL); - accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + accessor.filesConfigurationService.testOnFilesConfigurationChange({ files: { hotExit: 'off' } }); await model?.resolve(); model?.textEditorModel?.setValue('foo'); @@ -281,7 +281,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { const model = accessor.textFileService.files.get(resource); accessor.fileDialogService.setConfirmResult(ConfirmResult.DONT_SAVE); - accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + accessor.filesConfigurationService.testOnFilesConfigurationChange({ files: { hotExit: 'off' } }); await model?.resolve(); model?.textEditorModel?.setValue('foo'); @@ -319,7 +319,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { const veto = await event.value; assert.ok(!veto); - assert.ok(!accessor.workingCopyBackupService.discardedAllBackups); + assert.ok(accessor.workingCopyBackupService.discardedAllBackups); await cleanup(); }); @@ -333,7 +333,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { const model = accessor.textFileService.files.get(resource); accessor.fileDialogService.setConfirmResult(ConfirmResult.SAVE); - accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: 'off' } }); + accessor.filesConfigurationService.testOnFilesConfigurationChange({ files: { hotExit: 'off' } }); await model?.resolve(); model?.textEditorModel?.setValue('foo'); @@ -381,6 +381,48 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { await cleanup(); }); + test('onWillShutdown - scratchpads - veto if backup fails', async function () { + const { accessor, cleanup } = await createTracker(); + + class TestBackupWorkingCopy extends TestWorkingCopy { + + constructor(resource: URI) { + super(resource); + + accessor.workingCopyService.registerWorkingCopy(this); + } + + override capabilities = WorkingCopyCapabilities.Untitled | WorkingCopyCapabilities.Scratchpad; + + override async backup(token: CancellationToken): Promise { + throw new Error('unable to backup'); + } + + override isDirty(): boolean { + return false; + } + + override isModified(): boolean { + return true; + } + } + + const resource = toResource.call(this, '/path/custom.txt'); + new TestBackupWorkingCopy(resource); + + const event = new TestBeforeShutdownEvent(); + event.reason = ShutdownReason.QUIT; + accessor.lifecycleService.fireBeforeShutdown(event); + + const veto = await event.value; + assert.ok(veto); + + const finalVeto = await event.finalValue?.(); + assert.ok(finalVeto); // assert the tracker uses the internal finalVeto API + + await cleanup(); + }); + test('onWillShutdown - pending backup operations canceled and tracker suspended/resumsed', async function () { const { accessor, tracker, cleanup } = await createTracker(); @@ -532,7 +574,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { const model = accessor.textFileService.files.get(resource); // Set hot exit config - accessor.filesConfigurationService.onFilesConfigurationChange({ files: { hotExit: setting } }); + accessor.filesConfigurationService.testOnFilesConfigurationChange({ files: { hotExit: setting } }); // Set empty workspace if required if (!workspace) { diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryService.test.ts similarity index 79% rename from src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryService.test.ts index 8d7b2a613f..84d38a8c0f 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryService.test.ts @@ -4,40 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { TestNativePathService, TestNativeWindowConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; -import { TestContextService, TestProductService, TestStorageService, TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestContextService, TestStorageService, TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; import { NullLogService } from 'vs/platform/log/common/log'; import { FileService } from 'vs/platform/files/common/fileService'; -import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { Schemas } from 'vs/base/common/network'; -import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; -import { tmpdir } from 'os'; -import { dirname, join } from 'vs/base/common/path'; -import { Promises } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { existsSync, readFileSync, unlinkSync } from 'fs'; import { IWorkingCopyHistoryEntry, IWorkingCopyHistoryEntryDescriptor, IWorkingCopyHistoryEvent } from 'vs/workbench/services/workingCopy/common/workingCopyHistory'; import { IFileService } from 'vs/platform/files/common/files'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; -import { TestLifecycleService, TestRemoteAgentService, TestWillShutdownEvent } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestLifecycleService, TestPathService, TestRemoteAgentService, TestWillShutdownEvent } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { NativeWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService'; -import { joinPath, dirname as resourcesDirname, basename } from 'vs/base/common/resources'; +import { NativeWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistoryService'; +import { joinPath, dirname, basename } from 'vs/base/common/resources'; import { firstOrDefault } from 'vs/base/common/arrays'; - -class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService { - - constructor(private readonly testDir: URI | string) { - super({ ...TestNativeWindowConfiguration, 'user-data-dir': URI.isUri(testDir) ? testDir.fsPath : testDir }, TestProductService); - } - - override get localHistoryHome() { - return joinPath(URI.isUri(this.testDir) ? this.testDir : URI.file(this.testDir), 'History'); - } -} +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { generateUuid } from 'vs/base/common/uuid'; +import { join } from 'vs/base/common/path'; +import { VSBuffer } from 'vs/base/common/buffer'; export class TestWorkingCopyHistoryService extends NativeWorkingCopyHistoryService { @@ -45,19 +30,21 @@ export class TestWorkingCopyHistoryService extends NativeWorkingCopyHistoryServi readonly _configurationService: TestConfigurationService; readonly _lifecycleService: TestLifecycleService; - constructor(testDir: URI | string) { - const environmentService = new TestWorkbenchEnvironmentService(testDir); + constructor(fileService?: IFileService) { + const environmentService = TestEnvironmentService; const logService = new NullLogService(); - const fileService = new FileService(logService); - const diskFileSystemProvider = new DiskFileSystemProvider(logService); - fileService.registerProvider(Schemas.file, diskFileSystemProvider); + if (!fileService) { + fileService = new FileService(logService); + fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider()); + fileService.registerProvider(Schemas.vscodeUserData, new InMemoryFileSystemProvider()); + } const remoteAgentService = new TestRemoteAgentService(); const uriIdentityService = new UriIdentityService(fileService); - const labelService = new LabelService(environmentService, new TestContextService(), new TestNativePathService(), new TestRemoteAgentService(), new TestStorageService(), new TestLifecycleService()); + const labelService = new LabelService(environmentService, new TestContextService(), new TestPathService(), new TestRemoteAgentService(), new TestStorageService(), new TestLifecycleService()); const lifecycleService = new TestLifecycleService(); @@ -71,16 +58,17 @@ export class TestWorkingCopyHistoryService extends NativeWorkingCopyHistoryServi } } -flakySuite('WorkingCopyHistoryService', () => { +suite('WorkingCopyHistoryService', () => { - let testDir: string; - let historyHome: string; - let workHome: string; + let testDir: URI; + let historyHome: URI; + let workHome: URI; let service: TestWorkingCopyHistoryService; + let fileService: IFileService; - let testFile1Path: string; - let testFile2Path: string; - let testFile3Path: string; + let testFile1Path: URI; + let testFile2Path: URI; + let testFile3Path: URI; const testFile1PathContents = 'Hello Foo'; const testFile2PathContents = [ @@ -92,22 +80,23 @@ flakySuite('WorkingCopyHistoryService', () => { const testFile3PathContents = 'Hello Bar'; setup(async () => { - testDir = getRandomTestPath(tmpdir(), 'vsctests', 'workingcopyhistoryservice'); - historyHome = join(testDir, 'User', 'History'); - workHome = join(testDir, 'work'); + testDir = URI.file(join(generateUuid(), 'vsctests', 'workingcopyhistoryservice')).with({ scheme: Schemas.inMemory }); + historyHome = joinPath(testDir, 'User', 'History'); + workHome = joinPath(testDir, 'work'); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(); + fileService = service._fileService; - await Promises.mkdir(historyHome, { recursive: true }); - await Promises.mkdir(workHome, { recursive: true }); + await fileService.createFolder(historyHome); + await fileService.createFolder(workHome); - testFile1Path = join(workHome, 'foo.txt'); - testFile2Path = join(workHome, 'bar.txt'); - testFile3Path = join(workHome, 'foo-bar.txt'); + testFile1Path = joinPath(workHome, 'foo.txt'); + testFile2Path = joinPath(workHome, 'bar.txt'); + testFile3Path = joinPath(workHome, 'foo-bar.txt'); - await Promises.writeFile(testFile1Path, testFile1PathContents); - await Promises.writeFile(testFile2Path, testFile2PathContents); - await Promises.writeFile(testFile3Path, testFile3PathContents); + await fileService.writeFile(testFile1Path, VSBuffer.fromString(testFile1PathContents)); + await fileService.writeFile(testFile2Path, VSBuffer.fromString(testFile2PathContents)); + await fileService.writeFile(testFile3Path, VSBuffer.fromString(testFile3PathContents)); }); let increasingTimestampCounter = 1; @@ -122,7 +111,7 @@ flakySuite('WorkingCopyHistoryService', () => { if (expectEntryAdded) { assert.ok(entry, 'Unexpected undefined local history entry'); - assert.strictEqual(existsSync(entry.location.fsPath), true, 'Unexpected local history not stored on disk'); + assert.strictEqual((await fileService.exists(entry.location)), true, 'Unexpected local history not stored'); } return entry; @@ -130,24 +119,22 @@ flakySuite('WorkingCopyHistoryService', () => { teardown(() => { service.dispose(); - - return Promises.rm(testDir); }); test('addEntry', async () => { const addEvents: IWorkingCopyHistoryEvent[] = []; service.onDidAddEntry(e => addEvents.push(e)); - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); - const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); + const workingCopy2 = new TestWorkingCopy(testFile2Path); // Add Entry works const entry1A = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); const entry2A = await addEntry({ resource: workingCopy2.resource, source: 'My Source' }, CancellationToken.None); - assert.strictEqual(readFileSync(entry1A.location.fsPath).toString(), testFile1PathContents); - assert.strictEqual(readFileSync(entry2A.location.fsPath).toString(), testFile2PathContents); + assert.strictEqual((await fileService.readFile(entry1A.location)).value.toString(), testFile1PathContents); + assert.strictEqual((await fileService.readFile(entry2A.location)).value.toString(), testFile2PathContents); assert.strictEqual(addEvents.length, 2); assert.strictEqual(addEvents[0].entry.workingCopy.resource.toString(), workingCopy1.resource.toString()); @@ -157,8 +144,8 @@ flakySuite('WorkingCopyHistoryService', () => { const entry1B = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); const entry2B = await addEntry({ resource: workingCopy2.resource }, CancellationToken.None); - assert.strictEqual(readFileSync(entry1B.location.fsPath).toString(), testFile1PathContents); - assert.strictEqual(readFileSync(entry2B.location.fsPath).toString(), testFile2PathContents); + assert.strictEqual((await fileService.readFile(entry1B.location)).value.toString(), testFile1PathContents); + assert.strictEqual((await fileService.readFile(entry2B.location)).value.toString(), testFile2PathContents); assert.strictEqual(addEvents.length, 4); assert.strictEqual(addEvents[2].entry.workingCopy.resource.toString(), workingCopy1.resource.toString()); @@ -177,7 +164,7 @@ flakySuite('WorkingCopyHistoryService', () => { // Invalid working copies are ignored - const workingCopy3 = new TestWorkingCopy(URI.file(testFile2Path).with({ scheme: 'unsupported' })); + const workingCopy3 = new TestWorkingCopy(testFile2Path.with({ scheme: 'unsupported' })); const entry3A = await addEntry({ resource: workingCopy3.resource }, CancellationToken.None, false); assert.ok(!entry3A); @@ -188,7 +175,7 @@ flakySuite('WorkingCopyHistoryService', () => { const changeEvents: IWorkingCopyHistoryEvent[] = []; service.onDidChangeEntry(e => changeEvents.push(e)); - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); const entry = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); @@ -210,10 +197,10 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 3); @@ -224,7 +211,7 @@ flakySuite('WorkingCopyHistoryService', () => { const removeEvents: IWorkingCopyHistoryEvent[] = []; service.onDidRemoveEntry(e => removeEvents.push(e)); - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); const entry2 = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); @@ -252,17 +239,17 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 3); }); test('removeEntry - deletes history entries folder when last entry removed', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); let entry = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); @@ -271,12 +258,12 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); - assert.strictEqual(existsSync(dirname(entry.location.fsPath)), true); + assert.strictEqual((await fileService.exists(dirname(entry.location))), true); entry = firstOrDefault(await service.getEntries(workingCopy1.resource, CancellationToken.None))!; assert.ok(entry); @@ -288,20 +275,20 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); - assert.strictEqual(existsSync(dirname(entry.location.fsPath)), false); + assert.strictEqual((await fileService.exists(dirname(entry.location))), false); }); test('removeAll', async () => { let removed = false; service.onDidRemoveEntries(() => removed = true); - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); - const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); + const workingCopy2 = new TestWorkingCopy(testFile2Path); await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); @@ -327,10 +314,10 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 0); @@ -339,8 +326,8 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('getEntries - simple', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); - const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); + const workingCopy2 = new TestWorkingCopy(testFile2Path); let entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 0); @@ -368,8 +355,8 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('getEntries - metadata preserved when stored', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); - const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); + const workingCopy2 = new TestWorkingCopy(testFile2Path); const entry1 = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None); const entry2 = await addEntry({ resource: workingCopy2.resource }, CancellationToken.None); @@ -380,10 +367,10 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); let entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 1); @@ -396,7 +383,7 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('getEntries - corrupt meta.json is no problem', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); const entry1 = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); @@ -405,14 +392,14 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); - const metaFile = join(dirname(entry1.location.fsPath), 'entries.json'); - assert.ok(existsSync(metaFile)); - unlinkSync(metaFile); + const metaFile = joinPath(dirname(entry1.location), 'entries.json'); + assert.ok((await fileService.exists(metaFile))); + await fileService.del(metaFile); const entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 1); @@ -420,7 +407,7 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('getEntries - missing entries from meta.json is no problem', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); const entry1 = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); const entry2 = await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); @@ -430,12 +417,12 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); - unlinkSync(entry1.location.fsPath); + await fileService.del(entry1.location); const entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 1); @@ -443,7 +430,7 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('getEntries - in-memory and on-disk entries are merged', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); const entry1 = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None); const entry2 = await addEntry({ resource: workingCopy1.resource, source: 'other-source' }, CancellationToken.None); @@ -453,10 +440,10 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); const entry3 = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None); const entry4 = await addEntry({ resource: workingCopy1.resource, source: 'other-source' }, CancellationToken.None); @@ -470,7 +457,7 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('getEntries - configured max entries respected', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); await addEntry({ resource: workingCopy1.resource }, CancellationToken.None); @@ -496,8 +483,8 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('getAll', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); - const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); + const workingCopy2 = new TestWorkingCopy(testFile2Path); let resources = await service.getAll(CancellationToken.None); assert.strictEqual(resources.length, 0); @@ -520,12 +507,12 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); - const workingCopy3 = new TestWorkingCopy(URI.file(testFile3Path)); + const workingCopy3 = new TestWorkingCopy(testFile3Path); await addEntry({ resource: workingCopy3.resource, source: 'test-source' }, CancellationToken.None); resources = await service.getAll(CancellationToken.None); @@ -538,7 +525,7 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('getAll - ignores resource when no entries exist', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); const entry = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None); @@ -555,10 +542,10 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); resources = await service.getAll(CancellationToken.None); assert.strictEqual(resources.length, 0); @@ -576,7 +563,7 @@ flakySuite('WorkingCopyHistoryService', () => { } test('entries cleaned up on shutdown', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); const entry1 = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None); const entry2 = await addEntry({ resource: workingCopy1.resource, source: 'other-source' }, CancellationToken.None); @@ -590,15 +577,15 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - assert.ok(!existsSync(entry1.location.fsPath)); - assert.ok(!existsSync(entry2.location.fsPath)); - assert.ok(existsSync(entry3.location.fsPath)); - assert.ok(existsSync(entry4.location.fsPath)); + assert.ok(!(await fileService.exists(entry1.location))); + assert.ok(!(await fileService.exists(entry2.location))); + assert.ok((await fileService.exists(entry3.location))); + assert.ok((await fileService.exists(entry4.location))); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); let entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 2); @@ -614,14 +601,14 @@ flakySuite('WorkingCopyHistoryService', () => { service._lifecycleService.fireWillShutdown(event); await Promise.allSettled(event.value); - assert.ok(existsSync(entry3.location.fsPath)); - assert.ok(existsSync(entry4.location.fsPath)); - assert.ok(existsSync(entry5.location.fsPath)); + assert.ok((await fileService.exists(entry3.location))); + assert.ok((await fileService.exists(entry4.location))); + assert.ok((await fileService.exists(entry5.location))); - // Resolve from disk fresh and verify again + // Resolve from file service fresh and verify again service.dispose(); - service = new TestWorkingCopyHistoryService(testDir); + service = new TestWorkingCopyHistoryService(fileService); entries = await service.getEntries(workingCopy1.resource, CancellationToken.None); assert.strictEqual(entries.length, 3); @@ -634,7 +621,7 @@ flakySuite('WorkingCopyHistoryService', () => { let replaced: IWorkingCopyHistoryEntry | undefined = undefined; service.onDidReplaceEntry(e => replaced = e.entry); - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); service._configurationService.setUserConfiguration('workbench.localHistory.mergeWindow', 1); @@ -661,7 +648,7 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('move entries (file rename)', async () => { - const workingCopy = new TestWorkingCopy(URI.file(testFile1Path)); + const workingCopy = new TestWorkingCopy(testFile1Path); const entry1 = await addEntry({ resource: workingCopy.resource, source: 'test-source' }, CancellationToken.None); const entry2 = await addEntry({ resource: workingCopy.resource, source: 'test-source' }, CancellationToken.None); @@ -670,8 +657,8 @@ flakySuite('WorkingCopyHistoryService', () => { let entries = await service.getEntries(workingCopy.resource, CancellationToken.None); assert.strictEqual(entries.length, 3); - const renamedWorkingCopyResource = joinPath(resourcesDirname(workingCopy.resource), 'renamed.txt'); - await service._fileService.move(workingCopy.resource, renamedWorkingCopyResource); + const renamedWorkingCopyResource = joinPath(dirname(workingCopy.resource), 'renamed.txt'); + await fileService.move(workingCopy.resource, renamedWorkingCopyResource); const result = await service.moveEntries(workingCopy.resource, renamedWorkingCopyResource); @@ -708,8 +695,8 @@ flakySuite('WorkingCopyHistoryService', () => { }); test('entries moved (folder rename)', async () => { - const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path)); - const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path)); + const workingCopy1 = new TestWorkingCopy(testFile1Path); + const workingCopy2 = new TestWorkingCopy(testFile2Path); const entry1A = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None); const entry2A = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None); @@ -725,10 +712,10 @@ flakySuite('WorkingCopyHistoryService', () => { entries = await service.getEntries(workingCopy2.resource, CancellationToken.None); assert.strictEqual(entries.length, 3); - const renamedWorkHome = joinPath(resourcesDirname(URI.file(workHome)), 'renamed'); - await service._fileService.move(URI.file(workHome), renamedWorkHome); + const renamedWorkHome = joinPath(dirname(workHome), 'renamed'); + await fileService.move(workHome, renamedWorkHome); - const resources = await service.moveEntries(URI.file(workHome), renamedWorkHome); + const resources = await service.moveEntries(workHome, renamedWorkHome); const renamedWorkingCopy1Resource = joinPath(renamedWorkHome, basename(workingCopy1.resource)); const renamedWorkingCopy2Resource = joinPath(renamedWorkHome, basename(workingCopy2.resource)); diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryTracker.test.ts similarity index 87% rename from src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts rename to src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryTracker.test.ts index ca04c8bbac..cb5bd17118 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-sandbox/workingCopyHistoryTracker.test.ts @@ -5,16 +5,14 @@ import * as assert from 'assert'; import { Event } from 'vs/base/common/event'; -import { TestContextService, TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestContextService, TestStorageService, TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices'; import { randomPath } from 'vs/base/common/extpath'; -import { tmpdir } from 'os'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; -import { TestWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test'; import { WorkingCopyHistoryTracker } from 'vs/workbench/services/workingCopy/common/workingCopyHistoryTracker'; import { WorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { TestFileService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices'; +import { TestEnvironmentService, TestFileService, TestLifecycleService, TestPathService, TestRemoteAgentService } from 'vs/workbench/test/browser/workbenchTestServices'; import { DeferredPromise } from 'vs/base/common/async'; import { IFileService } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; @@ -29,6 +27,41 @@ import { assertIsDefined } from 'vs/base/common/types'; import { VSBuffer } from 'vs/base/common/buffer'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { NativeWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistoryService'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { LabelService } from 'vs/workbench/services/label/common/labelService'; + +class TestWorkingCopyHistoryService extends NativeWorkingCopyHistoryService { + + readonly _fileService: IFileService; + readonly _configurationService: TestConfigurationService; + readonly _lifecycleService: TestLifecycleService; + + constructor(testDir: URI | string) { + const environmentService = TestEnvironmentService; + const logService = new NullLogService(); + const fileService = new FileService(logService); + + fileService.registerProvider(Schemas.vscodeUserData, new InMemoryFileSystemProvider()); + + const remoteAgentService = new TestRemoteAgentService(); + + const uriIdentityService = new UriIdentityService(fileService); + + const labelService = new LabelService(environmentService, new TestContextService(), new TestPathService(), new TestRemoteAgentService(), new TestStorageService(), new TestLifecycleService()); + + const lifecycleService = new TestLifecycleService(); + + const configurationService = new TestConfigurationService(); + + super(fileService, remoteAgentService, environmentService, uriIdentityService, labelService, lifecycleService, logService, configurationService); + + this._fileService = fileService; + this._configurationService = configurationService; + this._lifecycleService = lifecycleService; + } +} suite('WorkingCopyHistoryTracker', () => { @@ -67,7 +100,7 @@ suite('WorkingCopyHistoryTracker', () => { } setup(async () => { - testDir = URI.file(randomPath(join(tmpdir(), 'vsctests', 'workingcopyhistorytracker'))).with({ scheme: Schemas.inMemory }); + testDir = URI.file(randomPath(join('vsctests', 'workingcopyhistorytracker'))).with({ scheme: Schemas.inMemory }); historyHome = joinPath(testDir, 'User', 'History'); workHome = joinPath(testDir, 'work'); diff --git a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts index 97d6814eb1..7867977698 100644 --- a/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts @@ -6,7 +6,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; -import { hasWorkspaceFileExtension, isSavedWorkspace, isUntitledWorkspace, IWorkspaceContextService, IWorkspaceIdentifier, WorkbenchState, WORKSPACE_EXTENSION, WORKSPACE_FILTER } from 'vs/platform/workspace/common/workspace'; +import { hasWorkspaceFileExtension, isSavedWorkspace, isUntitledWorkspace, isWorkspaceIdentifier, IWorkspaceContextService, IWorkspaceIdentifier, toWorkspaceIdentifier, WorkbenchState, WORKSPACE_EXTENSION, WORKSPACE_FILTER } from 'vs/platform/workspace/common/workspace'; import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, IEnterWorkspaceResult, IStoredWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; @@ -246,7 +246,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi } else { path = untitledWorkspace.configPath; if (!this.userDataProfileService.currentProfile.isDefault) { - await this.userDataProfilesService.setProfileForWorkspace(this.userDataProfileService.currentProfile, untitledWorkspace); + await this.userDataProfilesService.setProfileForWorkspace(untitledWorkspace, this.userDataProfileService.currentProfile); } } @@ -285,7 +285,7 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi const isNotUntitledWorkspace = !isUntitledWorkspace(targetConfigPathURI, this.environmentService); if (isNotUntitledWorkspace && !this.userDataProfileService.currentProfile.isDefault) { const newWorkspace = await this.workspacesService.getWorkspaceIdentifier(targetConfigPathURI); - await this.userDataProfilesService.setProfileForWorkspace(this.userDataProfileService.currentProfile, newWorkspace); + await this.userDataProfilesService.setProfileForWorkspace(newWorkspace, this.userDataProfileService.currentProfile); } // Return early if target is same as source @@ -331,9 +331,6 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi case JSONEditingErrorCode.ERROR_INVALID_FILE: this.onInvalidWorkspaceConfigurationFileError(); break; - case JSONEditingErrorCode.ERROR_FILE_DIRTY: - this.onWorkspaceConfigurationFileDirtyError(); - break; default: this.notificationService.error(error.message); } @@ -344,11 +341,6 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi this.askToOpenWorkspaceConfigurationFile(message); } - private onWorkspaceConfigurationFileDirtyError(): void { - const message = localize('errorWorkspaceConfigurationFileDirty', "Unable to write into workspace configuration file because the file has unsaved changes. Please save it and try again."); - this.askToOpenWorkspaceConfigurationFile(message); - } - private askToOpenWorkspaceConfigurationFile(message: string): void { this.notificationService.prompt(Severity.Error, message, [{ @@ -408,9 +400,9 @@ export abstract class AbstractWorkspaceEditingService implements IWorkspaceEditi } protected getCurrentWorkspaceIdentifier(): IWorkspaceIdentifier | undefined { - const workspace = this.contextService.getWorkspace(); - if (workspace?.configuration) { - return { id: workspace.id, configPath: workspace.configuration }; + const identifier = toWorkspaceIdentifier(this.contextService.getWorkspace()); + if (isWorkspaceIdentifier(identifier)) { + return identifier; } return undefined; diff --git a/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts index 4dd3f7bdb4..71a96ab02f 100644 --- a/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspaceEditingService.ts @@ -16,7 +16,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IHostService } from 'vs/workbench/services/host/browser/host'; import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; @@ -57,4 +57,4 @@ export class BrowserWorkspaceEditingService extends AbstractWorkspaceEditingServ } } -registerSingleton(IWorkspaceEditingService, BrowserWorkspaceEditingService, true); +registerSingleton(IWorkspaceEditingService, BrowserWorkspaceEditingService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workspaces/browser/workspacesService.ts b/src/vs/workbench/services/workspaces/browser/workspacesService.ts index ccd4eaf964..994d7227ec 100644 --- a/src/vs/workbench/services/workspaces/browser/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/browser/workspacesService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkspacesService, IWorkspaceFolderCreationData, IEnterWorkspaceResult, IRecentlyOpened, restoreRecentlyOpened, IRecent, isRecentFile, isRecentFolder, toStoreData, IStoredWorkspaceFolder, getStoredWorkspaceFolder, IStoredWorkspace, isRecentWorkspace } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { Emitter } from 'vs/base/common/event'; @@ -16,7 +16,6 @@ import { IFileService, FileOperationError, FileOperationResult } from 'vs/platfo import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { joinPath } from 'vs/base/common/resources'; import { VSBuffer } from 'vs/base/common/buffer'; -import { isWindows } from 'vs/base/common/platform'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IWorkspaceBackupInfo, IFolderBackupInfo } from 'vs/platform/backup/common/backup'; import { Schemas } from 'vs/base/common/network'; @@ -179,7 +178,7 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; if (folders) { for (const folder of folders) { - storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, this.environmentService.untitledWorkspacesHome, !isWindows, this.uriIdentityService.extUri)); + storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, true, folder.name, this.environmentService.untitledWorkspacesHome, this.uriIdentityService.extUri)); } } @@ -216,4 +215,4 @@ export class BrowserWorkspacesService extends Disposable implements IWorkspacesS //#endregion } -registerSingleton(IWorkspacesService, BrowserWorkspacesService, true); +registerSingleton(IWorkspacesService, BrowserWorkspacesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workspaces/common/canonicalUriService.ts b/src/vs/workbench/services/workspaces/common/canonicalUriService.ts new file mode 100644 index 0000000000..75160e8515 --- /dev/null +++ b/src/vs/workbench/services/workspaces/common/canonicalUriService.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ICanonicalUriService, ICanonicalUriProvider } from 'vs/platform/workspace/common/canonicalUri'; + +export class CanonicalUriService implements ICanonicalUriService { + declare readonly _serviceBrand: undefined; + + private readonly _providers = new Map(); + + registerCanonicalUriProvider(provider: ICanonicalUriProvider): IDisposable { + this._providers.set(provider.scheme, provider); + return { + dispose: () => this._providers.delete(provider.scheme) + }; + } + + async provideCanonicalUri(uri: URI, targetScheme: string, token: CancellationToken): Promise { + const provider = this._providers.get(uri.scheme); + if (provider) { + return provider.provideCanonicalUri(uri, targetScheme, token); + } + return undefined; + } +} + +registerSingleton(ICanonicalUriService, CanonicalUriService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workspaces/common/editSessionIdentityService.ts b/src/vs/workbench/services/workspaces/common/editSessionIdentityService.ts new file mode 100644 index 0000000000..f40b64017d --- /dev/null +++ b/src/vs/workbench/services/workspaces/common/editSessionIdentityService.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { insert } from 'vs/base/common/arrays'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { EditSessionIdentityMatch, IEditSessionIdentityCreateParticipant, IEditSessionIdentityProvider, IEditSessionIdentityService } from 'vs/platform/workspace/common/editSessions'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; + +export class EditSessionIdentityService implements IEditSessionIdentityService { + readonly _serviceBrand: undefined; + + private _editSessionIdentifierProviders = new Map(); + + constructor( + @IExtensionService private readonly _extensionService: IExtensionService, + @ILogService private readonly _logService: ILogService, + ) { } + + registerEditSessionIdentityProvider(provider: IEditSessionIdentityProvider): IDisposable { + if (this._editSessionIdentifierProviders.get(provider.scheme)) { + throw new Error(`A provider has already been registered for scheme ${provider.scheme}`); + } + + this._editSessionIdentifierProviders.set(provider.scheme, provider); + return toDisposable(() => { + this._editSessionIdentifierProviders.delete(provider.scheme); + }); + } + + async getEditSessionIdentifier(workspaceFolder: IWorkspaceFolder, token: CancellationToken): Promise { + const { scheme } = workspaceFolder.uri; + + const provider = await this.activateProvider(scheme); + this._logService.trace(`EditSessionIdentityProvider for scheme ${scheme} available: ${!!provider}`); + + return provider?.getEditSessionIdentifier(workspaceFolder, token); + } + + async provideEditSessionIdentityMatch(workspaceFolder: IWorkspaceFolder, identity1: string, identity2: string, cancellationToken: CancellationToken): Promise { + const { scheme } = workspaceFolder.uri; + + const provider = await this.activateProvider(scheme); + this._logService.trace(`EditSessionIdentityProvider for scheme ${scheme} available: ${!!provider}`); + + return provider?.provideEditSessionIdentityMatch?.(workspaceFolder, identity1, identity2, cancellationToken); + } + + async onWillCreateEditSessionIdentity(workspaceFolder: IWorkspaceFolder, cancellationToken: CancellationToken): Promise { + this._logService.debug('Running onWillCreateEditSessionIdentity participants...'); + + // TODO@joyceerhl show progress notification? + for (const participant of this._participants) { + await participant.participate(workspaceFolder, cancellationToken); + } + + this._logService.debug(`Done running ${this._participants.length} onWillCreateEditSessionIdentity participants.`); + } + + private _participants: IEditSessionIdentityCreateParticipant[] = []; + + addEditSessionIdentityCreateParticipant(participant: IEditSessionIdentityCreateParticipant): IDisposable { + const dispose = insert(this._participants, participant); + + return toDisposable(() => dispose()); + } + + private async activateProvider(scheme: string) { + const transformedScheme = scheme === 'vscode-remote' ? 'file' : scheme; + + const provider = this._editSessionIdentifierProviders.get(scheme); + if (provider) { + return provider; + } + + await this._extensionService.activateByEvent(`onEditSession:${transformedScheme}`); + return this._editSessionIdentifierProviders.get(scheme); + } +} + +registerSingleton(IEditSessionIdentityService, EditSessionIdentityService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts b/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts new file mode 100644 index 0000000000..d5d49c5e09 --- /dev/null +++ b/src/vs/workbench/services/workspaces/common/workspaceIdentityService.ts @@ -0,0 +1,143 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { VSBuffer } from 'vs/base/common/buffer'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { isEqualOrParent, joinPath, relativePath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkspaceStateFolder } from 'vs/platform/userDataSync/common/userDataSync'; +import { EditSessionIdentityMatch, IEditSessionIdentityService } from 'vs/platform/workspace/common/editSessions'; +import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; + +export const IWorkspaceIdentityService = createDecorator('IWorkspaceIdentityService'); +export interface IWorkspaceIdentityService { + _serviceBrand: undefined; + matches(folders: IWorkspaceStateFolder[], cancellationToken: CancellationToken): Promise<((obj: any) => any) | false>; + getWorkspaceStateFolders(cancellationToken: CancellationToken): Promise; +} + +export class WorkspaceIdentityService implements IWorkspaceIdentityService { + declare _serviceBrand: undefined; + + constructor( + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IEditSessionIdentityService private readonly editSessionIdentityService: IEditSessionIdentityService + ) { } + + async getWorkspaceStateFolders(cancellationToken: CancellationToken): Promise { + const workspaceStateFolders: IWorkspaceStateFolder[] = []; + + for (const workspaceFolder of this.workspaceContextService.getWorkspace().folders) { + const workspaceFolderIdentity = await this.editSessionIdentityService.getEditSessionIdentifier(workspaceFolder, cancellationToken); + if (!workspaceFolderIdentity) { continue; } + workspaceStateFolders.push({ resourceUri: workspaceFolder.uri.toString(), workspaceFolderIdentity }); + } + + return workspaceStateFolders; + } + + async matches(incomingWorkspaceFolders: IWorkspaceStateFolder[], cancellationToken: CancellationToken): Promise<((value: any) => any) | false> { + const incomingToCurrentWorkspaceFolderUris: { [key: string]: string } = {}; + + const incomingIdentitiesToIncomingWorkspaceFolders: { [key: string]: string } = {}; + for (const workspaceFolder of incomingWorkspaceFolders) { + incomingIdentitiesToIncomingWorkspaceFolders[workspaceFolder.workspaceFolderIdentity] = workspaceFolder.resourceUri; + } + + // Precompute the identities of the current workspace folders + const currentWorkspaceFoldersToIdentities = new Map(); + for (const workspaceFolder of this.workspaceContextService.getWorkspace().folders) { + const workspaceFolderIdentity = await this.editSessionIdentityService.getEditSessionIdentifier(workspaceFolder, cancellationToken); + if (!workspaceFolderIdentity) { continue; } + currentWorkspaceFoldersToIdentities.set(workspaceFolder, workspaceFolderIdentity); + } + + // Match the current workspace folders to the incoming workspace folders + for (const [currentWorkspaceFolder, currentWorkspaceFolderIdentity] of currentWorkspaceFoldersToIdentities.entries()) { + + // Happy case: identities do not need further disambiguation + const incomingWorkspaceFolder = incomingIdentitiesToIncomingWorkspaceFolders[currentWorkspaceFolderIdentity]; + if (incomingWorkspaceFolder) { + // There is an incoming workspace folder with the exact same identity as the current workspace folder + incomingToCurrentWorkspaceFolderUris[incomingWorkspaceFolder] = currentWorkspaceFolder.uri.toString(); + continue; + } + + // Unhappy case: compare the identity of the current workspace folder to all incoming workspace folder identities + let hasCompleteMatch = false; + for (const [incomingIdentity, incomingFolder] of Object.entries(incomingIdentitiesToIncomingWorkspaceFolders)) { + if (await this.editSessionIdentityService.provideEditSessionIdentityMatch(currentWorkspaceFolder, currentWorkspaceFolderIdentity, incomingIdentity, cancellationToken) === EditSessionIdentityMatch.Complete) { + incomingToCurrentWorkspaceFolderUris[incomingFolder] = currentWorkspaceFolder.uri.toString(); + hasCompleteMatch = true; + break; + } + } + + if (hasCompleteMatch) { + continue; + } + + return false; + } + + const convertUri = (uriToConvert: URI) => { + // Figure out which current folder the incoming URI is a child of + for (const incomingFolderUriKey of Object.keys(incomingToCurrentWorkspaceFolderUris)) { + const incomingFolderUri = URI.parse(incomingFolderUriKey); + if (isEqualOrParent(incomingFolderUri, uriToConvert)) { + const currentWorkspaceFolderUri = incomingToCurrentWorkspaceFolderUris[incomingFolderUriKey]; + + // Compute the relative file path section of the uri to convert relative to the folder it came from + const relativeFilePath = relativePath(incomingFolderUri, uriToConvert); + + // Reparent the relative file path under the current workspace folder it belongs to + if (relativeFilePath) { + return joinPath(URI.parse(currentWorkspaceFolderUri), relativeFilePath); + } + } + } + + // No conversion was possible; return the original URI + return uriToConvert; + }; + + // Recursively look for any URIs in the provided object and + // replace them with the URIs of the current workspace folders + const uriReplacer = (obj: any, depth = 0) => { + if (!obj || depth > 200) { + return obj; + } + + if (obj instanceof VSBuffer || obj instanceof Uint8Array) { + return obj; + } + + if (URI.isUri(obj)) { + return convertUri(obj); + } + + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; ++i) { + obj[i] = uriReplacer(obj[i], depth + 1); + } + } else { + // walk object + for (const key in obj) { + if (Object.hasOwnProperty.call(obj, key)) { + obj[key] = uriReplacer(obj[key], depth + 1); + } + } + } + + return obj; + }; + + return uriReplacer; + } +} + +registerSingleton(IWorkspaceIdentityService, WorkspaceIdentityService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index bc3351125e..6b6a9d7c33 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -10,7 +10,7 @@ import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { IPath } from 'vs/platform/window/common/window'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; import { isVirtualResource } from 'vs/platform/workspace/common/virtualWorkspace'; @@ -22,6 +22,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { isEqualAuthority } from 'vs/base/common/resources'; import { isWeb } from 'vs/base/common/platform'; +import { IFileService } from 'vs/platform/files/common/files'; export const WORKSPACE_TRUST_ENABLED = 'security.workspace.trust.enabled'; export const WORKSPACE_TRUST_STARTUP_PROMPT = 'security.workspace.trust.startupPrompt'; @@ -118,7 +119,8 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService, - @IWorkspaceTrustEnablementService private readonly workspaceTrustEnablementService: IWorkspaceTrustEnablementService + @IWorkspaceTrustEnablementService private readonly workspaceTrustEnablementService: IWorkspaceTrustEnablementService, + @IFileService private readonly fileService: IFileService ) { super(); @@ -164,6 +166,7 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork this.remoteAuthorityResolverService.resolveAuthority(this.environmentService.remoteAuthority) .then(async result => { this._remoteAuthority = result; + await this.fileService.activateProvider(Schemas.vscodeRemote); await this.updateWorkspaceTrust(); }) .finally(() => { @@ -873,9 +876,7 @@ class WorkspaceTrustMemento { set acceptsOutOfWorkspaceFiles(value: boolean) { this._mementoObject[this._acceptsOutOfWorkspaceFilesKey] = value; - if (this._memento) { - this._memento.saveMemento(); - } + this._memento?.saveMemento(); } get isEmptyWorkspaceTrusted(): boolean | undefined { @@ -885,10 +886,8 @@ class WorkspaceTrustMemento { set isEmptyWorkspaceTrusted(value: boolean | undefined) { this._mementoObject[this._isEmptyWorkspaceTrustedKey] = value; - if (this._memento) { - this._memento.saveMemento(); - } + this._memento?.saveMemento(); } } -registerSingleton(IWorkspaceTrustRequestService, WorkspaceTrustRequestService); +registerSingleton(IWorkspaceTrustRequestService, WorkspaceTrustRequestService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts index 1e47d707ac..574f71c869 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts @@ -11,7 +11,7 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/browser/configurationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; -// import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; {{SQL CARBON EDIT}} Remove unused +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { basename } from 'vs/base/common/resources'; @@ -20,14 +20,13 @@ import { IFileService } from 'vs/platform/files/common/files'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { ILifecycleService, ShutdownReason } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ILabelService } from 'vs/platform/label/common/label'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILabelService, Verbosity } from 'vs/platform/label/common/label'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { AbstractWorkspaceEditingService } from 'vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { INativeHostService } from 'vs/platform/native/common/native'; import { isMacintosh } from 'vs/base/common/platform'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { WorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; @@ -43,7 +42,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi @INativeHostService private nativeHostService: INativeHostService, @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, @IStorageService private storageService: IStorageService, - // @IExtensionService private extensionService: IExtensionService, {{SQL CARBON EDIT}} Remove unused + @IExtensionService private extensionService: IExtensionService, @IWorkingCopyBackupService private workingCopyBackupService: IWorkingCopyBackupService, @INotificationService notificationService: INotificationService, @ICommandService commandService: ICommandService, @@ -88,59 +87,54 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi return false; // Windows/Linux: quits when last window is closed, so do not ask then } - enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL - } + const { result } = await this.dialogService.prompt({ + type: Severity.Warning, + message: localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"), + detail: localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."), + buttons: [ + { + label: localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + run: async () => { + const newWorkspacePath = await this.pickNewWorkspacePath(); + if (!newWorkspacePath || !hasWorkspaceFileExtension(newWorkspacePath)) { + return true; // keep veto if no target was provided + } - const buttons = [ - { label: mnemonicButtonLabel(localize('save', "Save")), result: ConfirmResult.SAVE }, - { label: mnemonicButtonLabel(localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }, - { label: localize('cancel', "Cancel"), result: ConfirmResult.CANCEL } - ]; - const message = localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); - const detail = localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); - const { choice } = await this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId: 2 }); + try { + await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath); - switch (buttons[choice].result) { + // Make sure to add the new workspace to the history to find it again + const newWorkspaceIdentifier = await this.workspacesService.getWorkspaceIdentifier(newWorkspacePath); + await this.workspacesService.addRecentlyOpened([{ + label: this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: Verbosity.LONG }), + workspace: newWorkspaceIdentifier, + remoteAuthority: this.environmentService.remoteAuthority // remember whether this was a remote window + }]); - // Cancel: veto unload - case ConfirmResult.CANCEL: - return true; + // Delete the untitled one + await this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); + } catch (error) { + // ignore + } - // Don't Save: delete workspace - case ConfirmResult.DONT_SAVE: - await this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); - return false; + return false; + } + }, + { + label: localize({ key: 'doNotSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + run: async () => { + await this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); - // Save: save workspace, but do not veto unload if path provided - case ConfirmResult.SAVE: { - const newWorkspacePath = await this.pickNewWorkspacePath(); - if (!newWorkspacePath || !hasWorkspaceFileExtension(newWorkspacePath)) { - return true; // keep veto if no target was provided + return false; + } } - - try { - await this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath); - - // Make sure to add the new workspace to the history to find it again - const newWorkspaceIdentifier = await this.workspacesService.getWorkspaceIdentifier(newWorkspacePath); - await this.workspacesService.addRecentlyOpened([{ - label: this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }), - workspace: newWorkspaceIdentifier, - remoteAuthority: this.environmentService.remoteAuthority // remember whether this was a remote window - }]); - - // Delete the untitled one - await this.workspacesService.deleteUntitledWorkspace(workspaceIdentifier); - } catch (error) { - // ignore - } - - return false; + ], + cancelButton: { + run: () => true // veto } - } + }); + + return result; } override async isValidTargetWorkspacePath(workspaceUri: URI): Promise { @@ -148,13 +142,9 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi // Prevent overwriting a workspace that is currently opened in another window if (windows.some(window => isWorkspaceIdentifier(window.workspace) && this.uriIdentityService.extUri.isEqual(window.workspace.configPath, workspaceUri))) { - await this.dialogService.show( - Severity.Info, + await this.dialogService.info( localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(workspaceUri)), - undefined, - { - detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.") - } + localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.") ); return false; @@ -164,6 +154,11 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi } async enterWorkspace(workspaceUri: URI): Promise { + const stopped = await this.extensionService.stopExtensionHosts(localize('restartExtensionHost.reason', "Opening a multi-root workspace.")); + if (!stopped) { + return; + } + const result = await this.doEnterWorkspace(workspaceUri); if (result) { @@ -183,4 +178,4 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi } } -registerSingleton(IWorkspaceEditingService, NativeWorkspaceEditingService, true); +registerSingleton(IWorkspaceEditingService, NativeWorkspaceEditingService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts index e263c500a1..d55282efe2 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IMainProcessService } from 'vs/platform/ipc/common/mainProcessService'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; +import { INativeHostService } from 'vs/platform/native/common/native'; // @ts-ignore: interface is implemented via proxy export class NativeWorkspacesService implements IWorkspacesService { @@ -22,4 +22,4 @@ export class NativeWorkspacesService implements IWorkspacesService { } } -registerSingleton(IWorkspacesService, NativeWorkspacesService, true); +registerSingleton(IWorkspacesService, NativeWorkspacesService, InstantiationType.Delayed); diff --git a/src/vs/workbench/test/browser/arrayOperation.test.ts b/src/vs/workbench/test/browser/arrayOperation.test.ts new file mode 100644 index 0000000000..9738ab0fa1 --- /dev/null +++ b/src/vs/workbench/test/browser/arrayOperation.test.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { ArrayEdit, MonotonousIndexTransformer, SingleArrayEdit } from 'vs/workbench/services/textMate/browser/arrayOperation'; + +suite('array operation', () => { + function seq(start: number, end: number) { + const result: number[] = []; + for (let i = start; i < end; i++) { + result.push(i); + } + return result; + } + + test('simple', () => { + const edit = new ArrayEdit([ + new SingleArrayEdit(4, 3, 2), + new SingleArrayEdit(8, 0, 2), + new SingleArrayEdit(9, 2, 0), + ]); + + const arr = seq(0, 15).map(x => `item${x}`); + const newArr = arr.slice(); + + edit.applyToArray(newArr); + assert.deepStrictEqual(newArr, [ + 'item0', + 'item1', + 'item2', + 'item3', + undefined, + undefined, + 'item7', + undefined, + undefined, + 'item8', + 'item11', + 'item12', + 'item13', + 'item14', + ]); + + const transformer = new MonotonousIndexTransformer(edit); + assert.deepStrictEqual( + seq(0, 15).map((x) => { + const t = transformer.transform(x); + let r = `arr[${x}]: ${arr[x]} -> `; + if (t !== undefined) { + r += `newArr[${t}]: ${newArr[t]}`; + } else { + r += 'undefined'; + } + return r; + }), + [ + 'arr[0]: item0 -> newArr[0]: item0', + 'arr[1]: item1 -> newArr[1]: item1', + 'arr[2]: item2 -> newArr[2]: item2', + 'arr[3]: item3 -> newArr[3]: item3', + 'arr[4]: item4 -> undefined', + 'arr[5]: item5 -> undefined', + 'arr[6]: item6 -> undefined', + 'arr[7]: item7 -> newArr[6]: item7', + 'arr[8]: item8 -> newArr[9]: item8', + 'arr[9]: item9 -> undefined', + 'arr[10]: item10 -> undefined', + 'arr[11]: item11 -> newArr[10]: item11', + 'arr[12]: item12 -> newArr[11]: item12', + 'arr[13]: item13 -> newArr[12]: item13', + 'arr[14]: item14 -> newArr[13]: item14', + ] + ); + }); +}); diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index dfa468b769..749103d799 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -36,21 +36,21 @@ suite('Workbench parts', () => { super('myPart', { hasTitle: true }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } - override createTitleArea(parent: HTMLElement): HTMLElement { + protected override createTitleArea(parent: HTMLElement): HTMLElement { assert.strictEqual(parent, this.expectedParent); return super.createTitleArea(parent)!; } - override createContentArea(parent: HTMLElement): HTMLElement { + protected override createContentArea(parent: HTMLElement): HTMLElement { assert.strictEqual(parent, this.expectedParent); return super.createContentArea(parent)!; } - override getMemento(scope: StorageScope, target: StorageTarget) { + testGetMemento(scope: StorageScope, target: StorageTarget) { return super.getMemento(scope, target); } - override saveState(): void { + testSaveState(): void { return super.saveState(); } } @@ -61,7 +61,7 @@ suite('Workbench parts', () => { super('myPart2', { hasTitle: true }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } - override createTitleArea(parent: HTMLElement): HTMLElement { + protected override createTitleArea(parent: HTMLElement): HTMLElement { const titleContainer = append(parent, $('div')); const titleLabel = append(titleContainer, $('span')); titleLabel.id = 'myPart.title'; @@ -70,7 +70,7 @@ suite('Workbench parts', () => { return titleContainer; } - override createContentArea(parent: HTMLElement): HTMLElement { + protected override createContentArea(parent: HTMLElement): HTMLElement { const contentContainer = append(parent, $('div')); const contentSpan = append(contentContainer, $('span')); contentSpan.id = 'myPart.content'; @@ -86,11 +86,11 @@ suite('Workbench parts', () => { super('myPart2', { hasTitle: false }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } - override createTitleArea(parent: HTMLElement): HTMLElement { + protected override createTitleArea(parent: HTMLElement): HTMLElement { return null!; } - override createContentArea(parent: HTMLElement): HTMLElement { + protected override createContentArea(parent: HTMLElement): HTMLElement { const contentContainer = append(parent, $('div')); const contentSpan = append(contentContainer, $('span')); contentSpan.id = 'myPart.content'; @@ -124,17 +124,17 @@ suite('Workbench parts', () => { assert.strictEqual(part.getId(), 'myPart'); // Memento - let memento = part.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE) as any; + let memento = part.testGetMemento(StorageScope.PROFILE, StorageTarget.MACHINE) as any; assert(memento); memento.foo = 'bar'; memento.bar = [1, 2, 3]; - part.saveState(); + part.testSaveState(); // Re-Create to assert memento contents part = new MyPart(b); - memento = part.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); + memento = part.testGetMemento(StorageScope.PROFILE, StorageTarget.MACHINE); assert(memento); assert.strictEqual(memento.foo, 'bar'); assert.strictEqual(memento.bar.length, 3); @@ -143,9 +143,9 @@ suite('Workbench parts', () => { delete memento.foo; delete memento.bar; - part.saveState(); + part.testSaveState(); part = new MyPart(b); - memento = part.getMemento(StorageScope.PROFILE, StorageTarget.MACHINE); + memento = part.testGetMemento(StorageScope.PROFILE, StorageTarget.MACHINE); assert(memento); assert.strictEqual(isEmptyObject(memento), true); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editor.test.ts b/src/vs/workbench/test/browser/parts/editor/editor.test.ts index 7bc3d3cdd1..ee7f2b3691 100644 --- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts @@ -434,9 +434,9 @@ suite('Workbench editor utils', () => { for (const resource of resources) { if (custom) { - await accessor.editorService.openEditor(new TestFileEditorInput(resource, 'testTypeId'), { pinned: true, override: EditorResolution.DISABLED }); + await accessor.editorService.openEditor(new TestFileEditorInput(resource, 'testTypeId'), { pinned: true }); } else if (sideBySide) { - await accessor.editorService.openEditor(instantiationService.createInstance(SideBySideEditorInput, 'testSideBySideEditor', undefined, new TestFileEditorInput(resource, 'testTypeId'), new TestFileEditorInput(resource, 'testTypeId')), { pinned: true, override: EditorResolution.DISABLED }); + await accessor.editorService.openEditor(instantiationService.createInstance(SideBySideEditorInput, 'testSideBySideEditor', undefined, new TestFileEditorInput(resource, 'testTypeId'), new TestFileEditorInput(resource, 'testTypeId')), { pinned: true }); } else { await accessor.editorService.openEditor({ resource, options: { pinned: true } }); } diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts index c16475c10d..97ec969335 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts @@ -2124,7 +2124,7 @@ suite('EditorGroupModel', () => { assert.strictEqual(group.indexOf(input2), 1); assert.strictEqual(group.indexOf(input3), 2); - group.moveEditor(input1, 2); // moved out of sticky range + group.moveEditor(input1, 2); // moved out of sticky range// assert.strictEqual(group.isSticky(input1), false); assert.strictEqual(group.isSticky(input2), true); assert.strictEqual(group.isSticky(input3), false); @@ -2171,7 +2171,7 @@ suite('EditorGroupModel', () => { assert.strictEqual(group.indexOf(input2), 1); assert.strictEqual(group.indexOf(input3), 2); - group.moveEditor(input3, 0); // moved into sticky range + group.moveEditor(input3, 0); // moved into sticky range// assert.strictEqual(group.isSticky(input1), true); assert.strictEqual(group.isSticky(input2), false); assert.strictEqual(group.isSticky(input3), true); @@ -2325,4 +2325,40 @@ suite('EditorGroupModel', () => { assert.strictEqual(group2Events.opened[1].editor, input2group2); assert.strictEqual(group2Events.opened[1].editorIndex, 1); }); + + test('moving editor sends sticky event when sticky changes', () => { + const group1 = createEditorGroupModel(); + + const input1group1 = input(); + const input2group1 = input(); + const input3group1 = input(); + + // Open all the editors + group1.openEditor(input1group1, { pinned: true, active: true, index: 0, sticky: true }); + group1.openEditor(input2group1, { pinned: true, active: false, index: 1 }); + group1.openEditor(input3group1, { pinned: true, active: false, index: 2 }); + + const group1Events = groupListener(group1); + + group1.moveEditor(input2group1, 0); + assert.strictEqual(group1Events.sticky[0].editor, input2group1); + assert.strictEqual(group1Events.sticky[0].editorIndex, 0); + + const group2 = createEditorGroupModel(); + + const input1group2 = input(); + const input2group2 = input(); + const input3group2 = input(); + + // Open all the editors + group2.openEditor(input1group2, { pinned: true, active: true, index: 0, sticky: true }); + group2.openEditor(input2group2, { pinned: true, active: false, index: 1 }); + group2.openEditor(input3group2, { pinned: true, active: false, index: 2 }); + + const group2Events = groupListener(group2); + + group2.moveEditor(input1group2, 1); + assert.strictEqual(group2Events.unsticky[0].editor, input1group2); + assert.strictEqual(group2Events.unsticky[0].editorIndex, 1); + }); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts index 2877079062..2e9854ba3a 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts @@ -4,14 +4,80 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { isEditorInput, isResourceDiffEditorInput, isResourceEditorInput, isResourceMergeEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; -import { TestEditorInput } from 'vs/workbench/test/browser/workbenchTestServices'; +import { IResourceEditorInput, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { DEFAULT_EDITOR_ASSOCIATION, IResourceDiffEditorInput, IResourceMergeEditorInput, IResourceSideBySideEditorInput, IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; // {{SQL CARBON EDIT}} - remove unused imports +import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; +// import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; +import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput'; +import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; +import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { TestEditorInput, TestServiceAccessor, workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices'; suite('EditorInput', () => { - class MyEditorInput extends EditorInput { + let instantiationService: IInstantiationService; + let accessor: TestServiceAccessor; + let disposables: DisposableStore; + + const testResource: URI = URI.from({ scheme: 'random', path: '/path' }); + const untypedResourceEditorInput: IResourceEditorInput = { resource: testResource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; + const untypedTextResourceEditorInput: ITextResourceEditorInput = { resource: testResource, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; + const untypedResourceSideBySideEditorInput: IResourceSideBySideEditorInput = { primary: untypedResourceEditorInput, secondary: untypedResourceEditorInput, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; + const untypedUntitledResourceEditorinput: IUntitledTextResourceEditorInput = { resource: URI.from({ scheme: Schemas.untitled, path: '/path' }), options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; + const untypedResourceDiffEditorInput: IResourceDiffEditorInput = { original: untypedResourceEditorInput, modified: untypedResourceEditorInput, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; + const untypedResourceMergeEditorInput: IResourceMergeEditorInput = { base: untypedResourceEditorInput, input1: untypedResourceEditorInput, input2: untypedResourceEditorInput, result: untypedResourceEditorInput, options: { override: DEFAULT_EDITOR_ASSOCIATION.id } }; + + // Function to easily remove the overrides from the untyped inputs + const stripOverrides = () => { + if ( + !untypedResourceEditorInput.options || + !untypedTextResourceEditorInput.options || + !untypedUntitledResourceEditorinput.options || + !untypedResourceDiffEditorInput.options || + !untypedResourceMergeEditorInput.options + ) { + throw new Error('Malformed options on untyped inputs'); + } + // Some of the tests mutate the overrides so we want to reset them on each test + untypedResourceEditorInput.options.override = undefined; + untypedTextResourceEditorInput.options.override = undefined; + untypedUntitledResourceEditorinput.options.override = undefined; + untypedResourceDiffEditorInput.options.override = undefined; + untypedResourceMergeEditorInput.options.override = undefined; + }; + + setup(() => { + disposables = new DisposableStore(); + instantiationService = workbenchInstantiationService(undefined, disposables); + accessor = instantiationService.createInstance(TestServiceAccessor); + + if ( + !untypedResourceEditorInput.options || + !untypedTextResourceEditorInput.options || + !untypedUntitledResourceEditorinput.options || + !untypedResourceDiffEditorInput.options || + !untypedResourceMergeEditorInput.options + ) { + throw new Error('Malformed options on untyped inputs'); + } + // Some of the tests mutate the overrides so we want to reset them on each test + untypedResourceEditorInput.options.override = DEFAULT_EDITOR_ASSOCIATION.id; + untypedTextResourceEditorInput.options.override = DEFAULT_EDITOR_ASSOCIATION.id; + untypedUntitledResourceEditorinput.options.override = DEFAULT_EDITOR_ASSOCIATION.id; + untypedResourceDiffEditorInput.options.override = DEFAULT_EDITOR_ASSOCIATION.id; + untypedResourceMergeEditorInput.options.override = DEFAULT_EDITOR_ASSOCIATION.id; + }); + + teardown(() => { + disposables.dispose(); + }); + + /*class MyEditorInput extends EditorInput { readonly resource = undefined; override get typeId(): string { return 'myEditorInput'; } @@ -29,7 +95,7 @@ suite('EditorInput', () => { assert.ok(!isEditorInput({})); assert.ok(!isResourceEditorInput(input)); - assert.ok(!isUntitledResourceEditorInput(input)); + assert.ok(!isUntitledResourceEditorInput(input as any)); assert.ok(!isResourceDiffEditorInput(input)); assert.ok(!isResourceMergeEditorInput(input)); assert.ok(!isResourceSideBySideEditorInput(input)); @@ -45,7 +111,7 @@ suite('EditorInput', () => { input.dispose(); assert.strictEqual(counter, 1); - }); + });*/ test('untyped matches', () => { const testInputID = 'untypedMatches'; @@ -62,4 +128,112 @@ suite('EditorInput', () => { assert.ok(!testInput.matches(testUntypedInputWrong)); }); + + test('Untpyed inputs properly match TextResourceEditorInput', () => { + const textResourceEditorInput = instantiationService.createInstance(TextResourceEditorInput, testResource, undefined, undefined, undefined, undefined); + + assert.ok(textResourceEditorInput.matches(untypedResourceEditorInput)); + assert.ok(textResourceEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!textResourceEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(!textResourceEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(!textResourceEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(!textResourceEditorInput.matches(untypedResourceMergeEditorInput)); + + textResourceEditorInput.dispose(); + }); + + test('Untyped inputs properly match FileEditorInput', () => { + const fileEditorInput = instantiationService.createInstance(FileEditorInput, testResource, undefined, undefined, undefined, undefined, undefined, undefined); + + assert.ok(fileEditorInput.matches(untypedResourceEditorInput)); + assert.ok(fileEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!fileEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(!fileEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(!fileEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(!fileEditorInput.matches(untypedResourceMergeEditorInput)); + + // Now we remove the override on the untyped to ensure that FileEditorInput supports lightweight resource matching + stripOverrides(); + + assert.ok(fileEditorInput.matches(untypedResourceEditorInput)); + assert.ok(fileEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!fileEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(!fileEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(!fileEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(!fileEditorInput.matches(untypedResourceMergeEditorInput)); + + fileEditorInput.dispose(); + }); + + test('Untyped inputs properly match MergeEditorInput', () => { + const mergeData: MergeEditorInputData = { uri: testResource, description: undefined, detail: undefined, title: undefined }; + const mergeEditorInput = instantiationService.createInstance(MergeEditorInput, testResource, mergeData, mergeData, testResource); + + assert.ok(!mergeEditorInput.matches(untypedResourceEditorInput)); + assert.ok(!mergeEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!mergeEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(!mergeEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(!mergeEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(mergeEditorInput.matches(untypedResourceMergeEditorInput)); + + stripOverrides(); + + assert.ok(!mergeEditorInput.matches(untypedResourceEditorInput)); + assert.ok(!mergeEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!mergeEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(!mergeEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(!mergeEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(mergeEditorInput.matches(untypedResourceMergeEditorInput)); + + mergeEditorInput.dispose(); + }); + + test('Untyped inputs properly match UntitledTextEditorInput', () => { + const untitledModel = accessor.untitledTextEditorService.create({ associatedResource: { authority: '', path: '/path', fragment: '', query: '' } }); + const untitledTextEditorInput: UntitledTextEditorInput = instantiationService.createInstance(UntitledTextEditorInput, untitledModel); + + assert.ok(!untitledTextEditorInput.matches(untypedResourceEditorInput)); + assert.ok(!untitledTextEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!untitledTextEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(untitledTextEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(!untitledTextEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(!untitledTextEditorInput.matches(untypedResourceMergeEditorInput)); + + stripOverrides(); + + assert.ok(!untitledTextEditorInput.matches(untypedResourceEditorInput)); + assert.ok(!untitledTextEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!untitledTextEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(untitledTextEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(!untitledTextEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(!untitledTextEditorInput.matches(untypedResourceMergeEditorInput)); + + untitledTextEditorInput.dispose(); + }); + + test('Untyped inputs properly match DiffEditorInput', () => { + const fileEditorInput1 = instantiationService.createInstance(FileEditorInput, testResource, undefined, undefined, undefined, undefined, undefined, undefined); + const fileEditorInput2 = instantiationService.createInstance(FileEditorInput, testResource, undefined, undefined, undefined, undefined, undefined, undefined); + const diffEditorInput: DiffEditorInput = instantiationService.createInstance(DiffEditorInput, undefined, undefined, fileEditorInput1, fileEditorInput2, false); + + assert.ok(!diffEditorInput.matches(untypedResourceEditorInput)); + assert.ok(!diffEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!diffEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(!diffEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(diffEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(!diffEditorInput.matches(untypedResourceMergeEditorInput)); + + stripOverrides(); + + assert.ok(!diffEditorInput.matches(untypedResourceEditorInput)); + assert.ok(!diffEditorInput.matches(untypedTextResourceEditorInput)); + assert.ok(!diffEditorInput.matches(untypedResourceSideBySideEditorInput)); + assert.ok(!diffEditorInput.matches(untypedUntitledResourceEditorinput)); + assert.ok(diffEditorInput.matches(untypedResourceDiffEditorInput)); + assert.ok(!diffEditorInput.matches(untypedResourceMergeEditorInput)); + + diffEditorInput.dispose(); + fileEditorInput1.dispose(); + fileEditorInput2.dispose(); + }); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts index a431fadc5f..1651afade8 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorModel.test.ts @@ -40,7 +40,7 @@ suite('EditorModel', () => { class MyEditorModel extends EditorModel { } class MyTextEditorModel extends BaseTextEditorModel { - override createTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredLanguageId?: string) { + testCreateTextEditorModel(value: ITextBufferFactory, resource?: URI, preferredLanguageId?: string) { return super.createTextEditorModel(value, resource, preferredLanguageId); } @@ -99,7 +99,7 @@ suite('EditorModel', () => { const model = new MyTextEditorModel(modelService, languageService, instantiationService.createInstance(LanguageDetectionService), instantiationService.createInstance(TestAccessibilityService)); await model.resolve(); - model.createTextEditorModel(createTextBufferFactory('foo'), null!, Mimes.text); + model.testCreateTextEditorModel(createTextBufferFactory('foo'), null!, Mimes.text); assert.strictEqual(model.isResolved(), true); model.dispose(); }); diff --git a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts index 5a732c893e..d98f5ebbe0 100644 --- a/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/editorPane.test.ts @@ -43,7 +43,7 @@ class TestEditor extends EditorPane { override getId(): string { return 'testEditor'; } layout(): void { } - createEditor(): any { } + protected createEditor(): any { } } export class OtherTestEditor extends EditorPane { @@ -55,7 +55,7 @@ export class OtherTestEditor extends EditorPane { override getId(): string { return 'testOtherEditor'; } layout(): void { } - createEditor(): any { } + protected createEditor(): any { } } class TestInputSerializer implements IEditorSerializer { @@ -464,7 +464,7 @@ suite('EditorPane', () => { override getId(): string { return 'trustRequiredTestEditor'; } layout(): void { } - createEditor(): any { } + protected createEditor(): any { } } class TrustRequiredTestInput extends EditorInput { diff --git a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts index c53841cd2e..5de6c0df09 100644 --- a/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts @@ -12,6 +12,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IFileService } from 'vs/platform/files/common/files'; import { EditorInputCapabilities, Verbosity } from 'vs/workbench/common/editor'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; suite('ResourceEditorInput', () => { @@ -25,9 +26,10 @@ suite('ResourceEditorInput', () => { constructor( resource: URI, @ILabelService labelService: ILabelService, - @IFileService fileService: IFileService + @IFileService fileService: IFileService, + @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService ) { - super(resource, resource, labelService, fileService); + super(resource, resource, labelService, fileService, filesConfigurationService); } } diff --git a/src/vs/workbench/test/browser/viewlet.test.ts b/src/vs/workbench/test/browser/viewlet.test.ts index e3b3e954de..4fc0759af9 100644 --- a/src/vs/workbench/test/browser/viewlet.test.ts +++ b/src/vs/workbench/test/browser/viewlet.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { Registry } from 'vs/platform/registry/common/platform'; import { PaneCompositeDescriptor, Extensions, PaneCompositeRegistry, PaneComposite } from 'vs/workbench/browser/panecomposite'; import { isFunction } from 'vs/base/common/types'; +import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; suite('Viewlets', () => { @@ -20,7 +21,11 @@ suite('Viewlets', () => { throw new Error('Method not implemented.'); } - createViewPaneContainer() { return null!; } + override setBoundarySashes(sashes: IBoundarySashes): void { + throw new Error('Method not implemented.'); + } + + protected override createViewPaneContainer() { return null!; } } test('ViewletDescriptor API', function () { diff --git a/src/vs/workbench/test/browser/webview.test.ts b/src/vs/workbench/test/browser/webview.test.ts index 4c90cfbeb0..188cd9a34f 100644 --- a/src/vs/workbench/test/browser/webview.test.ts +++ b/src/vs/workbench/test/browser/webview.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { parentOriginHash } from 'vs/workbench/browser/webview'; +import { parentOriginHash } from 'vs/workbench/browser/iframe'; suite('parentOriginHash', () => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 1f3441327e..68a84086c4 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -27,7 +27,7 @@ import { FileOperationEvent, IFileService, IFileStat, IFileStatResult, FileChang import { IModelService } from 'vs/editor/common/services/model'; import { LanguageService } from 'vs/editor/common/services/languageService'; import { ModelService } from 'vs/editor/common/services/modelService'; -import { IResourceEncoding, ITextFileService, IReadTextFileOptions, ITextFileStreamContent, IWriteTextFileOptions, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResourceEncoding, ITextFileService, IReadTextFileOptions, ITextFileStreamContent, IWriteTextFileOptions, ITextFileEditorModel, ITextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textfiles'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; @@ -35,7 +35,8 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { MenuBarVisibility, IWindowOpenable, IOpenWindowOptions, IOpenEmptyWindowOptions } from 'vs/platform/window/common/window'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ThemeIcon } from 'vs/base/common/themables'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfiguration'; import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; @@ -52,11 +53,11 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/common/decorations'; import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IEditorReplacement, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions, GroupOrientation, ICloseAllEditorsOptions, ICloseEditorsFilter } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { IEditorService, ISaveEditorsOptions, IRevertAllEditorsOptions, PreferredGroup, IEditorsChangeEvent } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, ISaveEditorsOptions, IRevertAllEditorsOptions, PreferredGroup, IEditorsChangeEvent, ISaveEditorsResult } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor'; import { Dimension, IDimension } from 'vs/base/browser/dom'; -import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { ILoggerService, ILogService, NullLogService } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; import { timeout } from 'vs/base/common/async'; import { PaneComposite, PaneCompositeDescriptor } from 'vs/workbench/browser/panecomposite'; @@ -98,12 +99,11 @@ import { IInputBox, IInputOptions, IPickOptions, IQuickInputButton, IQuickInputS import { QuickInputService } from 'vs/workbench/services/quickinput/browser/quickInputService'; import { IListService } from 'vs/platform/list/browser/listService'; import { win32, posix } from 'vs/base/common/path'; -import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService, TestProductService, createFileStat } from 'vs/workbench/test/common/workbenchTestServices'; +import { TestContextService, TestStorageService, TestTextResourcePropertiesService, TestExtensionService, TestProductService, createFileStat, TestLoggerService } from 'vs/workbench/test/common/workbenchTestServices'; import { IViewsService, IView, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { newWriteableStream, ReadableStreamEvents } from 'vs/base/common/stream'; import { EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/browser/textFileService'; @@ -124,7 +124,7 @@ import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from import { TestWorkspaceTrustManagementService, TestWorkspaceTrustRequestService } from 'vs/workbench/services/workspaces/test/common/testWorkspaceTrustService'; import { IExtensionTerminalProfile, IShellLaunchConfig, ITerminalProfile, TerminalIcon, TerminalLocation, TerminalShellType } from 'vs/platform/terminal/common/terminal'; import { ICreateTerminalOptions, IDeserializedTerminalEditorInput, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { assertIsDefined, isArray } from 'vs/base/common/types'; +import { assertIsDefined } from 'vs/base/common/types'; import { IRegisterContributedProfileArgs, IShellLaunchConfigResolveOptions, ITerminalBackend, ITerminalProfileProvider, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal'; import { EditorResolverService } from 'vs/workbench/services/editor/browser/editorResolverService'; import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; @@ -140,7 +140,6 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { IPaneCompositePart, IPaneCompositeSelectorPart } from 'vs/workbench/browser/parts/paneCompositePart'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService'; -import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; import { IGroupModelChangeEvent } from 'vs/workbench/common/editor/editorGroupModel'; import { env } from 'vs/base/common/process'; @@ -156,16 +155,18 @@ import { TestEditorWorkerService } from 'vs/editor/test/common/services/testEdit import { IExtensionHostExitInfo, IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; import { IDiagnosticInfoOptions, IDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; -import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; -import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection'; +import { ExtensionType, IExtension, IExtensionDescription, IRelaxedExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService'; -import { IUserDataProfilesService, toUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService, toUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant, Metadata } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; +import { IHoverOptions, IHoverService, IHoverWidget } from 'vs/workbench/services/hover/browser/hover'; +import { IRemoteExtensionsScannerService } from 'vs/platform/remote/common/remoteExtensionsScanner'; +import { IRemoteSocketFactoryService, RemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined, undefined, undefined, undefined, undefined); @@ -194,7 +195,7 @@ export class TestTextResourceEditor extends TextResourceEditor { export class TestTextFileEditor extends TextFileEditor { protected override createEditorControl(parent: HTMLElement, configuration: any): void { - this.editorControl = this.instantiationService.createInstance(TestCodeEditor, parent, configuration, {}); + this.editorControl = this.instantiationService.createInstance(TestCodeEditor, parent, configuration, { contributions: [] }); } setSelection(selection: Selection | undefined, reason: EditorPaneSelectionChangeReason): void { @@ -223,7 +224,7 @@ export interface ITestInstantiationService extends IInstantiationService { } export class TestWorkingCopyService extends WorkingCopyService { - override unregisterWorkingCopy(workingCopy: IWorkingCopy): void { + testUnregisterWorkingCopy(workingCopy: IWorkingCopy): void { return super.unregisterWorkingCopy(workingCopy); } } @@ -232,6 +233,7 @@ export function workbenchInstantiationService( overrides?: { environmentService?: (instantiationService: IInstantiationService) => IEnvironmentService; fileService?: (instantiationService: IInstantiationService) => IFileService; + workingCopyBackupService?: (instantiationService: IInstantiationService) => IWorkingCopyBackupService; configurationService?: (instantiationService: IInstantiationService) => TestConfigurationService; textFileService?: (instantiationService: IInstantiationService) => ITextFileService; pathService?: (instantiationService: IInstantiationService) => IPathService; @@ -261,7 +263,6 @@ export function workbenchInstantiationService( } }); instantiationService.stub(IConfigurationService, configService); - instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService(contextKeyService, configService, workspaceContextService))); instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); instantiationService.stub(IUntitledTextEditorService, disposables.add(instantiationService.createInstance(UntitledTextEditorService))); instantiationService.stub(IStorageService, disposables.add(new TestStorageService())); @@ -287,10 +288,11 @@ export function workbenchInstantiationService( const fileService = overrides?.fileService ? overrides.fileService(instantiationService) : new TestFileService(); instantiationService.stub(IFileService, fileService); const uriIdentityService = new UriIdentityService(fileService); + instantiationService.stub(IFilesConfigurationService, disposables.add(new TestFilesConfigurationService(contextKeyService, configService, workspaceContextService, environmentService, uriIdentityService, fileService))); instantiationService.stub(IUriIdentityService, uriIdentityService); const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, new NullLogService())); instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); - instantiationService.stub(IWorkingCopyBackupService, new TestWorkingCopyBackupService()); + instantiationService.stub(IWorkingCopyBackupService, overrides?.workingCopyBackupService ? overrides?.workingCopyBackupService(instantiationService) : new TestWorkingCopyBackupService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(INotificationService, new TestNotificationService()); instantiationService.stub(IUntitledTextEditorService, disposables.add(instantiationService.createInstance(UntitledTextEditorService))); @@ -303,6 +305,7 @@ export function workbenchInstantiationService( instantiationService.stub(ITextFileService, overrides?.textFileService ? overrides.textFileService(instantiationService) : disposables.add(instantiationService.createInstance(TestTextFileService))); instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextModelService, disposables.add(instantiationService.createInstance(TextModelResolverService))); + instantiationService.stub(ILoggerService, new TestLoggerService(TestEnvironmentService.logsHome)); instantiationService.stub(ILogService, new NullLogService()); const editorGroupService = new TestEditorGroupsService([new TestEditorGroupView(0)]); instantiationService.stub(IEditorGroupsService, editorGroupService); @@ -316,11 +319,13 @@ export function workbenchInstantiationService( instantiationService.stub(ICodeEditorService, disposables.add(new CodeEditorService(editorService, themeService, configService))); instantiationService.stub(IPaneCompositePartService, new TestPaneCompositeService()); instantiationService.stub(IListService, new TestListService()); - instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService))); + const hoverService = instantiationService.stub(IHoverService, instantiationService.createInstance(TestHoverService)); + instantiationService.stub(IQuickInputService, disposables.add(new QuickInputService(configService, instantiationService, keybindingService, contextKeyService, themeService, layoutService, hoverService))); instantiationService.stub(IWorkspacesService, new TestWorkspacesService()); instantiationService.stub(IWorkspaceTrustManagementService, new TestWorkspaceTrustManagementService()); instantiationService.stub(ITerminalInstanceService, new TestTerminalInstanceService()); instantiationService.stub(IElevatedFileService, new BrowserElevatedFileService()); + instantiationService.stub(IRemoteSocketFactoryService, new RemoteSocketFactoryService()); return instantiationService; } @@ -377,9 +382,7 @@ export class TestTextFileService extends BrowserTextFileService { @IDialogService dialogService: IDialogService, @IFileDialogService fileDialogService: IFileDialogService, @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @IProductService productService: IProductService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, - @ITextModelService textModelService: ITextModelService, @ICodeEditorService codeEditorService: ICodeEditorService, @IPathService pathService: IPathService, @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, @@ -400,7 +403,6 @@ export class TestTextFileService extends BrowserTextFileService { fileDialogService, textResourceConfigurationService, filesConfigurationService, - textModelService, codeEditorService, pathService, workingCopyFileService, @@ -434,7 +436,8 @@ export class TestTextFileService extends BrowserTextFileService { encoding: 'utf8', value: await createTextBufferFactoryFromStream(content.value), size: 10, - readonly: false + readonly: false, + locked: false }; } @@ -483,7 +486,7 @@ class TestEnvironmentServiceWithArgs extends BrowserWorkbenchEnvironmentService args = []; } -export const TestEnvironmentService = new TestEnvironmentServiceWithArgs('', undefined!, Object.create(null), TestProductService); +export const TestEnvironmentService = new TestEnvironmentServiceWithArgs('', URI.file('tests').with({ scheme: 'vscode-tests' }), Object.create(null), TestProductService); export class TestProgressService implements IProgressService { @@ -732,6 +735,24 @@ export class TestSideBarPart implements IPaneCompositePart { layout(width: number, height: number, top: number, left: number): void { } } +class TestHoverService implements IHoverService { + private currentHover: IHoverWidget | undefined; + _serviceBrand: undefined; + showHover(options: IHoverOptions, focus?: boolean | undefined): IHoverWidget | undefined { + this.currentHover = new class implements IHoverWidget { + private _isDisposed = false; + get isDisposed(): boolean { return this._isDisposed; } + dispose(): void { + this._isDisposed = true; + } + }; + return this.currentHover; + } + hideHover(): void { + this.currentHover?.dispose(); + } +} + export class TestPanelPart implements IPaneCompositePart, IPaneCompositeSelectorPart { declare readonly _serviceBrand: undefined; @@ -819,6 +840,7 @@ export class TestEditorGroupsService implements IEditorGroupsService { setSize(_group: number | IEditorGroup, _size: { width: number; height: number }): void { } arrangeGroups(_arrangement: GroupsArrangement): void { } applyLayout(_layout: EditorGroupLayout): void { } + getLayout(): EditorGroupLayout { throw new Error('not implemented'); } setGroupOrientation(_orientation: GroupOrientation): void { } addGroup(_location: number | IEditorGroup, _direction: GroupDirection, _options?: IAddGroupOptions): IEditorGroup { throw new Error('not implemented'); } removeGroup(_group: number | IEditorGroup): void { } @@ -977,8 +999,8 @@ export class TestEditorService implements EditorServiceImpl { isOpened(_editor: IResourceEditorInputIdentifier): boolean { return false; } isVisible(_editor: EditorInput): boolean { return false; } replaceEditors(_editors: any, _group: any) { return Promise.resolve(undefined); } - save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } - saveAll(options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } + save(editors: IEditorIdentifier[], options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } + saveAll(options?: ISaveEditorsOptions): Promise { throw new Error('Method not implemented.'); } revert(editors: IEditorIdentifier[], options?: IRevertOptions): Promise { throw new Error('Method not implemented.'); } revertAll(options?: IRevertAllEditorsOptions): Promise { throw new Error('Method not implemented.'); } // {{SQL CARBON EDIT}} add createEditorInput back @@ -1163,8 +1185,6 @@ export function toTypedWorkingCopyId(resource: URI, typeId = 'testBackupTypeId') export class InMemoryTestWorkingCopyBackupService extends BrowserWorkingCopyBackupService { - override readonly fileService: IFileService; - private backupResourceJoiners: Function[]; private discardBackupJoiners: Function[]; @@ -1179,12 +1199,15 @@ export class InMemoryTestWorkingCopyBackupService extends BrowserWorkingCopyBack super(new TestContextService(TestWorkspace), environmentService, fileService, logService); - this.fileService = fileService; this.backupResourceJoiners = []; this.discardBackupJoiners = []; this.discardedBackups = []; } + testGetFileService(): IFileService { + return this.fileService; + } + joinBackupResource(): Promise { return new Promise(resolve => this.backupResourceJoiners.push(resolve)); } @@ -1356,10 +1379,11 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { } export class TestInMemoryFileSystemProvider extends InMemoryFileSystemProvider implements IFileSystemProviderWithFileReadStreamCapability { - override readonly capabilities: FileSystemProviderCapabilities = - FileSystemProviderCapabilities.FileReadWrite - | FileSystemProviderCapabilities.PathCaseSensitive - | FileSystemProviderCapabilities.FileReadStream; + override get capabilities(): FileSystemProviderCapabilities { + return FileSystemProviderCapabilities.FileReadWrite + | FileSystemProviderCapabilities.PathCaseSensitive + | FileSystemProviderCapabilities.FileReadStream; + } readFileStream(resource: URI): ReadableStreamEvents { const BUFFER_SIZE = 64 * 1024; @@ -1421,7 +1445,7 @@ export class TestHostService implements IHostService { export class TestFilesConfigurationService extends FilesConfigurationService { - override onFilesConfigurationChange(configuration: any): void { + testOnFilesConfigurationChange(configuration: any): void { super.onFilesConfigurationChange(configuration); } } @@ -1470,7 +1494,7 @@ export function registerTestEditor(id: string, inputs: SyncDescriptor { throw new Error('Method not implemented.'); } - createInstance(options: ICreateTerminalOptions, target?: TerminalLocation): ITerminalInstance { throw new Error('Method not implemented.'); } - getBackend(remoteAuthority?: string): ITerminalBackend | undefined { throw new Error('Method not implemented.'); } + createInstance(options: ICreateTerminalOptions, target: TerminalLocation): ITerminalInstance { throw new Error('Method not implemented.'); } + async getBackend(remoteAuthority?: string): Promise { throw new Error('Method not implemented.'); } + didRegisterBackend(remoteAuthority?: string): void { throw new Error('Method not implemented.'); } } export class TestTerminalEditorService implements ITerminalEditorService { @@ -1786,18 +1805,17 @@ export class TestTerminalEditorService implements ITerminalEditorService { onDidChangeActiveInstance = Event.None; onDidChangeInstances = Event.None; openEditor(instance: ITerminalInstance, editorOptions?: TerminalEditorLocation): Promise { throw new Error('Method not implemented.'); } - detachActiveEditorInstance(): ITerminalInstance { throw new Error('Method not implemented.'); } detachInstance(instance: ITerminalInstance): void { throw new Error('Method not implemented.'); } splitInstance(instanceToSplit: ITerminalInstance, shellLaunchConfig?: IShellLaunchConfig): ITerminalInstance { throw new Error('Method not implemented.'); } - revealActiveEditor(preserveFocus?: boolean): void { throw new Error('Method not implemented.'); } - resolveResource(instance: ITerminalInstance | URI): URI { throw new Error('Method not implemented.'); } + revealActiveEditor(preserveFocus?: boolean): Promise { throw new Error('Method not implemented.'); } + resolveResource(instance: ITerminalInstance): URI { throw new Error('Method not implemented.'); } reviveInput(deserializedInput: IDeserializedTerminalEditorInput): TerminalEditorInput { throw new Error('Method not implemented.'); } getInputFromResource(resource: URI): TerminalEditorInput { throw new Error('Method not implemented.'); } setActiveInstance(instance: ITerminalInstance): void { throw new Error('Method not implemented.'); } + focusActiveInstance(): Promise { throw new Error('Method not implemented.'); } getInstanceFromResource(resource: URI | undefined): ITerminalInstance | undefined { throw new Error('Method not implemented.'); } focusFindWidget(): void { throw new Error('Method not implemented.'); } hideFindWidget(): void { throw new Error('Method not implemented.'); } - getFindState(): FindReplaceState { throw new Error('Method not implemented.'); } findNext(): void { throw new Error('Method not implemented.'); } findPrevious(): void { throw new Error('Method not implemented.'); } } @@ -1837,11 +1855,12 @@ export class TestTerminalGroupService implements ITerminalGroupService { hidePanel(): void { throw new Error('Method not implemented.'); } focusTabs(): void { throw new Error('Method not implemented.'); } showTabs(): void { throw new Error('Method not implemented.'); } + focusHover(): void { throw new Error('Method not implemented.'); } setActiveInstance(instance: ITerminalInstance): void { throw new Error('Method not implemented.'); } + focusActiveInstance(): Promise { throw new Error('Method not implemented.'); } getInstanceFromResource(resource: URI | undefined): ITerminalInstance | undefined { throw new Error('Method not implemented.'); } focusFindWidget(): void { throw new Error('Method not implemented.'); } hideFindWidget(): void { throw new Error('Method not implemented.'); } - getFindState(): FindReplaceState { throw new Error('Method not implemented.'); } findNext(): void { throw new Error('Method not implemented.'); } findPrevious(): void { throw new Error('Method not implemented.'); } updateVisibility(): void { throw new Error('Method not implemented.'); } @@ -1856,6 +1875,7 @@ export class TestTerminalProfileService implements ITerminalProfileService { getPlatformKey(): Promise { throw new Error('Method not implemented.'); } refreshAvailableProfiles(): void { throw new Error('Method not implemented.'); } getDefaultProfileName(): string | undefined { throw new Error('Method not implemented.'); } + getDefaultProfile(): ITerminalProfile | undefined { throw new Error('Method not implemented.'); } getContributedDefaultProfile(shellLaunchConfig: IShellLaunchConfig): Promise { throw new Error('Method not implemented.'); } registerContributedProfile(args: IRegisterContributedProfileArgs): Promise { throw new Error('Method not implemented.'); } getContributedProfileProvider(extensionIdentifier: string, id: string): ITerminalProfileProvider | undefined { throw new Error('Method not implemented.'); } @@ -1878,7 +1898,6 @@ export class TestTerminalProfileResolverService implements ITerminalProfileResol } export class TestQuickInputService implements IQuickInputService { - declare readonly _serviceBrand: undefined; readonly onShow = Event.None; @@ -1890,7 +1909,7 @@ export class TestQuickInputService implements IQuickInputService { pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: true }, token?: CancellationToken): Promise; pick(picks: Promise[]> | QuickPickInput[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): Promise; async pick(picks: Promise[]> | QuickPickInput[], options?: Omit, 'canPickMany'>, token?: CancellationToken): Promise { - if (isArray(picks)) { + if (Array.isArray(picks)) { return { label: 'selectedPick', description: 'pick description', value: 'selectedPick' }; } else { return undefined; @@ -1921,17 +1940,10 @@ export class TestRemoteAgentService implements IRemoteAgentService { declare readonly _serviceBrand: undefined; - socketFactory: ISocketFactory = { - connect() { } - }; - getConnection(): IRemoteAgentConnection | null { return null; } async getEnvironment(): Promise { return null; } async getRawEnvironment(): Promise { return null; } async getExtensionHostExitInfo(reconnectionToken: string): Promise { return null; } - async whenExtensionsReady(): Promise { } - scanExtensions(skipExtensions?: ExtensionIdentifier[]): Promise { throw new Error('Method not implemented.'); } - scanSingleExtension(extensionLocation: URI, isBuiltin: boolean): Promise { throw new Error('Method not implemented.'); } async getDiagnosticInfo(options: IDiagnosticInfoOptions): Promise { return undefined; } async updateTelemetryLevel(telemetryLevel: TelemetryLevel): Promise { } async logTelemetry(eventName: string, data?: ITelemetryData): Promise { } @@ -1939,6 +1951,13 @@ export class TestRemoteAgentService implements IRemoteAgentService { async getRoundTripTime(): Promise { return undefined; } } +export class TestRemoteExtensionsScannerService implements IRemoteExtensionsScannerService { + declare readonly _serviceBrand: undefined; + async whenExtensionsReady(): Promise { } + scanExtensions(): Promise { throw new Error('Method not implemented.'); } + scanSingleExtension(): Promise { throw new Error('Method not implemented.'); } +} + export class TestWorkbenchExtensionEnablementService implements IWorkbenchExtensionEnablementService { _serviceBrand: undefined; onEnablementChanged = Event.None; @@ -1960,11 +1979,16 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens onDidInstallExtensions = Event.None; onUninstallExtension = Event.None; onDidUninstallExtension = Event.None; - onDidChangeProfileExtensions = Event.None; + onDidUpdateExtensionMetadata = Event.None; + onProfileAwareInstallExtension = Event.None; + onProfileAwareDidInstallExtensions = Event.None; + onProfileAwareUninstallExtension = Event.None; + onProfileAwareDidUninstallExtension = Event.None; + onDidChangeProfile = Event.None; installVSIX(location: URI, manifest: Readonly, installOptions?: InstallVSIXOptions | undefined): Promise { throw new Error('Method not implemented.'); } - installWebExtension(location: URI): Promise { + installFromLocation(location: URI): Promise { throw new Error('Method not implemented.'); } installExtensions(extensions: IGalleryExtension[], installOptions?: InstallOptions | undefined): Promise { @@ -1993,19 +2017,22 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens uninstall(extension: ILocalExtension, options?: UninstallOptions | undefined): Promise { throw new Error('Method not implemented.'); } - async reinstallFromGallery(extension: ILocalExtension): Promise { + async reinstallFromGallery(extension: ILocalExtension): Promise { + throw new Error('Method not implemented.'); } async getInstalled(type?: ExtensionType | undefined): Promise { return []; } getExtensionsControlManifest(): Promise { throw new Error('Method not implemented.'); } - getMetadata(extension: ILocalExtension): Promise | undefined> { - throw new Error('Method not implemented.'); - } - async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { return local; } - async updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise { return local; } + async updateMetadata(local: ILocalExtension, metadata: Partial): Promise { return local; } registerParticipant(pariticipant: IExtensionManagementParticipant): void { } async getTargetPlatform(): Promise { return TargetPlatform.UNDEFINED; } + async cleanUp(): Promise { } + download(): Promise { + throw new Error('Method not implemented.'); + } + copyExtensions(): Promise { throw new Error('Not Supported'); } + installExtensionsFromProfile(): Promise { throw new Error('Not Supported'); } } export class TestUserDataProfileService implements IUserDataProfileService { @@ -2013,13 +2040,14 @@ export class TestUserDataProfileService implements IUserDataProfileService { readonly _serviceBrand: undefined; readonly onDidUpdateCurrentProfile = Event.None; readonly onDidChangeCurrentProfile = Event.None; - readonly currentProfile = toUserDataProfile('test', URI.file('tests').with({ scheme: 'vscode-tests' })); + readonly currentProfile = toUserDataProfile('test', 'test', URI.file('tests').with({ scheme: 'vscode-tests' }), URI.file('tests').with({ scheme: 'vscode-tests' })); async updateCurrentProfile(): Promise { } + getShortName(profile: IUserDataProfile): string { return profile.shortName ?? profile.name; } } export class TestWebExtensionsScannerService implements IWebExtensionsScannerService { _serviceBrand: undefined; - onDidChangeProfileExtensions = Event.None; + onDidChangeProfile = Event.None; async scanSystemExtensions(): Promise { return []; } async scanUserExtensions(): Promise { return []; } async scanExtensionsUnderDevelopment(): Promise { return []; } @@ -2038,7 +2066,7 @@ export class TestWebExtensionsScannerService implements IWebExtensionsScannerSer removeExtension(): Promise { throw new Error('Method not implemented.'); } - scanMetadata(extensionLocation: URI): Promise | undefined> { + updateMetadata(extension: IScannedExtension, metaData: Partial, profileLocation: URI): Promise { throw new Error('Method not implemented.'); } scanExtensionManifest(extensionLocation: URI): Promise | null> { diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 77504b5ba6..bb9f57adeb 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind, IStatusMessageChangeEvent, StatusMessageChangeType } from 'vs/workbench/common/notifications'; import { Action } from 'vs/base/common/actions'; -import { INotification, Severity, NotificationsFilter } from 'vs/platform/notification/common/notification'; +import { INotification, Severity, NotificationsFilter, NotificationPriority } from 'vs/platform/notification/common/notification'; import { createErrorWithActions } from 'vs/base/common/errorMessage'; import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -132,16 +132,16 @@ suite('Notifications', () => { // Filter const item8 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.SILENT)!; - assert.strictEqual(item8.silent, true); + assert.strictEqual(item8.priority, NotificationPriority.SILENT); const item9 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.OFF)!; - assert.strictEqual(item9.silent, false); + assert.strictEqual(item9.priority, NotificationPriority.DEFAULT); const item10 = NotificationViewItem.create({ severity: Severity.Error, message: 'Error Message' }, NotificationsFilter.ERROR)!; - assert.strictEqual(item10.silent, false); + assert.strictEqual(item10.priority, NotificationPriority.DEFAULT); const item11 = NotificationViewItem.create({ severity: Severity.Warning, message: 'Error Message' }, NotificationsFilter.ERROR)!; - assert.strictEqual(item11.silent, true); + assert.strictEqual(item11.priority, NotificationPriority.SILENT); }); test('Items - does not fire changed when message did not change (content, severity)', async () => { @@ -280,7 +280,7 @@ suite('Notifications', () => { service.info('hello there'); assert.strictEqual(addNotificationCount, 1); assert.strictEqual(notification.message, 'hello there'); - assert.strictEqual(notification.silent, false); + assert.strictEqual(notification.priority, NotificationPriority.DEFAULT); assert.strictEqual(notification.source, undefined); let notificationHandle = service.notify({ message: 'important message', severity: Severity.Warning }); @@ -297,10 +297,10 @@ suite('Notifications', () => { assert.strictEqual(removeNotificationCount, 1); assert.strictEqual(notification.message, 'important message'); - notificationHandle = service.notify({ silent: true, message: 'test', severity: Severity.Ignore }); + notificationHandle = service.notify({ priority: NotificationPriority.SILENT, message: 'test', severity: Severity.Ignore }); assert.strictEqual(addNotificationCount, 3); assert.strictEqual(notification.message, 'test'); - assert.strictEqual(notification.silent, true); + assert.strictEqual(notification.priority, NotificationPriority.SILENT); notificationHandle.close(); assert.strictEqual(removeNotificationCount, 2); }); diff --git a/src/vs/workbench/test/common/resources.test.ts b/src/vs/workbench/test/common/resources.test.ts new file mode 100644 index 0000000000..88e7bdb075 --- /dev/null +++ b/src/vs/workbench/test/common/resources.test.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { URI } from 'vs/base/common/uri'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; + +suite('ResourceGlobMatcher', () => { + + const SETTING = 'test.matcher'; + + let contextService: IWorkspaceContextService; + let configurationService: TestConfigurationService; + + setup(() => { + contextService = new TestContextService(); + configurationService = new TestConfigurationService({ + [SETTING]: { + '**/*.md': true, + '**/*.txt': false + } + }); + }); + + test('Basics', async () => { + const matcher = new ResourceGlobMatcher(() => configurationService.getValue(SETTING), e => e.affectsConfiguration(SETTING), contextService, configurationService); + + // Matching + assert.equal(matcher.matches(URI.file('/foo/bar')), false); + assert.equal(matcher.matches(URI.file('/foo/bar.md')), true); + assert.equal(matcher.matches(URI.file('/foo/bar.txt')), false); + + // Events + let eventCounter = 0; + matcher.onExpressionChange(() => eventCounter++); + + await configurationService.setUserConfiguration(SETTING, { '**/*.foo': true }); + configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: (key: string) => key === SETTING } as any); + assert.equal(eventCounter, 1); + + assert.equal(matcher.matches(URI.file('/foo/bar.md')), false); + assert.equal(matcher.matches(URI.file('/foo/bar.foo')), true); + + await configurationService.setUserConfiguration(SETTING, undefined); + configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: (key: string) => key === SETTING } as any); + assert.equal(eventCounter, 2); + + assert.equal(matcher.matches(URI.file('/foo/bar.md')), false); + assert.equal(matcher.matches(URI.file('/foo/bar.foo')), false); + + await configurationService.setUserConfiguration(SETTING, { + '**/*.md': true, + '**/*.txt': false, + 'C:/bar/**': true, + '/bar/**': true + }); + configurationService.onDidChangeConfigurationEmitter.fire({ affectsConfiguration: (key: string) => key === SETTING } as any); + + assert.equal(matcher.matches(URI.file('/bar/foo.1')), true); + assert.equal(matcher.matches(URI.file('C:/bar/foo.1')), true); + }); +}); diff --git a/src/vs/workbench/test/common/utils.ts b/src/vs/workbench/test/common/utils.ts index 0866c97b48..fee2e28e94 100644 --- a/src/vs/workbench/test/common/utils.ts +++ b/src/vs/workbench/test/common/utils.ts @@ -10,6 +10,8 @@ import { LanguagesRegistry } from 'vs/editor/common/services/languagesRegistry'; /** * This function is called before test running and also again at the end of test running * and can be used to add assertions. e.g. that registries are empty, etc. + * + * !! This is called directly by the testing framework. */ export function assertCleanState(): void { // If this test fails, it is a clear indication that diff --git a/src/vs/workbench/test/common/workbenchTestServices.ts b/src/vs/workbench/test/common/workbenchTestServices.ts index 458fd66461..8e9b26aa03 100644 --- a/src/vs/workbench/test/common/workbenchTestServices.ts +++ b/src/vs/workbench/test/common/workbenchTestServices.ts @@ -17,12 +17,24 @@ import { IWorkingCopy, IWorkingCopyBackup, WorkingCopyCapabilities } from 'vs/wo import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkingCopyFileService, IWorkingCopyFileOperationParticipant, WorkingCopyFileEvent, IDeleteOperation, ICopyOperation, IMoveOperation, IFileOperationUndoRedoInfo, ICreateFileOperation, ICreateOperation, IStoredFileWorkingCopySaveParticipant } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IFileStatWithMetadata } from 'vs/platform/files/common/files'; -import { ISaveOptions, IRevertOptions, SaveReason } from 'vs/workbench/common/editor'; +import { IBaseFileStat, IFileStatWithMetadata } from 'vs/platform/files/common/files'; +import { ISaveOptions, IRevertOptions, SaveReason, GroupIdentifier } from 'vs/workbench/common/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import product from 'vs/platform/product/common/product'; import { IActivity, IActivityService } from 'vs/workbench/services/activity/common/activity'; import { IStoredFileWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy'; +import { AbstractLoggerService, ILogger, LogLevel, NullLogger } from 'vs/platform/log/common/log'; +import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { AutoSaveMode, IAutoSaveConfiguration, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; + +export class TestLoggerService extends AbstractLoggerService { + constructor(logsHome?: URI) { + super(LogLevel.Info, logsHome ?? URI.file('tests').with({ scheme: 'vscode-tests' })); + } + protected doCreateLogger(): ILogger { return new NullLogger(); } +} export class TestTextResourcePropertiesService implements ITextResourcePropertiesService { @@ -127,11 +139,32 @@ export class TestContextService implements IWorkspaceContextService { export class TestStorageService extends InMemoryStorageService { - override emitWillSaveState(reason: WillSaveStateReason): void { + testEmitWillSaveState(reason: WillSaveStateReason): void { super.emitWillSaveState(reason); } } +export class TestHistoryService implements IHistoryService { + + declare readonly _serviceBrand: undefined; + + constructor(private root?: URI) { } + + async reopenLastClosedEditor(): Promise { } + async goForward(): Promise { } + async goBack(): Promise { } + async goPrevious(): Promise { } + async goLast(): Promise { } + removeFromHistory(_input: EditorInput | IResourceEditorInput): void { } + clear(): void { } + clearRecentlyOpened(): void { } + getHistory(): readonly (EditorInput | IResourceEditorInput)[] { return []; } + async openNextRecentlyUsedEditor(group?: GroupIdentifier): Promise { } + async openPreviouslyUsedEditor(group?: GroupIdentifier): Promise { } + getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } + getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } +} + export class TestWorkingCopy extends Disposable implements IWorkingCopy { private readonly _onDidChangeDirty = this._register(new Emitter()); @@ -170,6 +203,10 @@ export class TestWorkingCopy extends Disposable implements IWorkingCopy { return this.dirty; } + isModified(): boolean { + return this.isDirty(); + } + async save(options?: ISaveOptions, stat?: IFileStatWithMetadata): Promise { this._onDidSave.fire({ reason: options?.reason ?? SaveReason.EXPLICIT, stat: stat ?? createFileStat(this.resource), source: options?.source }); @@ -196,6 +233,7 @@ export function createFileStat(resource: URI, readonly = false): IFileStatWithMe isDirectory: false, isSymbolicLink: false, readonly, + locked: false, name: basename(resource), children: undefined }; @@ -258,3 +296,22 @@ export class TestActivityService implements IActivityService { dispose() { } } + +export const NullFilesConfigurationService = new class implements IFilesConfigurationService { + + _serviceBrand: undefined; + + readonly onAutoSaveConfigurationChange = Event.None; + readonly onReadonlyChange = Event.None; + readonly onFilesAssociationChange = Event.None; + + readonly isHotExitEnabled = false; + readonly hotExitConfiguration = undefined; + + getAutoSaveConfiguration(): IAutoSaveConfiguration { throw new Error('Method not implemented.'); } + getAutoSaveMode(): AutoSaveMode { throw new Error('Method not implemented.'); } + toggleAutoSave(): Promise { throw new Error('Method not implemented.'); } + isReadonly(resource: URI, stat?: IBaseFileStat | undefined): boolean { return false; } + async updateReadonly(resource: URI, readonly: boolean | 'toggle' | 'reset'): Promise { } + preventSaveConflicts(resource: URI, language?: string | undefined): boolean { throw new Error('Method not implemented.'); } +}; diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts similarity index 51% rename from src/vs/workbench/test/electron-browser/workbenchTestServices.ts rename to src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts index f94433c582..e50b08cb5a 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-sandbox/workbenchTestServices.ts @@ -3,182 +3,49 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestFileService, TestFileDialogService, TestPathService, TestEncodingOracle } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; +import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestEncodingOracle, TestEnvironmentService, TestFileDialogService, TestFilesConfigurationService, TestFileService, TestLifecycleService, TestTextFileService } from 'vs/workbench/test/browser/workbenchTestServices'; import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { NativeTextFileService, } from 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService'; -import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { FileOperationError, IFileService } from 'vs/platform/files/common/files'; -import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; -import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IModelService } from 'vs/editor/common/services/model'; -import { INativeWorkbenchEnvironmentService, NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; -import { IDialogService, IFileDialogService, INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { URI } from 'vs/base/common/uri'; -import { IReadTextFileOptions, ITextFileStreamContent, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions, IOpenedWindow, IColorScheme, INativeWindowConfiguration } from 'vs/platform/window/common/window'; -import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv'; -import { LogLevel, ILogService, NullLogService } from 'vs/platform/log/common/log'; -import { IPathService } from 'vs/workbench/services/path/common/pathService'; -import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ModelService } from 'vs/editor/common/services/modelService'; -import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; -import { NodeTestWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test'; -import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { TestContextService, TestProductService } from 'vs/workbench/test/common/workbenchTestServices'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { MouseInputEvent } from 'vs/base/parts/sandbox/common/electronTypes'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; -import { homedir, release, tmpdir, hostname } from 'os'; -import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { getUserDataPath } from 'vs/platform/environment/node/userDataPath'; -import product from 'vs/platform/product/common/product'; -import { IElevatedFileService } from 'vs/workbench/services/files/common/elevatedFileService'; -import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; +import { INativeHostService, IOSProperties, IOSStatistics } from 'vs/platform/native/common/native'; +import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { IFileDialogService, INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { IPartsSplash } from 'vs/platform/theme/common/themeService'; -import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IOpenedWindow, IOpenEmptyWindowOptions, IWindowOpenable, IOpenWindowOptions, IColorScheme } from 'vs/platform/window/common/window'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IEnvironmentService, INativeEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { AbstractNativeExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService'; +import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IModelService } from 'vs/editor/common/services/model'; +import { ModelService } from 'vs/editor/common/services/modelService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; +import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; +import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; +import { NativeTextFileService } from 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService'; +import { insert } from 'vs/base/common/arrays'; +import { Schemas } from 'vs/base/common/network'; import { FileService } from 'vs/platform/files/common/fileService'; -import { joinPath } from 'vs/base/common/resources'; -import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; -import { VSBuffer } from 'vs/base/common/buffer'; - -const args = parseArgs(process.argv, OPTIONS); - -const homeDir = homedir(); -const NULL_PROFILE = { - name: '', - id: '', - isDefault: false, - location: URI.file(homeDir), - settingsResource: joinPath(URI.file(homeDir), 'settings.json'), - globalStorageHome: joinPath(URI.file(homeDir), 'globalStorage'), - keybindingsResource: joinPath(URI.file(homeDir), 'keybindings.json'), - tasksResource: joinPath(URI.file(homeDir), 'tasks.json'), - snippetsHome: joinPath(URI.file(homeDir), 'snippets'), - extensionsResource: undefined -}; - -export const TestNativeWindowConfiguration: INativeWindowConfiguration = { - windowId: 0, - machineId: 'testMachineId', - logLevel: LogLevel.Error, - mainPid: 0, - appRoot: '', - userEnv: {}, - execPath: process.execPath, - perfMarks: [], - colorScheme: { dark: true, highContrast: false }, - os: { release: release(), hostname: hostname() }, - product, - homeDir: homeDir, - tmpDir: tmpdir(), - userDataDir: getUserDataPath(args), - profiles: { current: NULL_PROFILE, all: [NULL_PROFILE] }, - ...args -}; - -export const TestEnvironmentService = new NativeWorkbenchEnvironmentService(TestNativeWindowConfiguration, TestProductService); - -export class TestTextFileService extends NativeTextFileService { - private resolveTextContentError!: FileOperationError | null; - - constructor( - @IFileService fileService: IFileService, - @IUntitledTextEditorService untitledTextEditorService: IUntitledTextEditorService, - @ILifecycleService lifecycleService: ILifecycleService, - @IInstantiationService instantiationService: IInstantiationService, - @IModelService modelService: IModelService, - @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, - @IDialogService dialogService: IDialogService, - @IFileDialogService fileDialogService: IFileDialogService, - @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, - @IProductService productService: IProductService, - @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, - @ITextModelService textModelService: ITextModelService, - @ICodeEditorService codeEditorService: ICodeEditorService, - @IPathService pathService: IPathService, - @IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService, - @ILogService logService: ILogService, - @IUriIdentityService uriIdentityService: IUriIdentityService, - @ILanguageService languageService: ILanguageService, - @IElevatedFileService elevatedFileService: IElevatedFileService, - @IDecorationsService decorationsService: IDecorationsService - ) { - super( - fileService, - untitledTextEditorService, - lifecycleService, - instantiationService, - modelService, - environmentService, - dialogService, - fileDialogService, - textResourceConfigurationService, - filesConfigurationService, - textModelService, - codeEditorService, - pathService, - workingCopyFileService, - uriIdentityService, - languageService, - elevatedFileService, - logService, - decorationsService - ); - } - - setResolveTextContentErrorOnce(error: FileOperationError): void { - this.resolveTextContentError = error; - } - - override async readStream(resource: URI, options?: IReadTextFileOptions): Promise { - if (this.resolveTextContentError) { - const error = this.resolveTextContentError; - this.resolveTextContentError = null; - - throw error; - } - - const content = await this.fileService.readFileStream(resource, options); - return { - resource: content.resource, - name: content.name, - mtime: content.mtime, - ctime: content.ctime, - etag: content.etag, - encoding: 'utf8', - value: await createTextBufferFactoryFromStream(content.value), - size: 10, - readonly: false - }; - } -} - -export class TestNativeTextFileServiceWithEncodingOverrides extends NativeTextFileService { - - private _testEncoding: TestEncodingOracle | undefined; - override get encoding(): TestEncodingOracle { - if (!this._testEncoding) { - this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); - } - - return this._testEncoding; - } -} +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { NullLogService } from 'vs/platform/log/common/log'; +import { FileUserDataProvider } from 'vs/platform/userData/common/fileUserDataProvider'; +import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { NativeWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupService'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class TestSharedProcessService implements ISharedProcessService { @@ -192,7 +59,6 @@ export class TestSharedProcessService implements ISharedProcessService { } export class TestNativeHostService implements INativeHostService { - declare readonly _serviceBrand: undefined; readonly windowId = -1; @@ -226,7 +92,7 @@ export class TestNativeHostService implements INativeHostService { async maximizeWindow(): Promise { } async unmaximizeWindow(): Promise { } async minimizeWindow(): Promise { } - async updateTitleBarOverlay(options: { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise { } + async updateWindowControls(options: { height?: number; backgroundColor?: string; foregroundColor?: string }): Promise { } async setMinimumSize(width: number | undefined, height: number | undefined): Promise { } async saveWindowSplash(value: IPartsSplash): Promise { } async focusWindow(options?: { windowId?: number | undefined } | undefined): Promise { } @@ -245,6 +111,7 @@ export class TestNativeHostService implements INativeHostService { async getOSStatistics(): Promise { return Object.create(null); } async getOSVirtualMachineHint(): Promise { return 0; } async getOSColorScheme(): Promise { return { dark: true, highContrast: false }; } + async hasWSLFeatureInstalled(): Promise { return false; } async killProcess(): Promise { } async setDocumentEdited(edited: boolean): Promise { } async openExternal(url: string): Promise { return false; } @@ -267,7 +134,6 @@ export class TestNativeHostService implements INativeHostService { async exit(code: number): Promise { } async openDevTools(options?: Electron.OpenDevToolsOptions | undefined): Promise { } async toggleDevTools(): Promise { } - async toggleSharedProcessWindow(): Promise { } async resolveProxy(url: string): Promise { return undefined; } async findFreePort(startPort: number, giveUpAfter: number, timeout: number, stride?: number): Promise { return -1; } async readClipboardText(type?: 'selection' | 'clipboard' | undefined): Promise { return ''; } @@ -278,24 +144,43 @@ export class TestNativeHostService implements INativeHostService { async writeClipboardBuffer(format: string, buffer: VSBuffer, type?: 'selection' | 'clipboard' | undefined): Promise { } async readClipboardBuffer(format: string): Promise { return VSBuffer.wrap(Uint8Array.from([])); } async hasClipboard(format: string, type?: 'selection' | 'clipboard' | undefined): Promise { return false; } - async sendInputEvent(event: MouseInputEvent): Promise { } + async sendInputEvent(event: any): Promise { } async windowsGetStringRegKey(hive: 'HKEY_CURRENT_USER' | 'HKEY_LOCAL_MACHINE' | 'HKEY_CLASSES_ROOT' | 'HKEY_USERS' | 'HKEY_CURRENT_CONFIG', path: string, name: string): Promise { return undefined; } + async profileRenderer(): Promise { throw new Error(); } } -export function workbenchInstantiationService(disposables = new DisposableStore()): ITestInstantiationService { +export class TestExtensionTipsService extends AbstractNativeExtensionTipsService { + + constructor( + @INativeEnvironmentService environmentService: INativeEnvironmentService, + @ITelemetryService telemetryService: ITelemetryService, + @IExtensionManagementService extensionManagementService: IExtensionManagementService, + @IStorageService storageService: IStorageService, + @INativeHostService nativeHostService: INativeHostService, + @IExtensionRecommendationNotificationService extensionRecommendationNotificationService: IExtensionRecommendationNotificationService, + @IFileService fileService: IFileService, + @IProductService productService: IProductService, + ) { + super(environmentService.userHome, nativeHostService, telemetryService, extensionManagementService, storageService, extensionRecommendationNotificationService, fileService, productService); + } +} + +export function workbenchInstantiationService(overrides?: { + environmentService?: (instantiationService: IInstantiationService) => IEnvironmentService; + fileService?: (instantiationService: IInstantiationService) => IFileService; + configurationService?: (instantiationService: IInstantiationService) => TestConfigurationService; + textFileService?: (instantiationService: IInstantiationService) => ITextFileService; + pathService?: (instantiationService: IInstantiationService) => IPathService; + editorService?: (instantiationService: IInstantiationService) => IEditorService; + contextKeyService?: (instantiationService: IInstantiationService) => IContextKeyService; + textEditorService?: (instantiationService: IInstantiationService) => ITextEditorService; +}, disposables = new DisposableStore()): ITestInstantiationService { const instantiationService = browserWorkbenchInstantiationService({ - textFileService: insta => insta.createInstance(TestTextFileService), - pathService: insta => insta.createInstance(TestNativePathService) + workingCopyBackupService: (instantiationService: IInstantiationService) => new TestNativeWorkingCopyBackupService(), + ...overrides }, disposables); instantiationService.stub(INativeHostService, new TestNativeHostService()); - instantiationService.stub(IEnvironmentService, TestEnvironmentService); - instantiationService.stub(INativeEnvironmentService, TestEnvironmentService); - instantiationService.stub(IWorkbenchEnvironmentService, TestEnvironmentService); - instantiationService.stub(INativeWorkbenchEnvironmentService, TestEnvironmentService); - const fileService = new FileService(new NullLogService()); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(TestEnvironmentService, fileService, new UriIdentityService(fileService), new NullLogService())); - instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); return instantiationService; } @@ -310,16 +195,102 @@ export class TestServiceAccessor { @IFileService public fileService: TestFileService, @INativeHostService public nativeHostService: TestNativeHostService, @IFileDialogService public fileDialogService: TestFileDialogService, - @IWorkingCopyBackupService public workingCopyBackupService: NodeTestWorkingCopyBackupService, + @IWorkingCopyBackupService public workingCopyBackupService: TestNativeWorkingCopyBackupService, @IWorkingCopyService public workingCopyService: IWorkingCopyService, @IEditorService public editorService: IEditorService ) { } } -export class TestNativePathService extends TestPathService { +export class TestNativeTextFileServiceWithEncodingOverrides extends NativeTextFileService { - constructor() { - super(URI.file(homedir())); + private _testEncoding: TestEncodingOracle | undefined; + override get encoding(): TestEncodingOracle { + if (!this._testEncoding) { + this._testEncoding = this._register(this.instantiationService.createInstance(TestEncodingOracle)); + } + + return this._testEncoding; + } +} + +export class TestNativeWorkingCopyBackupService extends NativeWorkingCopyBackupService { + + private backupResourceJoiners: Function[]; + private discardBackupJoiners: Function[]; + discardedBackups: IWorkingCopyIdentifier[]; + discardedAllBackups: boolean; + private pendingBackupsArr: Promise[]; + + constructor() { + const environmentService = TestEnvironmentService; + const logService = new NullLogService(); + const fileService = new FileService(logService); + const lifecycleService = new TestLifecycleService(); + super(environmentService as any, fileService, logService, lifecycleService); + + const inMemoryFileSystemProvider = new InMemoryFileSystemProvider(); + fileService.registerProvider(Schemas.inMemory, inMemoryFileSystemProvider); + fileService.registerProvider(Schemas.vscodeUserData, new FileUserDataProvider(Schemas.file, inMemoryFileSystemProvider, Schemas.vscodeUserData, logService)); + + this.backupResourceJoiners = []; + this.discardBackupJoiners = []; + this.discardedBackups = []; + this.pendingBackupsArr = []; + this.discardedAllBackups = false; + } + + testGetFileService(): IFileService { + return this.fileService; + } + + async waitForAllBackups(): Promise { + await Promise.all(this.pendingBackupsArr); + } + + joinBackupResource(): Promise { + return new Promise(resolve => this.backupResourceJoiners.push(resolve)); + } + + override async backup(identifier: IWorkingCopyIdentifier, content?: VSBufferReadableStream | VSBufferReadable, versionId?: number, meta?: any, token?: CancellationToken): Promise { + const p = super.backup(identifier, content, versionId, meta, token); + const removeFromPendingBackups = insert(this.pendingBackupsArr, p.then(undefined, undefined)); + + try { + await p; + } finally { + removeFromPendingBackups(); + } + + while (this.backupResourceJoiners.length) { + this.backupResourceJoiners.pop()!(); + } + } + + joinDiscardBackup(): Promise { + return new Promise(resolve => this.discardBackupJoiners.push(resolve)); + } + + override async discardBackup(identifier: IWorkingCopyIdentifier): Promise { + await super.discardBackup(identifier); + this.discardedBackups.push(identifier); + + while (this.discardBackupJoiners.length) { + this.discardBackupJoiners.pop()!(); + } + } + + override async discardBackups(filter?: { except: IWorkingCopyIdentifier[] }): Promise { + this.discardedAllBackups = true; + + return super.discardBackups(filter); + } + + async getBackupContents(identifier: IWorkingCopyIdentifier): Promise { + const backupResource = this.toBackupResource(identifier); + + const fileContents = await this.fileService.readFile(backupResource); + + return fileContents.value.toString(); } } diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 9400a89e24..470667ee25 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -55,6 +55,8 @@ import 'vs/workbench/browser/parts/views/viewsService'; import 'vs/platform/actions/common/actions.contribution'; import 'vs/platform/undoRedo/common/undoRedoService'; +import 'vs/workbench/services/workspaces/common/editSessionIdentityService'; +import 'vs/workbench/services/workspaces/common/canonicalUriService'; import 'vs/workbench/services/extensions/browser/extensionUrlHandler'; import 'vs/workbench/services/keybinding/common/keybindingEditing'; import 'vs/workbench/services/decorations/browser/decorationsService'; @@ -84,9 +86,12 @@ import 'vs/workbench/services/extensionRecommendations/common/extensionIgnoredRe import 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig'; import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/userDataSync/common/userDataSyncUtil'; -import 'vs/workbench/services/userDataProfile/common/userDataProfileImportExportService'; +import 'vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService'; import 'vs/workbench/services/userDataProfile/browser/userDataProfileManagement'; +import 'vs/workbench/services/userDataProfile/common/remoteUserDataProfiles'; import 'vs/workbench/services/remote/common/remoteExplorerService'; +import 'vs/workbench/services/remote/common/remoteExtensionsScanner'; +import 'vs/workbench/services/terminal/common/embedderTerminalService'; import 'vs/workbench/services/workingCopy/common/workingCopyService'; import 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; @@ -100,8 +105,13 @@ import 'vs/workbench/services/assignment/common/assignmentService'; import 'vs/workbench/services/outline/browser/outlineService'; import 'vs/workbench/services/languageDetection/browser/languageDetectionWorkerServiceImpl'; import 'vs/editor/common/services/languageFeaturesService'; +import 'vs/editor/common/services/semanticTokensStylingService'; +import 'vs/editor/common/services/treeViewsDndService'; +import 'vs/workbench/services/textMate/browser/textMateTokenizationFeature.contribution'; +import 'vs/workbench/services/userActivity/common/userActivityService'; +import 'vs/workbench/services/userActivity/browser/userActivityBrowser'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -126,23 +136,21 @@ import { IgnoredExtensionsManagementService, IIgnoredExtensionsManagementService import { ExtensionStorageService, IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; import { IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; -import { IExtensionsProfileScannerService, ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; -registerSingleton(IUserDataSyncLogService, UserDataSyncLogService); -registerSingleton(IIgnoredExtensionsManagementService, IgnoredExtensionsManagementService); -registerSingleton(IGlobalExtensionEnablementService, GlobalExtensionEnablementService); -registerSingleton(IExtensionStorageService, ExtensionStorageService); -registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); -registerSingleton(IContextViewService, ContextViewService, true); -registerSingleton(IListService, ListService, true); -registerSingleton(IEditorWorkerService, EditorWorkerService); -registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); -registerSingleton(IMarkerService, MarkerService, true); -registerSingleton(IContextKeyService, ContextKeyService); -registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); -registerSingleton(IDownloadService, DownloadService, true); -registerSingleton(IOpenerService, OpenerService, true); -registerSingleton(IExtensionsProfileScannerService, ExtensionsProfileScannerService); +registerSingleton(IUserDataSyncLogService, UserDataSyncLogService, InstantiationType.Delayed); +registerSingleton(IIgnoredExtensionsManagementService, IgnoredExtensionsManagementService, InstantiationType.Delayed); +registerSingleton(IGlobalExtensionEnablementService, GlobalExtensionEnablementService, InstantiationType.Delayed); +registerSingleton(IExtensionStorageService, ExtensionStorageService, InstantiationType.Delayed); +registerSingleton(IExtensionGalleryService, ExtensionGalleryService, InstantiationType.Delayed); +registerSingleton(IContextViewService, ContextViewService, InstantiationType.Delayed); +registerSingleton(IListService, ListService, InstantiationType.Delayed); +registerSingleton(IEditorWorkerService, EditorWorkerService, InstantiationType.Eager /* registers link detection and word based suggestions for any document */); +registerSingleton(IMarkerDecorationsService, MarkerDecorationsService, InstantiationType.Delayed); +registerSingleton(IMarkerService, MarkerService, InstantiationType.Delayed); +registerSingleton(IContextKeyService, ContextKeyService, InstantiationType.Delayed); +registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService, InstantiationType.Delayed); +registerSingleton(IDownloadService, DownloadService, InstantiationType.Delayed); +registerSingleton(IOpenerService, OpenerService, InstantiationType.Delayed); //#endregion @@ -224,50 +232,51 @@ import { IErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/com import { ErrorDiagnosticsService } from 'sql/workbench/services/diagnostics/browser/errorDiagnosticsService'; import { IComponentContextService, ComponentContextService } from 'sql/workbench/services/componentContext/browser/componentContextService'; -registerSingleton(IDashboardService, DashboardService); -registerSingleton(IDashboardViewService, DashboardViewService); -registerSingleton(IModelViewService, ModelViewService); -registerSingleton(IAngularEventingService, AngularEventingService); -registerSingleton(INewDashboardTabDialogService, NewDashboardTabDialogService); -registerSingleton(IAccountManagementService, AccountManagementService); -registerSingleton(ISerializationService, SerializationService); -registerSingleton(IEditorDescriptorService, EditorDescriptorService); -registerSingleton(ITaskService, TaskService); -registerSingleton(IMetadataService, MetadataService); -registerSingleton(IAdminService, AdminService); -registerSingleton(IJobManagementService, JobManagementService); -registerSingleton(IBackupService, BackupService); -registerSingleton(IAzureBlobService, AzureBlobService); -registerSingleton(IBackupUiService, BackupUiService); -registerSingleton(IScriptingService, ScriptingService); -registerSingleton(IRestoreService, RestoreService); -registerSingleton(IRestoreDialogController, RestoreDialogController); -registerSingleton(IFileBrowserService, FileBrowserService); -registerSingleton(IFileBrowserDialogController, FileBrowserDialogController); -registerSingleton(IBackupRestoreUrlBrowserDialogService, BackupRestoreUrlBrowserDialogService); -registerSingleton(IInsightsDialogService, InsightsDialogService); -registerSingleton(INotebookService, NotebookService); -registerSingleton(IAccountPickerService, AccountPickerService); -registerSingleton(IProfilerService, ProfilerService); -registerSingleton(IConnectionManagementService, ConnectionManagementService as any); -registerSingleton(ICapabilitiesService, CapabilitiesService); -registerSingleton(IErrorMessageService, ErrorMessageService); -registerSingleton(IConnectionDialogService, ConnectionDialogService); -registerSingleton(IServerGroupController, ServerGroupController); -registerSingleton(sqlICredentialsService, CredentialsService); -registerSingleton(IResourceProviderService, ResourceProviderService); -registerSingleton(IQueryManagementService, QueryManagementService); -registerSingleton(IQueryModelService, QueryModelService); -registerSingleton(IQueryEditorService, QueryEditorService); -registerSingleton(IAdsTelemetryService, AdsTelemetryService); -registerSingleton(IObjectExplorerService, ObjectExplorerService); -registerSingleton(IOEShimService, OEShimService); -registerSingleton(IAssessmentService, AssessmentService); -registerSingleton(IDataGridProviderService, DataGridProviderService); -registerSingleton(ITableDesignerService, TableDesignerService); -registerSingleton(IExecutionPlanService, ExecutionPlanService); -registerSingleton(IErrorDiagnosticsService, ErrorDiagnosticsService); -registerSingleton(IComponentContextService, ComponentContextService); +// {{SQL CARBON TODO}} - delayed or eager +registerSingleton(IDashboardService, DashboardService, InstantiationType.Eager); +registerSingleton(IDashboardViewService, DashboardViewService, InstantiationType.Eager); +registerSingleton(IModelViewService, ModelViewService, InstantiationType.Eager); +registerSingleton(IAngularEventingService, AngularEventingService, InstantiationType.Eager); +registerSingleton(INewDashboardTabDialogService, NewDashboardTabDialogService, InstantiationType.Eager); +registerSingleton(IAccountManagementService, AccountManagementService, InstantiationType.Eager); +registerSingleton(ISerializationService, SerializationService, InstantiationType.Eager); +registerSingleton(IEditorDescriptorService, EditorDescriptorService, InstantiationType.Eager); +registerSingleton(ITaskService, TaskService, InstantiationType.Eager); +registerSingleton(IMetadataService, MetadataService, InstantiationType.Eager); +registerSingleton(IAdminService, AdminService, InstantiationType.Eager); +registerSingleton(IJobManagementService, JobManagementService, InstantiationType.Eager); +registerSingleton(IBackupService, BackupService, InstantiationType.Eager); +registerSingleton(IAzureBlobService, AzureBlobService, InstantiationType.Eager); +registerSingleton(IBackupUiService, BackupUiService, InstantiationType.Eager); +registerSingleton(IScriptingService, ScriptingService, InstantiationType.Eager); +registerSingleton(IRestoreService, RestoreService, InstantiationType.Eager); +registerSingleton(IRestoreDialogController, RestoreDialogController, InstantiationType.Eager); +registerSingleton(IFileBrowserService, FileBrowserService, InstantiationType.Eager); +registerSingleton(IFileBrowserDialogController, FileBrowserDialogController, InstantiationType.Eager); +registerSingleton(IBackupRestoreUrlBrowserDialogService, BackupRestoreUrlBrowserDialogService, InstantiationType.Eager); +registerSingleton(IInsightsDialogService, InsightsDialogService, InstantiationType.Eager); +registerSingleton(INotebookService, NotebookService, InstantiationType.Eager); +registerSingleton(IAccountPickerService, AccountPickerService, InstantiationType.Eager); +registerSingleton(IProfilerService, ProfilerService, InstantiationType.Eager); +registerSingleton(IConnectionManagementService, ConnectionManagementService, InstantiationType.Eager); +registerSingleton(ICapabilitiesService, CapabilitiesService, InstantiationType.Eager); +registerSingleton(IErrorMessageService, ErrorMessageService, InstantiationType.Eager); +registerSingleton(IConnectionDialogService, ConnectionDialogService, InstantiationType.Eager); +registerSingleton(IServerGroupController, ServerGroupController, InstantiationType.Eager); +registerSingleton(sqlICredentialsService, CredentialsService, InstantiationType.Eager); +registerSingleton(IResourceProviderService, ResourceProviderService, InstantiationType.Eager); +registerSingleton(IQueryManagementService, QueryManagementService, InstantiationType.Eager); +registerSingleton(IQueryModelService, QueryModelService, InstantiationType.Eager); +registerSingleton(IQueryEditorService, QueryEditorService, InstantiationType.Eager); +registerSingleton(IAdsTelemetryService, AdsTelemetryService, InstantiationType.Eager); +registerSingleton(IObjectExplorerService, ObjectExplorerService, InstantiationType.Eager); +registerSingleton(IOEShimService, OEShimService, InstantiationType.Eager); +registerSingleton(IAssessmentService, AssessmentService, InstantiationType.Eager); +registerSingleton(IDataGridProviderService, DataGridProviderService, InstantiationType.Eager); +registerSingleton(ITableDesignerService, TableDesignerService, InstantiationType.Eager); +registerSingleton(IExecutionPlanService, ExecutionPlanService, InstantiationType.Eager); +registerSingleton(IErrorDiagnosticsService, ErrorDiagnosticsService, InstantiationType.Eager); +registerSingleton(IComponentContextService, ComponentContextService, InstantiationType.Eager); //#endregion @@ -290,6 +299,9 @@ import 'vs/workbench/contrib/contextmenu/browser/contextmenu.contribution'; // Notebook import 'vs/workbench/contrib/notebook/browser/notebook.contribution'; +import 'vs/workbench/contrib/chat/browser/chat.contribution'; +import 'vs/workbench/contrib/interactiveEditor/browser/interactiveEditor.contribution'; + // Interactive import 'vs/workbench/contrib/interactive/browser/interactive.contribution'; @@ -339,6 +351,9 @@ import 'vs/workbench/contrib/markers/browser/markers.contribution'; // Merge Editor import 'vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution'; +// Commands +import 'vs/workbench/contrib/commands/common/commands.contribution'; + // Comments import 'vs/workbench/contrib/comments/browser/comments.contribution'; @@ -359,15 +374,15 @@ import 'vs/workbench/contrib/extensions/browser/extensions.contribution'; import 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; // Output View +import 'vs/workbench/contrib/output/common/outputChannelModelService'; import 'vs/workbench/contrib/output/browser/output.contribution'; import 'vs/workbench/contrib/output/browser/outputView'; // Terminal -import 'vs/workbench/contrib/terminal/common/environmentVariable.contribution'; -import 'vs/workbench/contrib/terminal/common/terminalExtensionPoints.contribution'; +import 'vs/workbench/contrib/terminal/terminal.all'; + +// External terminal import 'vs/workbench/contrib/externalTerminal/browser/externalTerminal.contribution'; -import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; -import 'vs/workbench/contrib/terminal/browser/terminalView'; // Relauncher import 'vs/workbench/contrib/relauncher/browser/relauncher.contribution'; @@ -395,6 +410,12 @@ import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; // Formatter Help import 'vs/workbench/contrib/format/browser/format.contribution'; +// Folding +import 'vs/workbench/contrib/folding/browser/folding.contribution'; + +// Limit Indicator +import 'vs/workbench/contrib/limitIndicator/browser/limitIndicator.contribution'; + // Inlay Hint Accessibility import 'vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty'; @@ -404,16 +425,12 @@ import 'vs/workbench/contrib/themes/browser/themes.contribution'; // Update import 'vs/workbench/contrib/update/browser/update.contribution'; -// Watermark -import 'vs/workbench/contrib/watermark/browser/watermark'; - // Surveys import 'vs/workbench/contrib/surveys/browser/nps.contribution'; import 'vs/workbench/contrib/surveys/browser/ces.contribution'; import 'vs/workbench/contrib/surveys/browser/languageSurveys.contribution'; // Welcome -import 'vs/workbench/contrib/welcomeOverlay/browser/welcomeOverlay'; import 'sql/workbench/contrib/welcome/page/browser/welcomePage.contribution'; // {{SQL CARBON EDIT}} - add welcome page contribution // import 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution'; // {{SQL CARBON EDIT}} - remove vscode getting started import 'vs/workbench/contrib/welcomeWalkthrough/browser/walkThrough.contribution'; @@ -469,9 +486,8 @@ import 'vs/workbench/contrib/workspaces/browser/workspaces.contribution'; // List import 'vs/workbench/contrib/list/browser/list.contribution'; -// {{SQL CARBON EDIT}} - disable audio cues // Audio Cues -// import 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; +import 'vs/workbench/contrib/audioCues/browser/audioCues.contribution'; //#endregion @@ -584,4 +600,10 @@ import 'vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExten // Bracket Pair Colorizer 2 Telemetry import 'vs/workbench/contrib/bracketPairColorizer2Telemetry/browser/bracketPairColorizer2Telemetry.contribution'; +// Accessibility +import 'vs/workbench/contrib/accessibility/browser/accessibility.contribution'; + +// Share +import 'vs/workbench/contrib/share/browser/share.contribution'; + //#endregion diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 0b3c53915b..dc9a35dd51 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -37,8 +37,8 @@ import 'vs/workbench/electron-sandbox/parts/dialogs/dialog.contribution'; import 'vs/workbench/services/textfile/electron-sandbox/nativeTextFileService'; import 'vs/workbench/services/dialogs/electron-sandbox/fileDialogService'; import 'vs/workbench/services/workspaces/electron-sandbox/workspacesService'; -import 'vs/workbench/services/textMate/browser/nativeTextMateService'; import 'vs/workbench/services/menubar/electron-sandbox/menubarService'; +import 'vs/workbench/services/issue/electron-sandbox/issueMainService'; import 'vs/workbench/services/issue/electron-sandbox/issueService'; import 'vs/workbench/services/update/electron-sandbox/updateService'; import 'vs/workbench/services/url/electron-sandbox/urlService'; @@ -46,12 +46,12 @@ import 'vs/workbench/services/lifecycle/electron-sandbox/lifecycleService'; import 'vs/workbench/services/title/electron-sandbox/titleService'; import 'vs/workbench/services/host/electron-sandbox/nativeHostService'; import 'vs/workbench/services/request/electron-sandbox/requestService'; -import 'vs/workbench/services/extensionResourceLoader/electron-sandbox/extensionResourceLoaderService'; import 'vs/workbench/services/clipboard/electron-sandbox/clipboardService'; import 'vs/workbench/services/contextmenu/electron-sandbox/contextmenuService'; import 'vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService'; import 'vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService'; import 'vs/workbench/services/accessibility/electron-sandbox/accessibilityService'; +import 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout'; import 'vs/workbench/services/path/electron-sandbox/pathService'; import 'vs/workbench/services/themes/electron-sandbox/nativeHostColorSchemeService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementService'; @@ -61,7 +61,9 @@ import 'vs/workbench/services/encryption/electron-sandbox/encryptionService'; import 'vs/workbench/services/localization/electron-sandbox/languagePackService'; import 'vs/workbench/services/telemetry/electron-sandbox/telemetryService'; import 'vs/workbench/services/extensions/electron-sandbox/extensionHostStarter'; -import 'vs/platform/extensionManagement/electron-sandbox/extensionsScannerService'; +import 'vs/platform/extensionResourceLoader/common/extensionResourceLoaderService'; +import 'vs/workbench/services/localization/electron-sandbox/localeService'; +import 'vs/workbench/services/extensions/electron-sandbox/extensionsScannerService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService'; import 'vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService'; import 'vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService'; @@ -79,15 +81,23 @@ import 'vs/workbench/services/tunnel/electron-sandbox/tunnelService'; import 'vs/platform/diagnostics/electron-sandbox/diagnosticsService'; import 'vs/platform/profiling/electron-sandbox/profilingService'; import 'vs/platform/telemetry/electron-sandbox/customEndpointTelemetryService'; +import 'vs/platform/remoteTunnel/electron-sandbox/remoteTunnelService'; import 'vs/workbench/services/files/electron-sandbox/elevatedFileService'; import 'vs/workbench/services/search/electron-sandbox/searchService'; import 'vs/workbench/services/workingCopy/electron-sandbox/workingCopyHistoryService'; import 'vs/workbench/services/userDataSync/browser/userDataSyncEnablementService'; -import 'vs/workbench/services/extensions/electron-sandbox/sandboxExtensionService'; +import 'vs/workbench/services/extensions/electron-sandbox/nativeExtensionService'; +import 'vs/platform/userDataProfile/electron-sandbox/userDataProfileStorageService'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; +import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; +import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; + +registerSingleton(IUserDataInitializationService, new SyncDescriptor(UserDataInitializationService, [[]], true)); +registerSingleton(IExtensionsProfileScannerService, ExtensionsProfileScannerService, InstantiationType.Delayed); -registerSingleton(IUserDataInitializationService, UserDataInitializationService); //#endregion @@ -101,7 +111,6 @@ import 'vs/workbench/contrib/logs/electron-sandbox/logs.contribution'; import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution'; // Explorer -import 'vs/workbench/contrib/files/electron-sandbox/files.contribution'; import 'vs/workbench/contrib/files/electron-sandbox/fileActions.contribution'; // CodeEditor Contributions @@ -131,9 +140,6 @@ import 'vs/workbench/contrib/themes/browser/themes.test.contribution'; // User Data Sync import 'vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution'; -// Output -import 'vs/workbench/contrib/output/electron-sandbox/outputChannelModelService'; - // Tags import 'vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService'; import 'vs/workbench/contrib/tags/electron-sandbox/tags.contribution'; @@ -156,9 +162,17 @@ import 'vs/workbench/contrib/splash/electron-sandbox/splash.contribution'; // Local History import 'vs/workbench/contrib/localHistory/electron-sandbox/localHistory.contribution'; +// Merge Editor +import 'vs/workbench/contrib/mergeEditor/electron-sandbox/mergeEditor.contribution'; + +// Remote Tunnel +import 'vs/workbench/contrib/remoteTunnel/electron-sandbox/remoteTunnel.contribution'; + //#endregion + +export { main } from 'vs/workbench/electron-sandbox/desktop.main'; + // {{SQL CARBON EDIT}} - SQL-specific services -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ISqlOAuthService } from 'sql/platform/oAuth/common/sqlOAuthService'; import { SqlOAuthService } from 'sql/platform/oAuth/electron-browser/sqlOAuthServiceImpl'; import { IClipboardService as sqlIClipboardService } from 'sql/platform/clipboard/common/clipboardService'; @@ -168,15 +182,14 @@ import { AzureBlobService } from 'sql/workbench/services/azureBlob/browser/azure import { IAzureAccountService } from 'sql/platform/azureAccount/common/azureAccountService'; import { AzureAccountService } from 'sql/workbench/services/azureAccount/browser/azureAccountService'; -registerSingleton(ISqlOAuthService, SqlOAuthService); -registerSingleton(sqlIClipboardService, sqlClipboardService); -registerSingleton(IAzureBlobService, AzureBlobService); -registerSingleton(IAzureAccountService, AzureAccountService); +registerSingleton(ISqlOAuthService, SqlOAuthService, InstantiationType.Delayed); +registerSingleton(sqlIClipboardService, sqlClipboardService, InstantiationType.Delayed); +registerSingleton(IAzureBlobService, AzureBlobService, InstantiationType.Delayed); +registerSingleton(IAzureAccountService, AzureAccountService, InstantiationType.Delayed); // {{SQL CARBON EDIT}} - End // {{SQL CARBON EDIT}} // getting started -import 'sql/workbench/update/electron-browser/gettingStarted.contribution'; // CLI import 'sql/workbench/contrib/commandLine/electron-browser/commandLine.contribution'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index ad8fa42bdd..88dd690eac 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -35,7 +35,6 @@ import 'vs/workbench/browser/web.main'; //#region --- workbench services import 'vs/workbench/services/integrity/browser/integrityService'; -import 'vs/workbench/services/textMate/browser/browserTextMateService'; import 'vs/workbench/services/search/browser/searchService'; import 'vs/workbench/services/textfile/browser/browserTextFileService'; import 'vs/workbench/services/keybinding/browser/keyboardLayoutService'; @@ -53,7 +52,7 @@ import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/services/clipboard/browser/clipboardService'; -import 'vs/workbench/services/extensionResourceLoader/browser/extensionResourceLoaderService'; +import 'vs/workbench/services/localization/browser/localeService'; import 'vs/workbench/services/path/browser/pathService'; import 'vs/workbench/services/themes/browser/browserHostColorSchemeService'; import 'vs/workbench/services/encryption/browser/encryptionService'; @@ -62,9 +61,11 @@ import 'vs/workbench/services/tunnel/browser/tunnelService'; import 'vs/workbench/services/files/browser/elevatedFileService'; import 'vs/workbench/services/workingCopy/browser/workingCopyHistoryService'; import 'vs/workbench/services/userDataSync/browser/webUserDataSyncEnablementService'; +import 'vs/workbench/services/userDataProfile/browser/userDataProfileStorageService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import 'vs/platform/extensionResourceLoader/browser/extensionResourceLoaderService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; @@ -72,10 +73,9 @@ import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/ex import { ExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionTipsService'; import { IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; -import { ILoggerService, LogLevel } from 'vs/platform/log/common/log'; -import { FileLoggerService } from 'vs/platform/log/common/fileLog'; +import { LogLevel } from 'vs/platform/log/common/log'; import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines'; -import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSync'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataAutoSyncService, IUserDataSyncBackupStoreService, IUserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; @@ -91,22 +91,22 @@ import { IDiagnosticsService, NullDiagnosticsService } from 'vs/platform/diagnos import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; import { WebLanguagePacksService } from 'vs/platform/languagePacks/browser/languagePacks'; -registerSingleton(IWorkbenchExtensionManagementService, ExtensionManagementService); -registerSingleton(IAccessibilityService, AccessibilityService, true); -registerSingleton(IContextMenuService, ContextMenuService); -registerSingleton(ILoggerService, FileLoggerService); -registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService); -registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService); -registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService); -registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService); -registerSingleton(IUserDataSyncService, UserDataSyncService); -registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService); -registerSingleton(ITitleService, TitlebarPart); -registerSingleton(IExtensionTipsService, ExtensionTipsService); -registerSingleton(ITimerService, TimerService); -registerSingleton(ICustomEndpointTelemetryService, NullEndpointTelemetryService, true); -registerSingleton(IDiagnosticsService, NullDiagnosticsService, true); -registerSingleton(ILanguagePackService, WebLanguagePacksService, true); +registerSingleton(IWorkbenchExtensionManagementService, ExtensionManagementService, InstantiationType.Delayed); +registerSingleton(IAccessibilityService, AccessibilityService, InstantiationType.Delayed); +registerSingleton(IContextMenuService, ContextMenuService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncBackupStoreService, UserDataSyncBackupStoreService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncService, UserDataSyncService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncResourceProviderService, UserDataSyncResourceProviderService, InstantiationType.Delayed); +registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService, InstantiationType.Eager /* Eager to start auto sync */); +registerSingleton(ITitleService, TitlebarPart, InstantiationType.Eager); +registerSingleton(IExtensionTipsService, ExtensionTipsService, InstantiationType.Delayed); +registerSingleton(ITimerService, TimerService, InstantiationType.Delayed); +registerSingleton(ICustomEndpointTelemetryService, NullEndpointTelemetryService, InstantiationType.Delayed); +registerSingleton(IDiagnosticsService, NullDiagnosticsService, InstantiationType.Delayed); +registerSingleton(ILanguagePackService, WebLanguagePacksService, InstantiationType.Delayed); //#endregion @@ -115,21 +115,15 @@ registerSingleton(ILanguagePackService, WebLanguagePacksService, true); import { IClipboardService as sqlIClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { BrowserClipboardService as sqlClipboardService } from 'sql/platform/clipboard/browser/clipboardService'; -registerSingleton(sqlIClipboardService, sqlClipboardService); +registerSingleton(sqlIClipboardService, sqlClipboardService, InstantiationType.Delayed); //#endregion //#region --- workbench contributions -// Output -import 'vs/workbench/contrib/output/common/outputChannelModelService'; - // Logs import 'vs/workbench/contrib/logs/browser/logs.contribution'; -// Explorer -import 'vs/workbench/contrib/files/browser/files.web.contribution'; - // Localization import 'vs/workbench/contrib/localization/browser/localization.contribution'; @@ -145,6 +139,9 @@ import 'vs/workbench/contrib/debug/browser/extensionHostDebugService'; // Welcome Banner import 'vs/workbench/contrib/welcomeBanner/browser/welcomeBanner.contribution'; +// Welcome Dialog +import 'vs/workbench/contrib/welcomeDialog/browser/welcomeDialog.contribution'; + // Webview import 'vs/workbench/contrib/webview/browser/webview.web.contribution'; @@ -163,14 +160,11 @@ import 'vs/workbench/contrib/tasks/browser/taskService'; import 'vs/workbench/contrib/tags/browser/workspaceTagsService'; // Issues -import 'vs/workbench/contrib/issue/browser/issue.web.contribution'; +import 'vs/workbench/contrib/issue/browser/issue.contribution'; // Splash import 'vs/workbench/contrib/splash/browser/splash.contribution'; -// Offline -import 'vs/workbench/contrib/offline/browser/offline.contribution'; - //#endregion @@ -185,118 +179,38 @@ import 'vs/workbench/contrib/offline/browser/offline.contribution'; // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import { create, commands, env, window, workspace, logger } from 'vs/workbench/browser/web.factory'; -import { IWorkbench, ICommand, ICommonTelemetryPropertiesResolver, IDefaultEditor, IDefaultLayout, IDefaultView, IDevelopmentOptions, IExternalUriResolver, IExternalURLOpener, IHomeIndicator, IInitialColorTheme, IPosition, IProductQualityChangeHandler, IRange, IResourceUriProvider, ISettingsSyncOptions, IShowPortCandidate, ITunnel, ITunnelFactory, ITunnelOptions, ITunnelProvider, IWelcomeBanner, IWelcomeBannerAction, IWindowIndicator, IWorkbenchConstructionOptions, Menu } from 'vs/workbench/browser/web.api'; -import { UriComponents, URI } from 'vs/base/common/uri'; -import { IWebSocketFactory, IWebSocket } from 'vs/platform/remote/browser/browserSocketFactory'; +import { Menu } from 'vs/workbench/browser/web.api'; +import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { IProductConfiguration } from 'vs/base/common/product'; -import { ICredentialsProvider } from 'vs/platform/credentials/common/credentials'; -// eslint-disable-next-line no-duplicate-imports -import type { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService'; -// eslint-disable-next-line no-duplicate-imports -import type { IUpdateProvider, IUpdate } from 'vs/workbench/services/update/browser/updateService'; -// eslint-disable-next-line no-duplicate-imports -import type { IWorkspace, IWorkspaceProvider } from 'vs/workbench/services/host/browser/browserHostService'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { UserDataSyncResourceProviderService } from 'vs/platform/userDataSync/common/userDataSyncResourceProvider'; +import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; export { // Factory create, - IWorkbenchConstructionOptions, - IWorkbench, // Basic Types URI, - UriComponents, Event, Emitter, - IDisposable, Disposable, - - // Workspace - IWorkspace, - IWorkspaceProvider, - - // WebSockets - IWebSocketFactory, - IWebSocket, - - // Resources - IResourceUriProvider, - - // Credentials - ICredentialsProvider, - - // Callbacks - IURLCallbackProvider, - - // LogLevel + GroupOrientation, LogLevel, + RemoteAuthorityResolverError, + RemoteAuthorityResolverErrorCode, - // SettingsSync - ISettingsSyncOptions, - - // Updates/Quality - IUpdateProvider, - IUpdate, - IProductQualityChangeHandler, - - // Telemetry - ICommonTelemetryPropertiesResolver, - - // External Uris - IExternalUriResolver, - - // External URL Opener - IExternalURLOpener, - - // Tunnel - ITunnelProvider, - ITunnelFactory, - ITunnel, - ITunnelOptions, - - // Ports - IShowPortCandidate, - - // Commands - ICommand, - commands, - Menu, - - // Logger - logger, - - // Window - window, - - // Branding - IHomeIndicator, - IWelcomeBanner, - IWelcomeBannerAction, - IProductConfiguration, - IWindowIndicator, - IInitialColorTheme, - - // Default layout - IDefaultView, - IDefaultEditor, - IDefaultLayout, - IPosition, - IRange as ISelection, - - // Env + // Facade API env, - - // Workspace + window, workspace, - - // Development - IDevelopmentOptions + commands, + logger, + Menu }; - //#endregion //#region diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index ada6a156c7..ba41767047 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -206,7 +206,7 @@ declare module 'vscode' { /** * Get a word-range at the given position. By default words are defined by * common separators, like space, -, _, etc. In addition, per language custom - * [word definitions} can be defined. It + * [word definitions] can be defined. It * is also possible to provide a custom regular expression. * * * *Note 1:* A custom regular expression must not match the empty string and @@ -733,7 +733,7 @@ declare module 'vscode' { */ OpenOpen = 0, /** - * The decoration's range will not widen when edits occur at the start of end. + * The decoration's range will not widen when edits occur at the start or end. */ ClosedClosed = 1, /** @@ -752,10 +752,10 @@ declare module 'vscode' { export interface TextDocumentShowOptions { /** * An optional view column in which the {@link TextEditor editor} should be shown. - * The default is the {@link ViewColumn.Active active}, other values are adjusted to - * be `Min(column, columnCount + 1)`, the {@link ViewColumn.Active active}-column is - * not adjusted. Use {@linkcode ViewColumn.Beside} to open the - * editor to the side of the currently active one. + * The default is the {@link ViewColumn.Active active}. Columns that do not exist + * will be created as needed up to the maximum of {@linkcode ViewColumn.Nine}. + * Use {@linkcode ViewColumn.Beside} to open the editor to the side of the currently + * active one. */ viewColumn?: ViewColumn; @@ -814,10 +814,10 @@ declare module 'vscode' { export interface NotebookDocumentShowOptions { /** * An optional view column in which the {@link NotebookEditor notebook editor} should be shown. - * The default is the {@link ViewColumn.Active active}, other values are adjusted to - * be `Min(column, columnCount + 1)`, the {@link ViewColumn.Active active}-column is - * not adjusted. Use {@linkcode ViewColumn.Beside} to open the - * editor to the side of the currently active one. + * The default is the {@link ViewColumn.Active active}. Columns that do not exist + * will be created as needed up to the maximum of {@linkcode ViewColumn.Nine}. + * Use {@linkcode ViewColumn.Beside} to open the editor to the side of the currently + * active one. */ readonly viewColumn?: ViewColumn; @@ -1753,8 +1753,9 @@ declare module 'vscode' { /** * Optional flag indicating if this item is picked initially. This is only honored when using - * the {@link window.showQuickPick()} API. To do the same thing with the {@link window.createQuickPick()} API, - * simply set the {@link QuickPick.selectedItems} to the items you want picked initially. + * the {@link window.showQuickPick showQuickPick()} API. To do the same thing with + * the {@link window.createQuickPick createQuickPick()} API, simply set the {@link QuickPick.selectedItems} + * to the items you want picked initially. * (*Note:* This is only honored when the picker allows multiple selections.) * * @see {@link QuickPickOptions.canPickMany} @@ -1773,8 +1774,8 @@ declare module 'vscode' { /** * Optional buttons that will be rendered on this particular item. These buttons will trigger * an {@link QuickPickItemButtonEvent} when clicked. Buttons are only rendered when using a quickpick - * created by the {@link window.createQuickPick()} API. Buttons are not rendered when using - * the {@link window.showQuickPick()} API. + * created by the {@link window.createQuickPick createQuickPick()} API. Buttons are not rendered when using + * the {@link window.showQuickPick showQuickPick()} API. * * Note: this property is ignored when {@link QuickPickItem.kind kind} is set to {@link QuickPickItemKind.Separator} */ @@ -2020,7 +2021,7 @@ declare module 'vscode' { /** * Selection of the pre-filled {@linkcode InputBoxOptions.value value}. Defined as tuple of two number where the * first is the inclusive start index and the second the exclusive end index. When `undefined` the whole - * word will be selected, when empty (start equals end) only the cursor will be set, + * pre-filled value will be selected, when empty (start equals end) only the cursor will be set, * otherwise the defined range will be selected. */ valueSelection?: [number, number]; @@ -2067,7 +2068,9 @@ declare module 'vscode' { export class RelativePattern { /** - * A base file path to which this pattern will be matched against relatively. + * A base file path to which this pattern will be matched against relatively. The + * file path must be absolute, should not have any trailing path separators and + * not include any relative segments (`.` or `..`). */ baseUri: Uri; @@ -2277,6 +2280,18 @@ declare module 'vscode' { */ static readonly RefactorInline: CodeActionKind; + /** + * Base kind for refactoring move actions: `refactor.move` + * + * Example move actions: + * + * - Move a function to a new file + * - Move a property between classes + * - Move method to base class + * - ... + */ + static readonly RefactorMove: CodeActionKind; + /** * Base kind for refactoring rewrite actions: `refactor.rewrite` * @@ -2286,7 +2301,6 @@ declare module 'vscode' { * - Add or remove parameter * - Encapsulate field * - Make method static - * - Move method to base class * - ... */ static readonly RefactorRewrite: CodeActionKind; @@ -2471,19 +2485,32 @@ declare module 'vscode' { } /** - * The code action interface defines the contract between extensions and - * the [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature. + * Provides contextual actions for code. Code actions typically either fix problems or beautify/refactor code. * - * A code action can be any command that is {@link commands.getCommands known} to the system. + * Code actions are surfaced to users in a few different ways: + * + * - The [lightbulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature, which shows + * a list of code actions at the current cursor position. The lightbulb's list of actions includes both quick fixes + * and refactorings. + * - As commands that users can run, such as `Refactor`. Users can run these from the command palette or with keybindings. + * - As source actions, such `Organize Imports`. + * - {@link CodeActionKind.QuickFix Quick fixes} are shown in the problems view. + * - Change applied on save by the `editor.codeActionsOnSave` setting. */ export interface CodeActionProvider { /** - * Provide commands for the given document and range. + * Get code actions for a given range in a document. + * + * Only return code actions that are relevant to user for the requested range. Also keep in mind how the + * returned code actions will appear in the UI. The lightbulb widget and `Refactor` commands for instance show + * returned code actions as a list, so do not return a large number of code actions that will overwhelm the user. * * @param document The document in which the command was invoked. - * @param range The selector or range for which the command was invoked. This will always be a selection if - * there is a currently active editor. - * @param context Context carrying additional information. + * @param range The selector or range for which the command was invoked. This will always be a + * {@link Selection selection} if the actions are being requested in the currently active editor. + * @param context Provides additional information about what code actions are being requested. You can use this + * to see what specific type of code actions are being requested by the editor in order to return more relevant + * actions and avoid returning irrelevant code actions that the editor will discard. * @param token A cancellation token. * * @return An array of code actions, such as quick fixes or refactorings. The lack of a result can be signaled @@ -2742,8 +2769,12 @@ declare module 'vscode' { /** * Indicates that this markdown string is from a trusted source. Only *trusted* * markdown supports links that execute commands, e.g. `[Run it](command:myCommandId)`. + * + * Defaults to `false` (commands are disabled). + * + * If this is an object, only the set of commands listed in `enabledCommands` are allowed. */ - isTrusted?: boolean; + isTrusted?: boolean | { readonly enabledCommands: readonly string[] }; /** * Indicates that this markdown string can contain {@link ThemeIcon ThemeIcons}, e.g. `$(zap)`. @@ -3428,6 +3459,120 @@ declare module 'vscode' { constructor(range: Range, newText: string); } + /** + * A snippet edit represents an interactive edit that is performed by + * the editor. + * + * *Note* that a snippet edit can always be performed as a normal {@link TextEdit text edit}. + * This will happen when no matching editor is open or when a {@link WorkspaceEdit workspace edit} + * contains snippet edits for multiple files. In that case only those that match the active editor + * will be performed as snippet edits and the others as normal text edits. + */ + export class SnippetTextEdit { + + /** + * Utility to create a replace snippet edit. + * + * @param range A range. + * @param snippet A snippet string. + * @return A new snippet edit object. + */ + static replace(range: Range, snippet: SnippetString): SnippetTextEdit; + + /** + * Utility to create an insert snippet edit. + * + * @param position A position, will become an empty range. + * @param snippet A snippet string. + * @return A new snippet edit object. + */ + static insert(position: Position, snippet: SnippetString): SnippetTextEdit; + + /** + * The range this edit applies to. + */ + range: Range; + + /** + * The {@link SnippetString snippet} this edit will perform. + */ + snippet: SnippetString; + + /** + * Create a new snippet edit. + * + * @param range A range. + * @param snippet A snippet string. + */ + constructor(range: Range, snippet: SnippetString); + } + + /** + * A notebook edit represents edits that should be applied to the contents of a notebook. + */ + export class NotebookEdit { + + /** + * Utility to create a edit that replaces cells in a notebook. + * + * @param range The range of cells to replace + * @param newCells The new notebook cells. + */ + static replaceCells(range: NotebookRange, newCells: NotebookCellData[]): NotebookEdit; + + /** + * Utility to create an edit that replaces cells in a notebook. + * + * @param index The index to insert cells at. + * @param newCells The new notebook cells. + */ + static insertCells(index: number, newCells: NotebookCellData[]): NotebookEdit; + + /** + * Utility to create an edit that deletes cells in a notebook. + * + * @param range The range of cells to delete. + */ + static deleteCells(range: NotebookRange): NotebookEdit; + + /** + * Utility to create an edit that update a cell's metadata. + * + * @param index The index of the cell to update. + * @param newCellMetadata The new metadata for the cell. + */ + static updateCellMetadata(index: number, newCellMetadata: { [key: string]: any }): NotebookEdit; + + /** + * Utility to create an edit that updates the notebook's metadata. + * + * @param newNotebookMetadata The new metadata for the notebook. + */ + static updateNotebookMetadata(newNotebookMetadata: { [key: string]: any }): NotebookEdit; + + /** + * Range of the cells being edited. May be empty. + */ + range: NotebookRange; + + /** + * New cells being inserted. May be empty. + */ + newCells: NotebookCellData[]; + + /** + * Optional new metadata for the cells. + */ + newCellMetadata?: { [key: string]: any }; + + /** + * Optional new metadata for the notebook. + */ + newNotebookMetadata?: { [key: string]: any }; + + constructor(range: NotebookRange, newCells: NotebookCellData[]); + } + /** * Additional data for entries of a workspace edit. Supports to label entries and marks entries * as needing confirmation by the user. The editor groups edits with equal labels into tree nodes, @@ -3456,6 +3601,16 @@ declare module 'vscode' { iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; } + /** + * Additional data about a workspace edit. + */ + export interface WorkspaceEditMetadata { + /** + * Signal to the editor that this edit is a refactoring. + */ + isRefactoring?: boolean; + } + /** * A workspace edit is a collection of textual and files changes for * multiple resources and documents. @@ -3507,12 +3662,36 @@ declare module 'vscode' { has(uri: Uri): boolean; /** - * Set (and replace) text edits for a resource. + * Set (and replace) text edits or snippet edits for a resource. * * @param uri A resource identifier. - * @param edits An array of text edits. + * @param edits An array of edits. */ - set(uri: Uri, edits: TextEdit[]): void; + set(uri: Uri, edits: ReadonlyArray): void; + + /** + * Set (and replace) text edits or snippet edits with metadata for a resource. + * + * @param uri A resource identifier. + * @param edits An array of edits. + */ + set(uri: Uri, edits: ReadonlyArray<[TextEdit | SnippetTextEdit, WorkspaceEditEntryMetadata]>): void; + + /** + * Set (and replace) notebook edits for a resource. + * + * @param uri A resource identifier. + * @param edits An array of edits. + */ + set(uri: Uri, edits: readonly NotebookEdit[]): void; + + /** + * Set (and replace) notebook edits with metadata for a resource. + * + * @param uri A resource identifier. + * @param edits An array of edits. + */ + set(uri: Uri, edits: ReadonlyArray<[NotebookEdit, WorkspaceEditEntryMetadata]>): void; /** * Get the text edits for a resource. @@ -3525,14 +3704,26 @@ declare module 'vscode' { /** * Create a regular file. * - * @param uri Uri of the new file.. + * @param uri Uri of the new file. * @param options Defines if an existing file should be overwritten or be - * ignored. When overwrite and ignoreIfExists are both set overwrite wins. + * ignored. When `overwrite` and `ignoreIfExists` are both set `overwrite` wins. * When both are unset and when the file already exists then the edit cannot - * be applied successfully. + * be applied successfully. The `content`-property allows to set the initial contents + * the file is being created with. * @param metadata Optional metadata for the entry. */ - createFile(uri: Uri, options?: { overwrite?: boolean; ignoreIfExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void; + createFile(uri: Uri, options?: { + readonly overwrite?: boolean; + readonly ignoreIfExists?: boolean; + + /** + * The initial contents of the new file. + * + * If creating a file from a {@link DocumentDropEditProvider drop operation}, you can + * pass in a {@link DataTransferFile} to improve performance by avoiding extra data copying. + */ + readonly contents?: Uint8Array | DataTransferFile; + }, metadata?: WorkspaceEditEntryMetadata): void; /** * Delete a file or folder. @@ -3540,7 +3731,7 @@ declare module 'vscode' { * @param uri The uri of the file that is to be deleted. * @param metadata Optional metadata for the entry. */ - deleteFile(uri: Uri, options?: { recursive?: boolean; ignoreIfNotExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void; + deleteFile(uri: Uri, options?: { readonly recursive?: boolean; readonly ignoreIfNotExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void; /** * Rename a file or folder. @@ -3551,7 +3742,7 @@ declare module 'vscode' { * ignored. When overwrite and ignoreIfExists are both set overwrite wins. * @param metadata Optional metadata for the entry. */ - renameFile(oldUri: Uri, newUri: Uri, options?: { overwrite?: boolean; ignoreIfExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void; + renameFile(oldUri: Uri, newUri: Uri, options?: { readonly overwrite?: boolean; readonly ignoreIfExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void; /** * Get all text edits grouped by resource. @@ -6300,6 +6491,70 @@ declare module 'vscode' { dispose(): void; } + /** + * A channel for containing log output. + * + * To get an instance of a `LogOutputChannel` use + * {@link window.createOutputChannel createOutputChannel}. + */ + export interface LogOutputChannel extends OutputChannel { + + /** + * The current log level of the channel. Defaults to {@link env.logLevel editor log level}. + */ + readonly logLevel: LogLevel; + + /** + * An {@link Event} which fires when the log level of the channel changes. + */ + readonly onDidChangeLogLevel: Event; + + /** + * Outputs the given trace message to the channel. Use this method to log verbose information. + * + * The message is only logged if the channel is configured to display {@link LogLevel.Trace trace} log level. + * + * @param message trace message to log + */ + trace(message: string, ...args: any[]): void; + + /** + * Outputs the given debug message to the channel. + * + * The message is only logged if the channel is configured to display {@link LogLevel.Debug debug} log level or lower. + * + * @param message debug message to log + */ + debug(message: string, ...args: any[]): void; + + /** + * Outputs the given information message to the channel. + * + * The message is only logged if the channel is configured to display {@link LogLevel.Info info} log level or lower. + * + * @param message info message to log + */ + info(message: string, ...args: any[]): void; + + /** + * Outputs the given warning message to the channel. + * + * The message is only logged if the channel is configured to display {@link LogLevel.Warning warning} log level or lower. + * + * @param message warning message to log + */ + warn(message: string, ...args: any[]): void; + + /** + * Outputs the given error or error message to the channel. + * + * The message is only logged if the channel is configured to display {@link LogLevel.Error error} log level or lower. + * + * @param error Error or error message to log + */ + error(error: string | Error, ...args: any[]): void; + } + /** * Accessibility information which controls screen reader behavior. */ @@ -6537,10 +6792,10 @@ declare module 'vscode' { export interface TerminalEditorLocationOptions { /** * A view column in which the {@link Terminal terminal} should be shown in the editor area. - * Use {@link ViewColumn.Active active} to open in the active editor group, other values are - * adjusted to be `Min(column, columnCount + 1)`, the - * {@link ViewColumn.Active active}-column is not adjusted. Use - * {@linkcode ViewColumn.Beside} to open the editor to the side of the currently active one. + * The default is the {@link ViewColumn.Active active}. Columns that do not exist + * will be created as needed up to the maximum of {@linkcode ViewColumn.Nine}. + * Use {@linkcode ViewColumn.Beside} to open the editor to the side of the currently + * active one. */ viewColumn: ViewColumn; /** @@ -7190,6 +7445,11 @@ declare module 'vscode' { * Controls whether the terminal is cleared before executing the task. */ clear?: boolean; + + /** + * Controls whether the terminal is closed after executing the task. + */ + close?: boolean; } /** @@ -8245,9 +8505,11 @@ declare module 'vscode' { /** * Controls whether command uris are enabled in webview content or not. * - * Defaults to false. + * Defaults to `false` (command uris are disabled). + * + * If you pass in an array, only the commands in the array are allowed. */ - readonly enableCommandUris?: boolean; + readonly enableCommandUris?: boolean | readonly string[]; /** * Root paths from which the webview can load local (filesystem) resources using uris from `asWebviewUri` @@ -8572,6 +8834,12 @@ declare module 'vscode' { */ description?: string; + /** + * The badge to display for this webview view. + * To remove the badge, set to undefined. + */ + badge?: ViewBadge | undefined; + /** * Event fired when the view is disposed. * @@ -8793,7 +9061,7 @@ declare module 'vscode' { } /** - * Additional information used to implement {@linkcode CustomEditableDocument.backup}. + * Additional information used to implement {@linkcode CustomDocumentBackup}. */ interface CustomDocumentBackupContext { /** @@ -9020,6 +9288,42 @@ declare module 'vscode' { Web = 2 } + /** + * Log levels + */ + export enum LogLevel { + + /** + * No messages are logged with this level. + */ + Off = 0, + + /** + * All messages are logged with this level. + */ + Trace = 1, + + /** + * Messages with debug and higher log level are logged with this level. + */ + Debug = 2, + + /** + * Messages with info and higher log level are logged with this level. + */ + Info = 3, + + /** + * Messages with warning and higher log level are logged with this level. + */ + Warning = 4, + + /** + * Only error messages are logged with this level. + */ + Error = 5 + } + /** * Namespace describing the environment the editor runs in. */ @@ -9090,6 +9394,15 @@ declare module 'vscode' { */ export const onDidChangeTelemetryEnabled: Event; + /** + * Creates a new {@link TelemetryLogger telemetry logger}. + * + * @param sender The telemetry sender that is used by the telemetry logger. + * @param options Options for the telemetry logger. + * @returns A new telemetry logger + */ + export function createTelemetryLogger(sender: TelemetrySender, options?: TelemetryLoggerOptions): TelemetryLogger; + /** * The name of a remote. Defined by extensions, popular samples are `wsl` for the Windows * Subsystem for Linux or `ssh-remote` for remotes using a secure shell. @@ -9184,6 +9497,16 @@ declare module 'vscode' { * @return A uri that can be used on the client machine. */ export function asExternalUri(target: Uri): Thenable; + + /** + * The current log level of the editor. + */ + export const logLevel: LogLevel; + + /** + * An {@link Event} which fires when the log level of the editor changes. + */ + export const onDidChangeLogLevel: Event; } /** @@ -9451,8 +9774,8 @@ declare module 'vscode' { * to control where the editor is being shown. Might change the {@link window.activeTextEditor active editor}. * * @param document A text document to be shown. - * @param column A view column in which the {@link TextEditor editor} should be shown. The default is the {@link ViewColumn.Active active}, other values - * are adjusted to be `Min(column, columnCount + 1)`, the {@link ViewColumn.Active active}-column is not adjusted. Use {@linkcode ViewColumn.Beside} + * @param column A view column in which the {@link TextEditor editor} should be shown. The default is the {@link ViewColumn.Active active}. + * Columns that do not exist will be created as needed up to the maximum of {@linkcode ViewColumn.Nine}. Use {@linkcode ViewColumn.Beside} * to open the editor to the side of the currently active one. * @param preserveFocus When `true` the editor will not take focus. * @return A promise that resolves to an {@link TextEditor editor}. @@ -9749,6 +10072,14 @@ declare module 'vscode' { */ export function createOutputChannel(name: string, languageId?: string): OutputChannel; + /** + * Creates a new {@link LogOutputChannel log output channel} with the given name. + * + * @param name Human-readable string which will be used to represent the channel in the UI. + * @param options Options for the log output channel. + */ + export function createOutputChannel(name: string, options: { log: true }): LogOutputChannel; + /** * Create and show a new webview panel. * @@ -9829,22 +10160,23 @@ declare module 'vscode' { /** * Creates a status bar {@link StatusBarItem item}. * - * @param alignment The alignment of the item. - * @param priority The priority of the item. Higher values mean the item should be shown more to the left. - * @return A new status bar item. - */ - export function createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem; - - /** - * Creates a status bar {@link StatusBarItem item}. - * - * @param id The unique identifier of the item. + * @param id The identifier of the item. Must be unique within the extension. * @param alignment The alignment of the item. * @param priority The priority of the item. Higher values mean the item should be shown more to the left. * @return A new status bar item. */ export function createStatusBarItem(id: string, alignment?: StatusBarAlignment, priority?: number): StatusBarItem; + /** + * Creates a status bar {@link StatusBarItem item}. + * + * @see {@link createStatusBarItem} for creating a status bar item with an identifier. + * @param alignment The alignment of the item. + * @param priority The priority of the item. Higher values mean the item should be shown more to the left. + * @return A new status bar item. + */ + export function createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem; + /** * Creates a {@link Terminal} with a backing shell process. The cwd of the terminal will be the workspace * directory if it exists. @@ -10101,6 +10433,8 @@ declare module 'vscode' { /** * A file associated with a {@linkcode DataTransferItem}. + * + * Instances of this type can only be created by the editor and not by extensions. */ export interface DataTransferFile { /** @@ -10168,6 +10502,7 @@ declare module 'vscode' { * Retrieves the data transfer item for a given mime type. * * @param mimeType The mime type to get the data transfer item for, such as `text/plain` or `image/png`. + * Mimes type look ups are case-insensitive. * * Special mime types: * - `text/uri-list` — A string with `toString()`ed Uris separated by `\r\n`. To specify a cursor position in the file, @@ -10177,7 +10512,8 @@ declare module 'vscode' { /** * Sets a mime type to data transfer item mapping. - * @param mimeType The mime type to set the data for. + * + * @param mimeType The mime type to set the data for. Mimes types stored in lower case, with case-insensitive looks up. * @param value The data transfer item for the given mime type. */ set(mimeType: string, value: DataTransferItem): void; @@ -10258,6 +10594,22 @@ declare module 'vscode' { handleDrop?(target: T | undefined, dataTransfer: DataTransfer, token: CancellationToken): Thenable | void; } + /** + * A badge presenting a value for a view + */ + export interface ViewBadge { + + /** + * A label to present in tooltip for the badge. + */ + readonly tooltip: string; + + /** + * The value to present in the badge. + */ + readonly value: number; + } + /** * Represents a Tree view */ @@ -10311,6 +10663,12 @@ declare module 'vscode' { */ description?: string; + /** + * The badge to display for this TreeView. + * To remove the badge, set to undefined. + */ + badge?: ViewBadge | undefined; + /** * Reveals the given element in the tree view. * If the tree view is not visible then the tree view is shown and element is revealed. @@ -10550,7 +10908,7 @@ declare module 'vscode' { * Whether the terminal process environment should be exactly as provided in * `TerminalOptions.env`. When this is false (default), the environment will be based on the * window's environment and also apply configured platform settings like - * `terminal.integrated.windows.env` on top. When this is true, the complete environment + * `terminal.integrated.env.windows` on top. When this is true, the complete environment * must be provided as nothing will be inherited from the process or any configuration. */ strictEnv?: boolean; @@ -10708,7 +11066,7 @@ declare module 'vscode' { * **Example:** Exit the terminal when "y" is pressed, otherwise show a notification. * ```typescript * const writeEmitter = new vscode.EventEmitter(); - * const closeEmitter = new vscode.EventEmitter(); + * const closeEmitter = new vscode.EventEmitter(); * const pty: vscode.Pseudoterminal = { * onDidWrite: writeEmitter.event, * onDidClose: closeEmitter.event, @@ -10721,7 +11079,8 @@ declare module 'vscode' { * closeEmitter.fire(); * } * }; - * vscode.window.createTerminal({ name: 'Exit example', pty }); + * const terminal = vscode.window.createTerminal({ name: 'Exit example', pty }); + * terminal.show(true); * ``` */ onDidClose?: Event; @@ -10824,6 +11183,41 @@ declare module 'vscode' { * without providing an exit code. */ readonly code: number | undefined; + + /** + * The reason that triggered the exit of a terminal. + */ + readonly reason: TerminalExitReason; + } + + /** + * Terminal exit reason kind. + */ + export enum TerminalExitReason { + /** + * Unknown reason. + */ + Unknown = 0, + + /** + * The window closed/reloaded. + */ + Shutdown = 1, + + /** + * The shell process exited. + */ + Process = 2, + + /** + * The user closed the terminal. + */ + User = 3, + + /** + * An extension disposed the terminal. + */ + Extension = 4, } /** @@ -11093,7 +11487,7 @@ declare module 'vscode' { value: string; /** - * Optional placeholder in the filter text. + * Optional placeholder shown in the filter textbox when no filter has been entered. */ placeholder: string | undefined; @@ -11185,7 +11579,18 @@ declare module 'vscode' { value: string; /** - * Optional placeholder in the filter text. + * Selection range in the input value. Defined as tuple of two number where the + * first is the inclusive start index and the second the exclusive end index. When `undefined` the whole + * pre-filled value will be selected, when empty (start equals end) only the cursor will be set, + * otherwise the defined range will be selected. + * + * This property does not get updated when the user types or makes a selection, + * but it can be updated by the extension. + */ + valueSelection: readonly [number, number] | undefined; + + /** + * Optional placeholder shown when no value has been input. */ placeholder: string | undefined; @@ -11466,7 +11871,7 @@ declare module 'vscode' { * An event that is fired when files are going to be deleted. * * To make modifications to the workspace before the files are deleted, - * call the {@link FileWillCreateEvent.waitUntil `waitUntil}-function with a + * call the {@link FileWillCreateEvent.waitUntil `waitUntil`}-function with a * thenable that resolves to a {@link WorkspaceEdit workspace edit}. */ export interface FileWillDeleteEvent { @@ -11526,7 +11931,7 @@ declare module 'vscode' { * An event that is fired when files are going to be renamed. * * To make modifications to the workspace before the files are renamed, - * call the {@link FileWillCreateEvent.waitUntil `waitUntil}-function with a + * call the {@link FileWillCreateEvent.waitUntil `waitUntil`}-function with a * thenable that resolves to a {@link WorkspaceEdit workspace edit}. */ export interface FileWillRenameEvent { @@ -11751,9 +12156,11 @@ declare module 'vscode' { * by an optional set of `workspaceFoldersToAdd` on the `vscode.workspace.workspaceFolders` array. This "splice" * behavior can be used to add, remove and change workspace folders in a single operation. * - * If the first workspace folder is added, removed or changed, the currently executing extensions (including the - * one that called this method) will be terminated and restarted so that the (deprecated) `rootPath` property is - * updated to point to the first workspace folder. + * **Note:** in some cases calling this method may result in the currently executing extensions (including the + * one that called this method) to be terminated and restarted. For example when the first workspace folder is + * added, removed or changed the (deprecated) `rootPath` property is updated to point to the first workspace + * folder. Another case is when transitioning from an empty or single-folder workspace into a multi-folder + * workspace (see also: https://code.visualstudio.com/docs/editor/workspaces). * * Use the {@linkcode onDidChangeWorkspaceFolders onDidChangeWorkspaceFolders()} event to get notified when the * workspace folders have been updated. @@ -11878,7 +12285,7 @@ declare module 'vscode' { * If you want to monitor file events across all opened workspace folders: * * ```ts - * vscode.workspace.createFileSystemWatcher('**​/*.js')); + * vscode.workspace.createFileSystemWatcher('**​/*.js'); * ``` * * *Note:* the array of workspace folders can be empty if no workspace is opened (empty window). @@ -11954,9 +12361,10 @@ declare module 'vscode' { * not be attempted, when a single edit fails. * * @param edit A workspace edit. + * @param metadata Optional {@link WorkspaceEditMetadata metadata} for the edit. * @return A thenable that resolves when the edit could be applied. */ - export function applyEdit(edit: WorkspaceEdit): Thenable; + export function applyEdit(edit: WorkspaceEdit, metadata?: WorkspaceEditMetadata): Thenable; /** * All text documents currently known to the editor. @@ -12073,11 +12481,11 @@ declare module 'vscode' { export const notebookDocuments: readonly NotebookDocument[]; /** - * Open a notebook. Will return early if this notebook is already {@link notebook.notebookDocuments loaded}. Otherwise - * the notebook is loaded and the {@linkcode notebook.onDidOpenNotebookDocument onDidOpenNotebookDocument}-event fires. + * Open a notebook. Will return early if this notebook is already {@link notebookDocuments loaded}. Otherwise + * the notebook is loaded and the {@linkcode onDidOpenNotebookDocument}-event fires. * * *Note* that the lifecycle of the returned notebook is owned by the editor and not by the extension. That means an - * {@linkcode notebook.onDidCloseNotebookDocument onDidCloseNotebookDocument}-event can occur at any time after. + * {@linkcode onDidCloseNotebookDocument}-event can occur at any time after. * * *Note* that opening a notebook does not show a notebook editor. This function only returns a notebook document which * can be shown in a notebook editor but it can also be used for other things. @@ -12103,6 +12511,21 @@ declare module 'vscode' { */ export const onDidChangeNotebookDocument: Event; + /** + * An event that is emitted when a {@link NotebookDocument notebook document} will be saved to disk. + * + * *Note 1:* Subscribers can delay saving by registering asynchronous work. For the sake of data integrity the editor + * might save without firing this event. For instance when shutting down with dirty files. + * + * *Note 2:* Subscribers are called sequentially and they can {@link NotebookDocumentWillSaveEvent.waitUntil delay} saving + * by registering asynchronous work. Protection against misbehaving listeners is implemented as such: + * * there is an overall time budget that all listeners share and if that is exhausted no further listener is called + * * listeners that take a long time or produce errors frequently will not be called anymore + * + * The current thresholds are 1.5 seconds as overall time budget and a listener can misbehave 3 times before being ignored. + */ + export const onWillSaveNotebookDocument: Event; + /** * An event that is emitted when a {@link NotebookDocument notebook} is saved. */ @@ -12992,7 +13415,7 @@ declare module 'vscode' { /** * The metadata of this cell. Can be anything but must be JSON-stringifyable. */ - readonly metadata: { [key: string]: any }; + readonly metadata: { readonly [key: string]: any }; /** * The outputs of this cell. @@ -13092,7 +13515,7 @@ declare module 'vscode' { export interface NotebookDocumentCellChange { /** - * The affected notebook. + * The affected cell. */ readonly cell: NotebookCell; @@ -13172,6 +13595,61 @@ declare module 'vscode' { readonly cellChanges: readonly NotebookDocumentCellChange[]; } + /** + * An event that is fired when a {@link NotebookDocument notebook document} will be saved. + * + * To make modifications to the document before it is being saved, call the + * {@linkcode NotebookDocumentWillSaveEvent.waitUntil waitUntil}-function with a thenable + * that resolves to a {@link WorkspaceEdit workspace edit}. + */ + export interface NotebookDocumentWillSaveEvent { + /** + * A cancellation token. + */ + readonly token: CancellationToken; + + /** + * The {@link NotebookDocument notebook document} that will be saved. + */ + readonly notebook: NotebookDocument; + + /** + * The reason why save was triggered. + */ + readonly reason: TextDocumentSaveReason; + + /** + * Allows to pause the event loop and to apply {@link WorkspaceEdit workspace edit}. + * Edits of subsequent calls to this function will be applied in order. The + * edits will be *ignored* if concurrent modifications of the notebook document happened. + * + * *Note:* This function can only be called during event dispatch and not + * in an asynchronous manner: + * + * ```ts + * workspace.onWillSaveNotebookDocument(event => { + * // async, will *throw* an error + * setTimeout(() => event.waitUntil(promise)); + * + * // sync, OK + * event.waitUntil(promise); + * }) + * ``` + * + * @param thenable A thenable that resolves to {@link WorkspaceEdit workspace edit}. + */ + waitUntil(thenable: Thenable): void; + + /** + * Allows to pause the event loop until the provided thenable resolved. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } + /** * The summary of a notebook cell execution. */ @@ -14606,6 +15084,26 @@ declare module 'vscode' { * If compact is true, debug sessions with a single child are hidden in the CALL STACK view to make the tree more compact. */ compact?: boolean; + + /** + * When true, a save will not be triggered for open editors when starting a debug session, regardless of the value of the `debug.saveBeforeStart` setting. + */ + suppressSaveBeforeStart?: boolean; + + /** + * When true, the debug toolbar will not be shown for this session. + */ + suppressDebugToolbar?: boolean; + + /** + * When true, the window statusbar color will not be changed for this session. + */ + suppressDebugStatusbar?: boolean; + + /** + * When true, the debug viewlet will not be automatically revealed for this session. + */ + suppressDebugView?: boolean; } /** @@ -14686,7 +15184,7 @@ declare module 'vscode' { * * @param debugType The debug type for which the provider is registered. * @param provider The {@link DebugConfigurationProvider debug configuration provider} to register. - * @param triggerKind The {@link DebugConfigurationProviderTrigger trigger} for which the 'provideDebugConfiguration' method of the provider is registered. If `triggerKind` is missing, the value `DebugConfigurationProviderTriggerKind.Initial` is assumed. + * @param triggerKind The {@link DebugConfigurationProviderTriggerKind trigger} for which the 'provideDebugConfiguration' method of the provider is registered. If `triggerKind` is missing, the value `DebugConfigurationProviderTriggerKind.Initial` is assumed. * @return A {@link Disposable} that unregisters this provider when being disposed. */ export function registerDebugConfigurationProvider(debugType: string, provider: DebugConfigurationProvider, triggerKind?: DebugConfigurationProviderTriggerKind): Disposable; @@ -14840,6 +15338,14 @@ declare module 'vscode' { Preview = 1 } + /** + * The state of a comment thread. + */ + export enum CommentThreadState { + Unresolved = 0, + Resolved = 1 + } + /** * A collection of {@link Comment comments} representing a conversation at a particular range in a document. */ @@ -14897,6 +15403,11 @@ declare module 'vscode' { */ label?: string; + /** + * The optional state of a comment thread, which may affect how the comment is displayed. + */ + state?: CommentThreadState; + /** * Dispose this comment thread. * @@ -15145,18 +15656,35 @@ declare module 'vscode' { readonly label: string; } + /** + * Optional options to be used when calling {@link authentication.getSession} with the flag `forceNewSession`. + */ + export interface AuthenticationForceNewSessionOptions { + /** + * An optional message that will be displayed to the user when we ask to re-authenticate. Providing additional context + * as to why you are asking a user to re-authenticate can help increase the odds that they will accept. + */ + detail?: string; + } /** * Options to be used when getting an {@link AuthenticationSession} from an {@link AuthenticationProvider}. */ export interface AuthenticationGetSessionOptions { /** - * Whether the existing user session preference should be cleared. + * Whether the existing session preference should be cleared. * * For authentication providers that support being signed into multiple accounts at once, the user will be * prompted to select an account to use when {@link authentication.getSession getSession} is called. This preference * is remembered until {@link authentication.getSession getSession} is called with this flag. * + * Note: + * The preference is extension specific. So if one extension calls {@link authentication.getSession getSession}, it will not + * affect the session preference for another extension calling {@link authentication.getSession getSession}. Additionally, + * the preference is set for the current workspace and also globally. This means that new workspaces will use the "global" + * value at first and then when this flag is provided, a new value can be set for that workspace. This also means + * that pre-existing workspaces will not lose their preference if a new workspace sets this flag. + * * Defaults to false. */ clearSessionPreference?: boolean; @@ -15188,7 +15716,7 @@ declare module 'vscode' { * * This defaults to false. */ - forceNewSession?: boolean | { detail: string }; + forceNewSession?: boolean | AuthenticationForceNewSessionOptions; /** * Whether we should show the indication to sign in in the Accounts menu. @@ -15338,7 +15866,7 @@ declare module 'vscode' { * @param options The {@link AuthenticationGetSessionOptions} to use * @returns A thenable that resolves to an authentication session */ - export function getSession(providerId: string, scopes: readonly string[], options: AuthenticationGetSessionOptions & { forceNewSession: true | { detail: string } }): Thenable; + export function getSession(providerId: string, scopes: readonly string[], options: AuthenticationGetSessionOptions & { forceNewSession: true | AuthenticationForceNewSessionOptions }): Thenable; /** * Get an authentication session matching the desired scopes. Rejects if a provider with providerId is not @@ -15376,6 +15904,89 @@ declare module 'vscode' { export function registerAuthenticationProvider(id: string, label: string, provider: AuthenticationProvider, options?: AuthenticationProviderOptions): Disposable; } + /** + * Namespace for localization-related functionality in the extension API. To use this properly, + * you must have `l10n` defined in your extension manifest and have bundle.l10n..json files. + * For more information on how to generate bundle.l10n..json files, check out the + * [vscode-l10n repo](https://github.com/microsoft/vscode-l10n). + * + * Note: Built-in extensions (for example, Git, TypeScript Language Features, GitHub Authentication) + * are excluded from the `l10n` property requirement. In other words, they do not need to specify + * a `l10n` in the extension manifest because their translated strings come from Language Packs. + */ + export namespace l10n { + /** + * Marks a string for localization. If a localized bundle is available for the language specified by + * {@link env.language} and the bundle has a localized value for this message, then that localized + * value will be returned (with injected {@link args} values for any templated values). + * + * @param message - The message to localize. Supports index templating where strings like `{0}` and `{1}` are + * replaced by the item at that index in the {@link args} array. + * @param args - The arguments to be used in the localized string. The index of the argument is used to + * match the template placeholder in the localized string. + * @returns localized string with injected arguments. + * + * @example + * l10n.t('Hello {0}!', 'World'); + */ + export function t(message: string, ...args: Array): string; + + /** + * Marks a string for localization. If a localized bundle is available for the language specified by + * {@link env.language} and the bundle has a localized value for this message, then that localized + * value will be returned (with injected {@link args} values for any templated values). + * + * @param message The message to localize. Supports named templating where strings like `{foo}` and `{bar}` are + * replaced by the value in the Record for that key (foo, bar, etc). + * @param args The arguments to be used in the localized string. The name of the key in the record is used to + * match the template placeholder in the localized string. + * @returns localized string with injected arguments. + * + * @example + * l10n.t('Hello {name}', { name: 'Erich' }); + */ + export function t(message: string, args: Record): string; + /** + * Marks a string for localization. If a localized bundle is available for the language specified by + * {@link env.language} and the bundle has a localized value for this message, then that localized + * value will be returned (with injected args values for any templated values). + * + * @param options The options to use when localizing the message. + * @returns localized string with injected arguments. + */ + export function t(options: { + /** + * The message to localize. If {@link options.args args} is an array, this message supports index templating where strings like + * `{0}` and `{1}` are replaced by the item at that index in the {@link options.args args} array. If `args` is a `Record`, + * this supports named templating where strings like `{foo}` and `{bar}` are replaced by the value in + * the Record for that key (foo, bar, etc). + */ + message: string; + /** + * The arguments to be used in the localized string. As an array, the index of the argument is used to + * match the template placeholder in the localized string. As a Record, the key is used to match the template + * placeholder in the localized string. + */ + args?: Array | Record; + /** + * A comment to help translators understand the context of the message. + */ + comment: string | string[]; + }): string; + /** + * The bundle of localized strings that have been loaded for the extension. + * It's undefined if no bundle has been loaded. The bundle is typically not loaded if + * there was no bundle found or when we are running with the default language. + */ + export const bundle: { [key: string]: string } | undefined; + /** + * The URI of the localization bundle that has been loaded for the extension. + * It's undefined if no bundle has been loaded. The bundle is typically not loaded if + * there was no bundle found or when we are running with the default language. + */ + export const uri: Uri | undefined; + } + /** * Namespace for testing functionality. Tests are published by registering * {@link TestController} instances, then adding {@link TestItem TestItems}. @@ -15451,6 +16062,13 @@ declare module 'vscode' { */ isDefault: boolean; + /** + * Whether this profile supports continuous running of requests. If so, + * then {@link TestRunRequest.continuous} may be set to `true`. Defaults + * to false. + */ + supportsContinuousRun: boolean; + /** * Associated tag for the profile. If this is set, only {@link TestItem} * instances with the same tag will be eligible to execute in this profile. @@ -15471,6 +16089,11 @@ declare module 'vscode' { * associated with the request should be created before the function returns * or the returned promise is resolved. * + * If {@link supportsContinuousRun} is set, then {@link TestRunRequest.continuous} + * may be `true`. In this case, the profile should observe changes to + * source code and create new test runs by calling {@link TestController.createTestRun}, + * until the cancellation is requested on the `token`. + * * @param request Request information for the test run. * @param cancellationToken Token that signals the used asked to abort the * test run. If cancellation is requested on this token, all {@link TestRun} @@ -15493,7 +16116,7 @@ declare module 'vscode' { */ export interface TestController { /** - * The id of the controller passed in {@link vscode.tests.createTestController}. + * The id of the controller passed in {@link tests.createTestController}. * This must be globally unique. */ readonly id: string; @@ -15509,7 +16132,7 @@ declare module 'vscode' { * "test tree." * * The extension controls when to add tests. For example, extensions should - * add tests for a file when {@link vscode.workspace.onDidOpenTextDocument} + * add tests for a file when {@link workspace.onDidOpenTextDocument} * fires in order for decorations for tests within a file to be visible. * * However, the editor may sometimes explicitly request children using the @@ -15525,16 +16148,17 @@ declare module 'vscode' { * @param runHandler Function called to start a test run. * @param isDefault Whether this is the default action for its kind. * @param tag Profile test tag. + * @param supportsContinuousRun Whether the profile supports continuous running. * @returns An instance of a {@link TestRunProfile}, which is automatically * associated with this controller. */ - createRunProfile(label: string, kind: TestRunProfileKind, runHandler: (request: TestRunRequest, token: CancellationToken) => Thenable | void, isDefault?: boolean, tag?: TestTag): TestRunProfile; + createRunProfile(label: string, kind: TestRunProfileKind, runHandler: (request: TestRunRequest, token: CancellationToken) => Thenable | void, isDefault?: boolean, tag?: TestTag, supportsContinuousRun?: boolean): TestRunProfile; /** * A function provided by the extension that the editor may call to request * children of a test item, if the {@link TestItem.canResolveChildren} is * `true`. When called, the item should discover children and call - * {@link vscode.tests.createTestItem} as children are discovered. + * {@link TestController.createTestItem} as children are discovered. * * Generally the extension manages the lifecycle of test items, but under * certain conditions the editor may request the children of a specific @@ -15607,9 +16231,9 @@ declare module 'vscode' { /** * A TestRunRequest is a precursor to a {@link TestRun}, which in turn is - * created by passing a request to {@link tests.runTests}. The TestRunRequest - * contains information about which tests should be run, which should not be - * run, and how they are run (via the {@link TestRunRequest.profile profile}). + * created by passing a request to {@link TestController.createTestRun}. The + * TestRunRequest contains information about which tests should be run, which + * should not be run, and how they are run (via the {@link TestRunRequest.profile profile}). * * In general, TestRunRequests are created by the editor and pass to * {@link TestRunProfile.runHandler}, however you can also create test @@ -15643,16 +16267,24 @@ declare module 'vscode' { */ readonly profile: TestRunProfile | undefined; + /** + * Whether the profile should run continuously as source code changes. Only + * relevant for profiles that set {@link TestRunProfile.supportsContinuousRun}. + */ + readonly continuous?: boolean; + /** * @param include Array of specific tests to run, or undefined to run all tests * @param exclude An array of tests to exclude from the run. * @param profile The run profile used for this request. + * @param continuous Whether to run tests continuously as source changes. */ - constructor(include?: readonly TestItem[], exclude?: readonly TestItem[], profile?: TestRunProfile); + constructor(include?: readonly TestItem[], exclude?: readonly TestItem[], profile?: TestRunProfile, continuous?: boolean); } /** - * Options given to {@link TestController.runTests} + * A TestRun represents an in-progress or completed test run and + * provides methods to report the state of individual tests in the run. */ export interface TestRun { /** @@ -15721,7 +16353,8 @@ declare module 'vscode' { /** * Appends raw output from the test runner. On the user's request, the * output will be displayed in a terminal. ANSI escape sequences, - * such as colors and text styles, are supported. + * such as colors and text styles, are supported. New lines must be given + * as CRLF (`\r\n`) rather than LF (`\n`). * * @param output Output text to append. * @param location Indicate that the output was logged at the given @@ -15731,7 +16364,7 @@ declare module 'vscode' { appendOutput(output: string, location?: Location, test?: TestItem): void; /** - * Signals that the end of the test run. Any tests included in the run whose + * Signals the end of the test run. Any tests included in the run whose * states have not been updated will have their state reset. */ end(): void; @@ -15817,7 +16450,7 @@ declare module 'vscode' { /** * Tags associated with this test item. May be used in combination with - * {@link TestRunProfile.tags}, or simply as an organizational feature. + * {@link TestRunProfile.tag tags}, or simply as an organizational feature. */ tags: readonly TestTag[]; @@ -16130,7 +16763,7 @@ declare module 'vscode' { * Whether or not the group is currently active. * * *Note* that only one tab group is active at a time, but that multiple tab - * groups can have an {@link TabGroup.aciveTab active tab}. + * groups can have an {@link activeTab active tab}. * * @see {@link Tab.isActive} */ @@ -16157,7 +16790,7 @@ declare module 'vscode' { } /** - * Represents the main editor area which consists of multple groups which contain tabs. + * Represents the main editor area which consists of multiple groups which contain tabs. */ export interface TabGroups { /** @@ -16200,6 +16833,144 @@ declare module 'vscode' { */ close(tabGroup: TabGroup | readonly TabGroup[], preserveFocus?: boolean): Thenable; } + + /** + * A special value wrapper denoting a value that is safe to not clean. + * This is to be used when you can guarantee no identifiable information is contained in the value and the cleaning is improperly redacting it. + */ + export class TelemetryTrustedValue { + readonly value: T; + + constructor(value: T); + } + + /** + * A telemetry logger which can be used by extensions to log usage and error telementry. + * + * A logger wraps around an {@link TelemetrySender sender} but it guarantees that + * - user settings to disable or tweak telemetry are respected, and that + * - potential sensitive data is removed + * + * It also enables an "echo UI" that prints whatever data is send and it allows the editor + * to forward unhandled errors to the respective extensions. + * + * To get an instance of a `TelemetryLogger`, use + * {@link env.createTelemetryLogger `createTelemetryLogger`}. + */ + export interface TelemetryLogger { + + /** + * An {@link Event} which fires when the enablement state of usage or error telemetry changes. + */ + readonly onDidChangeEnableStates: Event; + + /** + * Whether or not usage telemetry is enabled for this logger. + */ + readonly isUsageEnabled: boolean; + + /** + * Whether or not error telemetry is enabled for this logger. + */ + readonly isErrorsEnabled: boolean; + + /** + * Log a usage event. + * + * After completing cleaning, telemetry setting checks, and data mix-in calls `TelemetrySender.sendEventData` to log the event. + * Automatically supports echoing to extension telemetry output channel. + * @param eventName The event name to log + * @param data The data to log + */ + logUsage(eventName: string, data?: Record): void; + + /** + * Log an error event. + * + * After completing cleaning, telemetry setting checks, and data mix-in calls `TelemetrySender.sendEventData` to log the event. Differs from `logUsage` in that it will log the event if the telemetry setting is Error+. + * Automatically supports echoing to extension telemetry output channel. + * @param eventName The event name to log + * @param data The data to log + */ + logError(eventName: string, data?: Record): void; + + /** + * Log an error event. + * + * Calls `TelemetrySender.sendErrorData`. Does cleaning, telemetry checks, and data mix-in. + * Automatically supports echoing to extension telemetry output channel. + * Will also automatically log any exceptions thrown within the extension host process. + * @param error The error object which contains the stack trace cleaned of PII + * @param data Additional data to log alongside the stack trace + */ + logError(error: Error, data?: Record): void; + + /** + * Dispose this object and free resources. + */ + dispose(): void; + } + + /** + * The telemetry sender is the contract between a telemetry logger and some telemetry service. **Note** that extensions must NOT + * call the methods of their sender directly as the logger provides extra guards and cleaning. + * + * ```js + * const sender: vscode.TelemetrySender = {...}; + * const logger = vscode.env.createTelemetryLogger(sender); + * + * // GOOD - uses the logger + * logger.logUsage('myEvent', { myData: 'myValue' }); + * + * // BAD - uses the sender directly: no data cleansing, ignores user settings, no echoing to the telemetry output channel etc + * sender.logEvent('myEvent', { myData: 'myValue' }); + * ``` + */ + export interface TelemetrySender { + /** + * Function to send event data without a stacktrace. Used within a {@link TelemetryLogger} + * + * @param eventName The name of the event which you are logging + * @param data A serializable key value pair that is being logged + */ + sendEventData(eventName: string, data?: Record): void; + + /** + * Function to send an error. Used within a {@link TelemetryLogger} + * + * @param error The error being logged + * @param data Any additional data to be collected with the exception + */ + sendErrorData(error: Error, data?: Record): void; + + /** + * Optional flush function which will give this sender a chance to send any remaining events + * as its {@link TelemetryLogger} is being disposed + */ + flush?(): void | Thenable; + } + + /** + * Options for creating a {@link TelemetryLogger} + */ + export interface TelemetryLoggerOptions { + /** + * Whether or not you want to avoid having the built-in common properties such as os, extension name, etc injected into the data object. + * Defaults to `false` if not defined. + */ + readonly ignoreBuiltInCommonProperties?: boolean; + + /** + * Whether or not unhandled errors on the extension host caused by your extension should be logged to your sender. + * Defaults to `false` if not defined. + */ + readonly ignoreUnhandledErrors?: boolean; + + /** + * Any additional common properties which should be injected into the data object. + */ + readonly additionalCommonProperties?: Record; + } } /** diff --git a/src/vscode-dts/vscode.proposed.authGetSessions.d.ts b/src/vscode-dts/vscode.proposed.authGetSessions.d.ts new file mode 100644 index 0000000000..7095c4586b --- /dev/null +++ b/src/vscode-dts/vscode.proposed.authGetSessions.d.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/152399 + + export interface AuthenticationForceNewSessionOptions { + /** + * The session that you are asking to be recreated. The Auth Provider can use this to + * help guide the user to log in to the correct account. + */ + sessionToRecreate?: AuthenticationSession; + } + + export namespace authentication { + /** + * Get all authentication sessions matching the desired scopes that this extension has access to. In order to request access, + * use {@link getSession}. To request an additional account, specify {@link AuthenticationGetSessionOptions.clearSessionPreference} + * and {@link AuthenticationGetSessionOptions.createIfNone} together. + * + * Currently, there are only two authentication providers that are contributed from built in extensions + * to the editor that implement GitHub and Microsoft authentication: their providerId's are 'github' and 'microsoft'. + * + * @param providerId The id of the provider to use + * @param scopes A list of scopes representing the permissions requested. These are dependent on the authentication provider + * @returns A thenable that resolves to a readonly array of authentication sessions. + */ + export function getSessions(providerId: string, scopes: readonly string[]): Thenable; + } + + /** + * The options passed in to the provider when creating a session. + */ + export interface AuthenticationProviderCreateSessionOptions { + /** + * The session that is being asked to be recreated. If this is passed in, the provider should + * attempt to recreate the session based on the information in this session. + */ + sessionToRecreate?: AuthenticationSession; + } + + export interface AuthenticationProvider { + /** + * Prompts a user to login. + * + * If login is successful, the onDidChangeSessions event should be fired. + * + * If login fails, a rejected promise should be returned. + * + * If the provider has specified that it does not support multiple accounts, + * then this should never be called if there is already an existing session matching these + * scopes. + * @param scopes A list of scopes, permissions, that the new session should be created with. + * @param options Additional options for creating a session. + * @returns A promise that resolves to an authentication session. + */ + createSession(scopes: readonly string[], options: AuthenticationProviderCreateSessionOptions): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.badges.d.ts b/src/vscode-dts/vscode.proposed.badges.d.ts deleted file mode 100644 index 278bcf5445..0000000000 --- a/src/vscode-dts/vscode.proposed.badges.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/62783 @matthewjamesadam - - /** - * A badge presenting a value for a view - */ - export interface ViewBadge { - - /** - * A label to present in tooltips for the badge - */ - readonly tooltip: string; - - /** - * The value to present in the badge - */ - readonly value: number; - } - - export interface TreeView { - /** - * The badge to display for this TreeView. - * To remove the badge, set to undefined. - */ - badge?: ViewBadge | undefined; - } - - export interface WebviewView { - /** - * The badge to display for this webview view. - * To remove the badge, set to undefined. - */ - badge?: ViewBadge | undefined; - } -} diff --git a/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts b/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts new file mode 100644 index 0000000000..d52dfa0ee8 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.canonicalUriProvider.d.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/180582 + + export namespace workspace { + /** + * + * @param scheme The URI scheme that this provider can provide canonical URIs for. + * A canonical URI represents the conversion of a resource's alias into a source of truth URI. + * Multiple aliases may convert to the same source of truth URI. + * @param provider A provider which can convert URIs of scheme @param scheme to + * a canonical URI which is stable across machines. + */ + export function registerCanonicalUriProvider(scheme: string, provider: CanonicalUriProvider): Disposable; + + /** + * + * @param uri The URI to provide a canonical URI for. + * @param token A cancellation token for the request. + */ + export function getCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, token: CancellationToken): ProviderResult; + } + + export interface CanonicalUriProvider { + /** + * + * @param uri The URI to provide a canonical URI for. + * @param options Options that the provider should honor in the URI it returns. + * @param token A cancellation token for the request. + * @returns The canonical URI for the requested URI or undefined if no canonical URI can be provided. + */ + provideCanonicalUri(uri: Uri, options: CanonicalUriRequestOptions, token: CancellationToken): ProviderResult; + } + + export interface CanonicalUriRequestOptions { + /** + * + * The desired scheme of the canonical URI. + */ + targetScheme: string; + } +} diff --git a/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts b/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts new file mode 100644 index 0000000000..c19d4d986d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/135591 @alexr00 + + // export interface FileDecorationProvider { + // provideFileDecoration(uri: Uri, token: CancellationToken): ProviderResult; + // } + + /** + * A file decoration represents metadata that can be rendered with a file. + */ + export class FileDecoration2 { + /** + * A very short string that represents this decoration. + */ + badge?: string | ThemeIcon; + + /** + * 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 | ThemeIcon, tooltip?: string, color?: ThemeColor); + } +} diff --git a/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts b/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts new file mode 100644 index 0000000000..f71dc82d51 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/171166 + + export enum CommentState { + Published = 0, + Draft = 1 + } + + export interface Comment { + state?: CommentState; + } +} diff --git a/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts b/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts new file mode 100644 index 0000000000..9c6a29c8b5 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `comments/comment/editorActions` menu diff --git a/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts b/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts new file mode 100644 index 0000000000..4df143f341 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder for comment peek context menus + +// https://github.com/microsoft/vscode/issues/151533 @alexr00 + + diff --git a/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts b/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts new file mode 100644 index 0000000000..22bc87b699 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribCommentThreadAdditionalMenu.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder for comment thread additional menus + +// https://github.com/microsoft/vscode/issues/163281 diff --git a/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts b/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts index 51b62d7dd9..f875a1032a 100644 --- a/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts +++ b/src/vscode-dts/vscode.proposed.contribEditSessions.d.ts @@ -3,4 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// empty placeholder declaration for the `contribEditSessions`-contribution point +// empty placeholder for edit sessions contribution point from core + +// https://github.com/microsoft/vscode/issues/157734 @joyceerhl diff --git a/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts b/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts new file mode 100644 index 0000000000..7052227900 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribEditorContentMenu.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `editor/content` menu diff --git a/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts b/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts new file mode 100644 index 0000000000..ec5d218fb3 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribMergeEditorMenus.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for `mergeEditor/*` menus diff --git a/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts b/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts new file mode 100644 index 0000000000..46936a7d9d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribNotebookStaticPreloads.d.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder declaration for the `notebookPreload` contribution point diff --git a/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts b/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts index a35925ffe3..5d5bbf2ae3 100644 --- a/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts +++ b/src/vscode-dts/vscode.proposed.contribShareMenu.d.ts @@ -4,3 +4,4 @@ *--------------------------------------------------------------------------------------------*/ // empty placeholder declaration for the `file/share`-submenu contribution point +// https://github.com/microsoft/vscode/issues/176316 diff --git a/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts b/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts new file mode 100644 index 0000000000..fe09f62a58 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.contribStatusBarItems.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// empty placeholder for status bar items contribution + +// https://github.com/microsoft/vscode/issues/167874 @jrieken diff --git a/src/vscode-dts/vscode.proposed.contribViewSize.d.ts b/src/vscode-dts/vscode.proposed.contribViewSize.d.ts deleted file mode 100644 index 48f747eee8..0000000000 --- a/src/vscode-dts/vscode.proposed.contribViewSize.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// empty placeholder for view size - -// https://github.com/microsoft/vscode/issues/122283 @alexr00 - -/** - * View contributions can include a `size`, which can be a number. A number works similar to the css flex property. - * - * For example, if you have 3 views, with sizes 1, 1, and 2, the views of size 1 will together take up the same amount of space as the view of size 2. - * - * A number value will only be used as an initial size. After a user has changed the size of the view, the user's choice will be restored. -*/ - diff --git a/src/vscode-dts/vscode.proposed.customEditorMove.d.ts b/src/vscode-dts/vscode.proposed.customEditorMove.d.ts index 561b7b3f5e..1ee2451c9f 100644 --- a/src/vscode-dts/vscode.proposed.customEditorMove.d.ts +++ b/src/vscode-dts/vscode.proposed.customEditorMove.d.ts @@ -23,7 +23,7 @@ declare module 'vscode' { * * @return Thenable indicating that the webview editor has been moved. */ - // eslint-disable-next-line vscode-dts-provider-naming + // eslint-disable-next-line local/vscode-dts-provider-naming moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel, token: CancellationToken): Thenable; } } diff --git a/src/vscode-dts/vscode.proposed.debugFocus.d.ts b/src/vscode-dts/vscode.proposed.debugFocus.d.ts new file mode 100644 index 0000000000..5ab9f830a9 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.debugFocus.d.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // See https://github.com/microsoft/vscode/issues/63943 + + export class ThreadFocus { + /** + * Create a ThreadFocus + * @param session + * @param threadId + * @param frameId + */ + constructor( + session: DebugSession, + threadId?: number); + + + /** + * Debug session for thread. + */ + readonly session: DebugSession; + + /** + * Id of the associated thread (DAP id). May be undefined if thread has become unselected. + */ + readonly threadId: number | undefined; + } + + export class StackFrameFocus { + /** + * Create a StackFrameFocus + * @param session + * @param threadId + * @param frameId + */ + constructor( + session: DebugSession, + threadId?: number, + frameId?: number); + + + /** + * Debug session for thread. + */ + readonly session: DebugSession; + + /** + * Id of the associated thread (DAP id). May be undefined if a frame is unselected. + */ + readonly threadId: number | undefined; + /** + * Id of the stack frame (DAP id). May be undefined if a frame is unselected. + */ + readonly frameId: number | undefined; + } + + + export namespace debug { + /** + * The currently focused thread or stack frame id, or `undefined` if this has not been set. (e.g. not in debug mode). + */ + export let stackFrameFocus: ThreadFocus | StackFrameFocus | undefined; + + /** + * An {@link Event} which fires when the {@link debug.stackFrameFocus} changes. Provides a sessionId. threadId is not undefined + * when a thread of frame has gained focus. frameId is defined when a stackFrame has gained focus. + */ + export const onDidChangeStackFrameFocus: Event; + } +} diff --git a/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts b/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts new file mode 100644 index 0000000000..c955a0a1b1 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.diffContentOptions.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + // TODO@rebornix: add github issue link + + export interface NotebookDocumentContentOptions { + /** + * Controls if a cell metadata property should be reverted when the cell content + * is reverted in notebook diff editor. + */ + cellContentMetadata?: { [key: string]: boolean | undefined }; + } +} diff --git a/src/vscode-dts/vscode.proposed.documentPaste.d.ts b/src/vscode-dts/vscode.proposed.documentPaste.d.ts index d63fc00506..0579c363e4 100644 --- a/src/vscode-dts/vscode.proposed.documentPaste.d.ts +++ b/src/vscode-dts/vscode.proposed.documentPaste.d.ts @@ -37,36 +37,68 @@ declare module 'vscode' { * * @return Optional workspace edit that applies the paste. Return undefined to use standard pasting. */ - provideDocumentPasteEdits(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; + provideDocumentPasteEdits?(document: TextDocument, ranges: readonly Range[], dataTransfer: DataTransfer, token: CancellationToken): ProviderResult; } /** * An operation applied on paste */ class DocumentPasteEdit { + /** + * Identifies the type of edit. + * + * This id should be unique within the extension but does not need to be unique across extensions. + */ + id: string; + + /** + * Human readable label that describes the edit. + */ + label: string; + + /** + * The relative priority of this edit. Higher priority items are shown first in the UI. + * + * Defaults to `0`. + */ + priority?: number; + /** * The text or snippet to insert at the pasted locations. */ - readonly insertText: string | SnippetString; + insertText: string | SnippetString; /** * An optional additional edit to apply on paste. */ - readonly additionalEdit?: WorkspaceEdit; + additionalEdit?: WorkspaceEdit; /** * @param insertText The text or snippet to insert at the pasted locations. + * + * TODO: Reverse args, but this will break existing consumers :( */ - constructor(insertText: string | SnippetString); + constructor(insertText: string | SnippetString, id: string, label: string); } interface DocumentPasteProviderMetadata { /** - * Mime types that `provideDocumentPasteEdits` should be invoked for. - * - * Use the special `files` mimetype to indicate the provider should be invoked if any files are present in the `DataTransfer`. + * Mime types that {@link DocumentPasteEditProvider.prepareDocumentPaste provideDocumentPasteEdits} may add on copy. */ - readonly pasteMimeTypes: readonly string[]; + readonly copyMimeTypes?: readonly string[]; + + /** + * Mime types that {@link DocumentPasteEditProvider.provideDocumentPasteEdits provideDocumentPasteEdits} should be invoked for. + * + * This can either be an exact mime type such as `image/png`, or a wildcard pattern such as `image/*`. + * + * Use `text/uri-list` for resources dropped from the explorer or other tree views in the workbench. + * + * Use `files` to indicate that the provider should be invoked if any {@link DataTransferFile files} are present in the {@link DataTransfer}. + * Note that {@link DataTransferFile} entries are only created when dropping content from outside the editor, such as + * from the operating system. + */ + readonly pasteMimeTypes?: readonly string[]; } namespace languages { diff --git a/src/vscode-dts/vscode.proposed.dropMetadata.d.ts b/src/vscode-dts/vscode.proposed.dropMetadata.d.ts new file mode 100644 index 0000000000..c86f32519a --- /dev/null +++ b/src/vscode-dts/vscode.proposed.dropMetadata.d.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/179430 + + export interface DocumentDropEdit { + /** + * Identifies the type of edit. + * + * This id should be unique within the extension but does not need to be unique across extensions. + */ + id?: string; + + /** + * The relative priority of this edit. Higher priority items are shown first in the UI. + * + * Defaults to `0`. + */ + priority?: number; + + /** + * Human readable label that describes the edit. + */ + label?: string; + } + + export interface DocumentDropEditProviderMetadata { + /** + * List of data transfer types that the provider supports. + * + * This can either be an exact mime type such as `image/png`, or a wildcard pattern such as `image/*`. + * + * Use `text/uri-list` for resources dropped from the explorer or other tree views in the workbench. + * + * Use `files` to indicate that the provider should be invoked if any {@link DataTransferFile files} are present in the {@link DataTransfer}. + * Note that {@link DataTransferFile} entries are only created when dropping content from outside the editor, such as + * from the operating system. + */ + readonly dropMimeTypes: readonly string[]; + } + + export namespace languages { + export function registerDocumentDropEditProvider(selector: DocumentSelector, provider: DocumentDropEditProvider, metadata?: DocumentDropEditProviderMetadata): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts b/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts new file mode 100644 index 0000000000..e7fa91f867 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.editSessionIdentityProvider.d.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/157734 + + export namespace workspace { + /** + * An event that is emitted when an edit session identity is about to be requested. + */ + export const onWillCreateEditSessionIdentity: Event; + + /** + * + * @param scheme The URI scheme that this provider can provide edit session identities for. + * @param provider A provider which can convert URIs for workspace folders of scheme @param scheme to + * an edit session identifier which is stable across machines. This enables edit sessions to be resolved. + */ + export function registerEditSessionIdentityProvider(scheme: string, provider: EditSessionIdentityProvider): Disposable; + } + + export interface EditSessionIdentityProvider { + /** + * + * @param workspaceFolder The workspace folder to provide an edit session identity for. + * @param token A cancellation token for the request. + * @returns A string representing the edit session identity for the requested workspace folder. + */ + provideEditSessionIdentity(workspaceFolder: WorkspaceFolder, token: CancellationToken): ProviderResult; + + /** + * + * @param identity1 An edit session identity. + * @param identity2 A second edit session identity to compare to @param identity1. + * @param token A cancellation token for the request. + * @returns An {@link EditSessionIdentityMatch} representing the edit session identity match confidence for the provided identities. + */ + provideEditSessionIdentityMatch(identity1: string, identity2: string, token: CancellationToken): ProviderResult; + } + + export enum EditSessionIdentityMatch { + Complete = 100, + Partial = 50, + None = 0 + } + + export interface EditSessionIdentityWillCreateEvent { + + /** + * A cancellation token. + */ + readonly token: CancellationToken; + + /** + * The workspace folder to create an edit session identity for. + */ + readonly workspaceFolder: WorkspaceFolder; + + /** + * Allows to pause the event until the provided thenable resolves. + * + * *Note:* This function can only be called during event dispatch. + * + * @param thenable A thenable that delays saving. + */ + waitUntil(thenable: Thenable): void; + } +} diff --git a/src/vscode-dts/vscode.proposed.envCollectionOptions.d.ts b/src/vscode-dts/vscode.proposed.envCollectionOptions.d.ts new file mode 100644 index 0000000000..7ba95acd6a --- /dev/null +++ b/src/vscode-dts/vscode.proposed.envCollectionOptions.d.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/179476 + + /** + * Options applied to the mutator. + */ + export interface EnvironmentVariableMutatorOptions { + /** + * Apply to the environment just before the process is created. + * + * Defaults to true. + */ + applyAtProcessCreation?: boolean; + + /** + * Apply to the environment in the shell integration script. Note that this _will not_ apply + * the mutator if shell integration is disabled or not working for some reason. + * + * Defaults to false. + */ + applyAtShellIntegration?: boolean; + } + + /** + * A type of mutation and its value to be applied to an environment variable. + */ + export interface EnvironmentVariableMutator { + /** + * Options applied to the mutator. + */ + readonly options: EnvironmentVariableMutatorOptions; + } + + export interface EnvironmentVariableCollection extends Iterable<[variable: string, mutator: EnvironmentVariableMutator]> { + /** + * @param options Options applied to the mutator. + */ + replace(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void; + + /** + * @param options Options applied to the mutator. + */ + append(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void; + + /** + * @param options Options applied to the mutator. + */ + prepend(variable: string, value: string, options?: EnvironmentVariableMutatorOptions): void; + } +} diff --git a/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts b/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts new file mode 100644 index 0000000000..1f074a551d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.envCollectionWorkspace.d.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/182069 + + // export interface ExtensionContext { + // /** + // * Gets the extension's environment variable collection for this workspace, enabling changes + // * to be applied to terminal environment variables. + // * + // * @param scope The scope to which the environment variable collection applies to. + // */ + // readonly environmentVariableCollection: EnvironmentVariableCollection & { getScopedEnvironmentVariableCollection(scope: EnvironmentVariableScope): EnvironmentVariableCollection }; + // } + + export type EnvironmentVariableScope = { + /** + * Any specific workspace folder to get collection for. If unspecified, collection applicable to all workspace folders is returned. + */ + workspaceFolder?: WorkspaceFolder; + }; + + export interface EnvironmentVariableCollection extends Iterable<[variable: string, mutator: EnvironmentVariableMutator]> { + /** + * A description for the environment variable collection, this will be used to describe the + * changes in the UI. + */ + description: string | MarkdownString | undefined; + } +} diff --git a/extensions/xml-language-features/extension.webpack.config.js b/src/vscode-dts/vscode.proposed.envShellEvent.d.ts similarity index 60% rename from extensions/xml-language-features/extension.webpack.config.js rename to src/vscode-dts/vscode.proposed.envShellEvent.d.ts index f35561d9f2..a157a1a2b9 100644 --- a/extensions/xml-language-features/extension.webpack.config.js +++ b/src/vscode-dts/vscode.proposed.envShellEvent.d.ts @@ -3,18 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -//@ts-check +declare module 'vscode' { -'use strict'; + // See https://github.com/microsoft/vscode/issues/160694 + export namespace env { -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, - resolve: { - mainFields: ['module', 'main'] - }, - entry: { - extension: './src/extension.ts', + /** + * An {@link Event} which fires when the default shell changes. + */ + export const onDidChangeShell: Event; } -}); +} diff --git a/src/vscode-dts/vscode.proposed.fileComments.d.ts b/src/vscode-dts/vscode.proposed.fileComments.d.ts new file mode 100644 index 0000000000..d555014b74 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.fileComments.d.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface CommentThread2 { + /** + * The uri of the document the thread has been created on. + */ + readonly uri: Uri; + + /** + * The range the comment thread is located within the document. The thread icon will be shown + * at the last line of the range. + */ + range: Range | undefined; + + /** + * The ordered comments of the thread. + */ + comments: readonly Comment[]; + + /** + * Whether the thread should be collapsed or expanded when opening the document. + * Defaults to Collapsed. + */ + collapsibleState: CommentThreadCollapsibleState; + + /** + * Whether the thread supports reply. + * Defaults to true. + */ + canReply: boolean; + + /** + * Context value of the comment thread. This can be used to contribute thread specific actions. + * For example, a comment thread is given a context value as `editable`. When contributing actions to `comments/commentThread/title` + * using `menus` extension point, you can specify context value for key `commentThread` in `when` expression like `commentThread == editable`. + * ```json + * "contributes": { + * "menus": { + * "comments/commentThread/title": [ + * { + * "command": "extension.deleteCommentThread", + * "when": "commentThread == editable" + * } + * ] + * } + * } + * ``` + * This will show action `extension.deleteCommentThread` only for comment threads with `contextValue` is `editable`. + */ + contextValue?: string; + + /** + * The optional human-readable label describing the {@link CommentThread Comment Thread} + */ + label?: string; + + /** + * The optional state of a comment thread, which may affect how the comment is displayed. + */ + state?: CommentThreadState; + + /** + * Dispose this comment thread. + * + * Once disposed, this comment thread will be removed from visible editors and Comment Panel when appropriate. + */ + dispose(): void; + } + + export interface CommentController { + createCommentThread(uri: Uri, range: Range | undefined, comments: readonly Comment[]): CommentThread | CommentThread2; + } + + export interface CommentingRangeProvider2 { + /** + * Provide a list of ranges which allow new comment threads creation or null for a given document + */ + provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; + } +} diff --git a/src/vscode-dts/vscode.proposed.formatMultipleRanges.d.ts b/src/vscode-dts/vscode.proposed.formatMultipleRanges.d.ts new file mode 100644 index 0000000000..d76872b6c0 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.formatMultipleRanges.d.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/158776 + + + export interface DocumentRangeFormattingEditProvider { + + /** + * Provide formatting edits for multiple ranges in a document. + * + * The given ranges are hints and providers can decide to format a smaller + * or larger range. Often this is done by adjusting the start and end + * of the range to full syntax nodes. + * + * @param document The document in which the command was invoked. + * @param ranges The ranges which should be formatted. + * @param options Options controlling formatting. + * @param token A cancellation token. + * @return A set of text edits or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined`, `null`, or an empty array. + */ + provideDocumentRangesFormattingEdits?(document: TextDocument, ranges: Range[], options: FormattingOptions, token: CancellationToken): ProviderResult; + } +} diff --git a/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts b/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts new file mode 100644 index 0000000000..d3fbf312b0 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.handleIssueUri.d.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/46726 + + export interface IssueUriRequestHandler { + /** + *Handle the request by the issue reporter for the Uri you want to direct the user to. + */ + handleIssueUrlRequest(): ProviderResult; + } + + export namespace env { + /** + * Register an {@link IssueUriRequestHandler}. By registering an issue uri request handler, + * you can direct the built-in issue reporter to your issue reporting web experience of choice. + * The Uri that the handler returns will be opened in the user's browser. + * + * Examples of this include: + * - Using GitHub Issue Forms or GitHub Discussions you can pre-fill the issue creation with relevant information from the current workspace using query parameters + * - Directing to a different web form that isn't on GitHub for reporting issues + * + * @param handler the issue uri request handler to register for this extension. + */ + export function registerIssueUriRequestHandler(handler: IssueUriRequestHandler): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.indentSize.d.ts b/src/vscode-dts/vscode.proposed.indentSize.d.ts new file mode 100644 index 0000000000..894c48917e --- /dev/null +++ b/src/vscode-dts/vscode.proposed.indentSize.d.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + /** + * Represents a {@link TextEditor text editor}'s {@link TextEditor.options options}. + */ + export interface TextEditorOptions { + /** + * The size in spaces a tab takes. This is used for two purposes: + * - the rendering width of a tab character; + * - the number of spaces to insert when {@link TextEditorOptions.insertSpaces insertSpaces} is true + * and `indentSize` is set to `"tabSize"`. + * + * When getting a text editor's options, this property will always be a number (resolved). + * When setting a text editor's options, this property is optional and it can be a number or `"auto"`. + */ + tabSize?: number | string; + /** + * The number of spaces to insert when [insertSpaces](#TextEditorOptions.insertSpaces) is true. + * + * When getting a text editor's options, this property will always be a number (resolved). + * When setting a text editor's options, this property is optional and it can be a number or `"tabSize"`. + */ + indentSize?: number | 'tabSize'; + } +} diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts index ef52597ac7..9fec483a41 100644 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts +++ b/src/vscode-dts/vscode.proposed.inlineCompletionsAdditions.d.ts @@ -7,19 +7,6 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima - export interface InlineCompletionItemNew { - /** - * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. - * Defaults to `false`. - */ - completeBracketPairs?: boolean; - } - - export interface InlineCompletionItemProviderNew { - // eslint-disable-next-line vscode-dts-provider-naming - handleDidShowCompletionItem?(completionItem: InlineCompletionItemNew): void; - } - export interface InlineCompletionItem { /** * If set to `true`, unopened closing brackets are removed and unclosed opening brackets are closed. @@ -29,8 +16,19 @@ declare module 'vscode' { } export interface InlineCompletionItemProvider { - // eslint-disable-next-line vscode-dts-provider-naming - handleDidShowCompletionItem?(completionItem: InlineCompletionItem): void; + /** + * @param completionItem The completion item that was shown. + * @param updatedInsertText The actual insert text (after brackets were fixed). + */ + // eslint-disable-next-line local/vscode-dts-provider-naming + handleDidShowCompletionItem?(completionItem: InlineCompletionItem, updatedInsertText: string): void; + + /** + * Is called when an inline completion item was accepted partially. + * @param acceptedLength The length of the substring of the inline completion that was accepted already. + */ + // eslint-disable-next-line local/vscode-dts-provider-naming + handleDidPartiallyAcceptCompletionItem?(completionItem: InlineCompletionItem, acceptedLength: number): void; } // When finalizing `commands`, make sure to add a corresponding constructor parameter. @@ -39,5 +37,16 @@ declare module 'vscode' { * A list of commands associated with the inline completions of this list. */ commands?: Command[]; + + /** + * When set, overrides the user setting of `editor.inlineSuggest.suppressSuggestions`. + */ + suppressSuggestions?: boolean; + + /** + * When set and the user types a suggestion without derivating from it, the inline suggestion is not updated. + * Defaults to false (might change). + */ + enableForwardStability?: boolean; } } diff --git a/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts b/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts deleted file mode 100644 index ea96fddb29..0000000000 --- a/src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts +++ /dev/null @@ -1,168 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/124024 @hediet @alexdima - // Temporary API to allow for safe migration. - - export namespace languages { - - /** - * Registers an inline completion 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 completion provider. - * @return A {@link Disposable} that unregisters this provider when being disposed. - */ - export function registerInlineCompletionItemProviderNew(selector: DocumentSelector, provider: InlineCompletionItemProviderNew): Disposable; - } - - /** - * The inline completion item provider interface defines the contract between extensions and - * the inline completion feature. - * - * Providers are asked for completions either explicitly by a user gesture or implicitly when typing. - */ - export interface InlineCompletionItemProviderNew { - - /** - * Provides inline completion items for the given position and document. - * If inline completions are enabled, this method will be called whenever the user stopped typing. - * It will also be called when the user explicitly triggers inline completions or explicitly asks for the next or previous inline completion. - * In that case, all available inline completions should be returned. - * `context.triggerKind` can be used to distinguish between these scenarios. - * - * @param document The document inline completions are requested for. - * @param position The position inline completions are requested for. - * @param context A context object with additional information. - * @param token A cancellation token. - * @return An array of completion items or a thenable that resolves to an array of completion items. - */ - provideInlineCompletionItems(document: TextDocument, position: Position, context: InlineCompletionContextNew, token: CancellationToken): ProviderResult; - } - - /** - * Provides information about the context in which an inline completion was requested. - */ - export interface InlineCompletionContextNew { - /** - * Describes how the inline completion was triggered. - */ - readonly triggerKind: InlineCompletionTriggerKindNew; - - /** - * Provides information about the currently selected item in the autocomplete widget if it is visible. - * - * If set, provided inline completions must extend the text of the selected item - * and use the same range, otherwise they are not shown as preview. - * As an example, if the document text is `console.` and the selected item is `.log` replacing the `.` in the document, - * the inline completion must also replace `.` and start with `.log`, for example `.log()`. - * - * Inline completion providers are requested again whenever the selected item changes. - */ - readonly selectedCompletionInfo: SelectedCompletionInfoNew | undefined; - } - - /** - * Describes the currently selected completion item. - */ - export interface SelectedCompletionInfoNew { - /** - * The range that will be replaced if this completion item is accepted. - */ - readonly range: Range; - - /** - * The text the range will be replaced with if this completion is accepted. - */ - readonly text: string; - } - - /** - * Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. - */ - export enum InlineCompletionTriggerKindNew { - /** - * Completion was triggered explicitly by a user gesture. - * Return multiple completion items to enable cycling through them. - */ - Invoke = 0, - - /** - * Completion was triggered automatically while editing. - * It is sufficient to return a single completion item in this case. - */ - Automatic = 1, - } - - /** - * Represents a collection of {@link InlineCompletionItemNew inline completion items} to be presented - * in the editor. - */ - export class InlineCompletionListNew { - /** - * The inline completion items. - */ - items: InlineCompletionItemNew[]; - - /** - * A list of commands associated with the inline completions of this list. - */ - commands?: Command[]; - - /** - * Creates a new list of inline completion items with optionally given commands. - */ - constructor(items: InlineCompletionItemNew[], commands?: Command[]); - } - - /** - * An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. - * - * @see {@link InlineCompletionItemProviderNew.provideInlineCompletionItems} - */ - export class InlineCompletionItemNew { - /** - * The text to replace the range with. Must be set. - * Is used both for the preview and the accept operation. - */ - insertText: string | SnippetString; - - /** - * A text that is used to decide if this inline completion should be shown. When `falsy` - * the {@link InlineCompletionItemNew.insertText} is used. - * - * An inline completion is shown if the text to replace is a prefix of the filter text. - */ - filterText?: string; - - /** - * The range to replace. - * Must begin and end on the same line. - * - * Prefer replacements over insertions to provide a better experience when the user deletes typed text. - */ - range?: Range; - - /** - * An optional {@link Command} that is executed *after* inserting this completion. - */ - command?: Command; - - /** - * Creates a new inline completion item. - * - * @param insertText The text to replace the range with. - * @param range The range to replace. If not set, the word at the requested position will be used. - * @param command An optional {@link Command} that is executed *after* inserting this completion. - */ - constructor(insertText: string | SnippetString, range?: Range, command?: Command); - } -} diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts new file mode 100644 index 0000000000..1d74b8add5 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -0,0 +1,184 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface InteractiveEditorSlashCommand { + command: string; + detail?: string; + refer?: boolean; + // kind: CompletionItemKind; + } + + // todo@API make classes + export interface InteractiveEditorSession { + placeholder?: string; + slashCommands?: InteractiveEditorSlashCommand[]; + wholeRange?: Range; + } + + // todo@API make classes + export interface InteractiveEditorRequest { + session: InteractiveEditorSession; + prompt: string; + + selection: Selection; + wholeRange: Range; + attempt: number; + } + + // todo@API make classes + export interface InteractiveEditorResponse { + edits: TextEdit[] | WorkspaceEdit; + placeholder?: string; + wholeRange?: Range; + } + + // todo@API make classes + export interface InteractiveEditorMessageResponse { + contents: MarkdownString; + placeholder?: string; + wholeRange?: Range; + } + + export enum InteractiveEditorResponseFeedbackKind { + Unhelpful = 0, + Helpful = 1, + Undone = 2 + } + + export interface TextDocumentContext { + document: TextDocument; + selection: Selection; + action?: string; + } + + export interface InteractiveEditorSessionProvider { + // Create a session. The lifetime of this session is the duration of the editing session with the input mode widget. + prepareInteractiveEditorSession(context: TextDocumentContext, token: CancellationToken): ProviderResult; + + provideInteractiveEditorResponse(request: InteractiveEditorRequest, token: CancellationToken): ProviderResult; + + // eslint-disable-next-line local/vscode-dts-provider-naming + releaseInteractiveEditorSession?(session: S): any; + + // todo@API use enum instead of boolean + // eslint-disable-next-line local/vscode-dts-provider-naming + handleInteractiveEditorResponseFeedback?(session: S, response: R, kind: InteractiveEditorResponseFeedbackKind): void; + } + + + export interface InteractiveSessionState { } + + export interface InteractiveSessionParticipantInformation { + name: string; + + /** + * A full URI for the icon of the participant. + */ + icon?: Uri; + } + + export interface InteractiveSession { + requester: InteractiveSessionParticipantInformation; + responder: InteractiveSessionParticipantInformation; + inputPlaceholder?: string; + + saveState?(): InteractiveSessionState; + } + + export interface InteractiveSessionRequestArgs { + command: string; + args: any; + } + + export interface InteractiveRequest { + session: InteractiveSession; + message: string | InteractiveSessionReplyFollowup; + } + + export interface InteractiveResponseErrorDetails { + message: string; + responseIsIncomplete?: boolean; + responseIsFiltered?: boolean; + } + + export interface InteractiveResponseForProgress { + errorDetails?: InteractiveResponseErrorDetails; + } + + export interface InteractiveProgressContent { + content: string; + } + + export interface InteractiveProgressId { + responseId: string; + } + + export type InteractiveProgress = InteractiveProgressContent | InteractiveProgressId; + + export interface InteractiveResponseCommand { + commandId: string; + args?: any[]; + title: string; // supports codicon strings + } + + export interface InteractiveSessionSlashCommand { + command: string; + kind: CompletionItemKind; + detail?: string; + } + + export interface InteractiveSessionReplyFollowup { + message: string; + tooltip?: string; + title?: string; + + // Extensions can put any serializable data here, such as an ID/version + metadata?: any; + } + + export type InteractiveSessionFollowup = InteractiveSessionReplyFollowup | InteractiveResponseCommand; + + export type InteractiveWelcomeMessageContent = string | InteractiveSessionReplyFollowup[]; + + export interface InteractiveSessionProvider { + provideWelcomeMessage?(token: CancellationToken): ProviderResult; + provideFollowups?(session: S, token: CancellationToken): ProviderResult<(string | InteractiveSessionFollowup)[]>; + provideSlashCommands?(session: S, token: CancellationToken): ProviderResult; + + prepareSession(initialState: InteractiveSessionState | undefined, token: CancellationToken): ProviderResult; + resolveRequest(session: S, context: InteractiveSessionRequestArgs | string, token: CancellationToken): ProviderResult; + provideResponseWithProgress(request: InteractiveRequest, progress: Progress, token: CancellationToken): ProviderResult; + + // eslint-disable-next-line local/vscode-dts-provider-naming + removeRequest(session: S, requestId: string): void; + } + + export interface InteractiveSessionDynamicRequest { + /** + * The message that will be displayed in the UI + */ + message: string; + + /** + * Any extra metadata/context that will go to the provider. + * NOTE not actually used yet. + */ + metadata?: any; + } + + export namespace interactive { + // current version of the proposal. + export const _version: 1 | number; + + export function registerInteractiveSessionProvider(id: string, provider: InteractiveSessionProvider): Disposable; + export function addInteractiveRequest(context: InteractiveSessionRequestArgs): void; + + export function sendInteractiveRequestToProvider(providerId: string, message: InteractiveSessionDynamicRequest): void; + + export function registerInteractiveEditorSessionProvider(provider: InteractiveEditorSessionProvider): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.interactiveSlashCommands.d.ts b/src/vscode-dts/vscode.proposed.interactiveSlashCommands.d.ts new file mode 100644 index 0000000000..bdf42f0122 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.interactiveSlashCommands.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface InteractiveSlashCommandProvider { + provideSlashCommands(token: CancellationToken): ProviderResult; + resolveSlashCommand(command: string, token: CancellationToken): ProviderResult; + } + + export namespace interactiveSlashCommands { + export function registerSlashCommandProvider(chatProviderId: string, provider: InteractiveSlashCommandProvider): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts b/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts new file mode 100644 index 0000000000..33886379aa --- /dev/null +++ b/src/vscode-dts/vscode.proposed.interactiveUserActions.d.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export enum InteractiveSessionVoteDirection { + Up = 1, + Down = 2 + } + + export interface InteractiveSessionVoteAction { + // eslint-disable-next-line local/vscode-dts-string-type-literals + kind: 'vote'; + responseId: string; + direction: InteractiveSessionVoteDirection; + } + + export enum InteractiveSessionCopyKind { + // Keyboard shortcut or context menu + Action = 1, + Toolbar = 2 + } + + export interface InteractiveSessionCopyAction { + // eslint-disable-next-line local/vscode-dts-string-type-literals + kind: 'copy'; + responseId: string; + codeBlockIndex: number; + copyType: InteractiveSessionCopyKind; + copiedCharacters: number; + totalCharacters: number; + copiedText: string; + } + + export interface InteractiveSessionInsertAction { + // eslint-disable-next-line local/vscode-dts-string-type-literals + kind: 'insert'; + responseId: string; + codeBlockIndex: number; + totalCharacters: number; + newFile?: boolean; + } + + export interface InteractiveSessionTerminalAction { + // eslint-disable-next-line local/vscode-dts-string-type-literals + kind: 'runInTerminal'; + responseId: string; + codeBlockIndex: number; + languageId?: string; + } + + export interface InteractiveSessionCommandAction { + // eslint-disable-next-line local/vscode-dts-string-type-literals + kind: 'command'; + command: InteractiveResponseCommand; + } + + export type InteractiveSessionUserAction = InteractiveSessionVoteAction | InteractiveSessionCopyAction | InteractiveSessionInsertAction | InteractiveSessionTerminalAction | InteractiveSessionCommandAction; + + export interface InteractiveSessionUserActionEvent { + action: InteractiveSessionUserAction; + providerId: string; + } + + export namespace interactive { + export const onDidPerformUserAction: Event; + } +} diff --git a/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts b/src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts similarity index 60% rename from src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts rename to src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts index ea9afb8beb..5fc958cd4c 100644 --- a/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookCodeActions.d.ts @@ -5,11 +5,13 @@ declare module 'vscode' { - // https://github.com/microsoft/vscode/issues/145374 + // https://github.com/microsoft/vscode/issues/179213 - interface WorkspaceEdit { + export class NotebookCodeActionKind { + // can only return MULTI CELL workspaceEdits + // ex: notebook.organizeImprots + static readonly Notebook: CodeActionKind; - // todo@API have a SnippetTextEdit and allow to set that? - replace(uri: Uri, range: Range, newText: string | SnippetString, metadata?: WorkspaceEditEntryMetadata): void; + constructor(value: string); } } diff --git a/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts deleted file mode 100644 index 8ce1c53ef7..0000000000 --- a/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/147248 - - /** @deprecated */ - interface NotebookDocumentBackup { - /** - * Unique identifier for the backup. - * - * This id is passed back to your extension in `openNotebook` when opening a notebook editor from a backup. - */ - readonly id: string; - - /** - * Delete the current backup. - * - * This is called by the editor when it is clear the current backup is no longer needed, such as when a new backup - * is made or when the file is saved. - */ - delete(): void; - } - - /** @deprecated */ - interface NotebookDocumentBackupContext { - readonly destination: Uri; - } - - /** @deprecated */ - interface NotebookDocumentOpenContext { - readonly backupId?: string; - readonly untitledDocumentData?: Uint8Array; - } - - // todo@API use openNotebookDOCUMENT to align with openCustomDocument etc? - // todo@API rename to NotebookDocumentContentProvider - /** @deprecated */ - - export interface NotebookContentProvider { - - readonly options?: NotebookDocumentContentOptions; - readonly onDidChangeNotebookContentOptions?: Event; - - /** - * Content providers should always use {@link FileSystemProvider file system providers} to - * resolve the raw content for `uri` as the resource is not necessarily a file on disk. - */ - openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext, token: CancellationToken): NotebookData | Thenable; - - // todo@API use NotebookData instead - saveNotebook(document: NotebookDocument, token: CancellationToken): Thenable; - - // todo@API use NotebookData instead - saveNotebookAs(targetResource: Uri, document: NotebookDocument, token: CancellationToken): Thenable; - - // todo@API use NotebookData instead - backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, token: CancellationToken): Thenable; - } - - export namespace workspace { - - // TODO@api use NotebookDocumentFilter instead of just notebookType:string? - // TODO@API options duplicates the more powerful variant on NotebookContentProvider - /** @deprecated */ - export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions): Disposable; - } -} diff --git a/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts b/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts new file mode 100644 index 0000000000..4da3553954 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookControllerAffinityHidden.d.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + // https://github.com/microsoft/vscode/issues/161144 + export enum NotebookControllerAffinity2 { + Default = 1, + Preferred = 2, + Hidden = -1 + } + + export interface NotebookController { + updateNotebookAffinity(notebook: NotebookDocument, affinity: NotebookControllerAffinity | NotebookControllerAffinity2): void; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookDebugOptions.d.ts b/src/vscode-dts/vscode.proposed.notebookDebugOptions.d.ts deleted file mode 100644 index a82d77dc13..0000000000 --- a/src/vscode-dts/vscode.proposed.notebookDebugOptions.d.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // eslint-disable-next-line vscode-dts-region-comments - // @roblourens: debugUI.simple: https://github.com/microsoft/vscode/issues/147264. Used for Jupyter's Run By Line. - // suppressSaveBeforeStart: https://github.com/microsoft/vscode/issues/147263. Used to enable debugging untitled/unsaved notebooks. - - /** - * Options for {@link debug.startDebugging starting a debug session}. - */ - export interface DebugSessionOptions { - - debugUI?: { - /** - * When true, the debug toolbar will not be shown for this session, the window statusbar color will not be changed, and the debug viewlet will not be automatically revealed. - */ - simple?: boolean; - }; - - /** - * When true, a save will not be triggered for open editors when starting a debug session, regardless of the value of the `debug.saveBeforeStart` setting. - */ - suppressSaveBeforeStart?: boolean; - } -} diff --git a/src/vscode-dts/vscode.proposed.notebookEditor.d.ts b/src/vscode-dts/vscode.proposed.notebookEditor.d.ts deleted file mode 100644 index e15d68df29..0000000000 --- a/src/vscode-dts/vscode.proposed.notebookEditor.d.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/149271 - - // â—ï¸ Important: The main NotebookEditor api has been finalized. - // This file only contains deprecated properties/functions from the proposal. - - export interface NotebookEditor { - /** - * The document associated with this notebook editor. - * - * @deprecated Use {@linkcode NotebookEditor.notebook} instead. - */ - readonly document: NotebookDocument; - } - - export namespace window { - /** - * A short-hand for `openNotebookDocument(uri).then(document => showNotebookDocument(document, options))`. - * - * @deprecated Will not be finalized. - * - * @see {@link workspace.openNotebookDocument} - * - * @param uri The resource to open. - * @param options {@link NotebookDocumentShowOptions Editor options} to configure the behavior of showing the {@link NotebookEditor notebook editor}. - * - * @return A promise that resolves to an {@link NotebookEditor notebook editor}. - */ - export function showNotebookDocument(uri: Uri, options?: NotebookDocumentShowOptions): Thenable; - } -} diff --git a/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts b/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts deleted file mode 100644 index d6b2945a07..0000000000 --- a/src/vscode-dts/vscode.proposed.notebookEditorEdit.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/106744 - - export interface WorkspaceEdit { - replaceNotebookMetadata(uri: Uri, value: { [key: string]: any }): void; - - /** - * @deprecated Please migrate to the new `notebookWorkspaceEdit` proposed API. - */ - replaceNotebookCells(uri: Uri, range: NotebookRange, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void; - - /** - * @deprecated Please migrate to the new `notebookWorkspaceEdit` proposed API. - */ - replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: { [key: string]: any }, metadata?: WorkspaceEditEntryMetadata): void; - } - - export interface NotebookEditorEdit { - /** - * @deprecated Please migrate to the new `notebookWorkspaceEdit` proposed API. - */ - replaceMetadata(value: { [key: string]: any }): void; - - /** - * @deprecated Please migrate to the new `notebookWorkspaceEdit` proposed API. - */ - replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - - /** - * @deprecated Please migrate to the new `notebookWorkspaceEdit` proposed API. - */ - replaceCellMetadata(index: number, metadata: { [key: string]: any }): void; - } - - export interface NotebookEditor { - /** - * Perform an edit on the notebook associated with this notebook editor. - * - * The given callback-function is invoked with an {@link NotebookEditorEdit edit-builder} which must - * be used to make edits. Note that the edit-builder is only valid while the - * callback executes. - * - * @deprecated Please migrate to the new `notebookWorkspaceEdit` proposed API. - * - * @param callback A function which can create edits using an {@link NotebookEditorEdit edit-builder}. - * @return A promise that resolves with a value indicating if the edits could be applied. - */ - edit(callback: (editBuilder: NotebookEditorEdit) => void): Thenable; - } -} diff --git a/src/vscode-dts/vscode.proposed.notebookExecution.d.ts b/src/vscode-dts/vscode.proposed.notebookExecution.d.ts new file mode 100644 index 0000000000..819ab2d990 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.notebookExecution.d.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + /** + * A NotebookExecution is how {@link NotebookController notebook controller} can indicate whether the notebook controller is busy or not. + * + * When {@linkcode NotebookExecution.start start()} is called on the execution task, it causes the Notebook to enter a executing state . + * When {@linkcode NotebookExecution.end end()} is called, it enters the idle state. + */ + export interface NotebookExecution { + /** + * Signal that the execution has begun. + */ + start(): void; + + /** + * Signal that execution has ended. + */ + end(): void; + } + + export interface NotebookController { + /** + * Create an execution task. + * + * _Note_ that there can only be one execution per Notebook, that also includes NotebookCellExecutions and t an error is thrown if + * a cell execution or another NotebookExecution is created while another is still active. + * + * This should be used to indicate the {@link NotebookController notebook controller} is busy even though user may not have executed any cell though the UI. + * @param {NotebookDocument} notebook + * @returns A notebook execution. + */ + createNotebookExecution(notebook: NotebookDocument): NotebookExecution; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts index f711cb84dc..88b0eddd16 100644 --- a/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookKernelSource.d.ts @@ -4,4 +4,43 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { + export interface NotebookControllerDetectionTask { + /** + * Dispose and remove the detection task. + */ + dispose(): void; + } + + export class NotebookKernelSourceAction { + readonly label: string; + readonly description?: string; + readonly detail?: string; + readonly command: string | Command; + readonly documentation?: Uri; + + constructor(label: string); + } + + export interface NotebookKernelSourceActionProvider { + /** + * An optional event to signal that the kernel source actions have changed. + */ + onDidChangeNotebookKernelSourceActions?: Event; + /** + * Provide kernel source actions + */ + provideNotebookKernelSourceActions(token: CancellationToken): ProviderResult; + } + + export namespace notebooks { + /** + * Create notebook controller detection task + */ + export function createNotebookControllerDetectionTask(notebookType: string): NotebookControllerDetectionTask; + + /** + * Register a notebook kernel source action provider + */ + export function registerKernelSourceActionProvider(notebookType: string, provider: NotebookKernelSourceActionProvider): Disposable; + } } diff --git a/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts b/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts index 33c5f0a3ab..5049a53705 100644 --- a/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts @@ -8,14 +8,13 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/106744 export interface NotebookRegistrationData { - displayName: string; - filenamePattern: (GlobPattern | { include: GlobPattern; exclude: GlobPattern })[]; - exclusive?: boolean; + readonly displayName: string; + readonly filenamePattern: ReadonlyArray<(GlobPattern | { readonly include: GlobPattern; readonly exclude: GlobPattern })>; + readonly exclusive?: boolean; } export namespace workspace { - // SPECIAL overload with NotebookRegistrationData - export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions, registrationData?: NotebookRegistrationData): Disposable; + // SPECIAL overload with NotebookRegistrationData export function registerNotebookSerializer(notebookType: string, serializer: NotebookSerializer, options?: NotebookDocumentContentOptions, registration?: NotebookRegistrationData): Disposable; } diff --git a/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts b/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts index 82ac443cdd..981a522948 100644 --- a/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts +++ b/src/vscode-dts/vscode.proposed.notebookMessaging.d.ts @@ -18,7 +18,7 @@ declare module 'vscode' { * against the `dependencies` and `optionalDependencies` arrays in the * notebook renderer contribution point. */ - provides: string[]; + provides: readonly string[]; /** * URI of the JavaScript module to preload. @@ -31,7 +31,7 @@ declare module 'vscode' { * @param uri URI of the JavaScript module to preload * @param provides Value for the `provides` property */ - constructor(uri: Uri, provides?: string | string[]); + constructor(uri: Uri, provides?: string | readonly string[]); } export interface NotebookController { @@ -43,7 +43,7 @@ declare module 'vscode' { * An event that fires when a {@link NotebookController.rendererScripts renderer script} has send a message to * the controller. */ - readonly onDidReceiveMessage: Event<{ editor: NotebookEditor; message: any }>; + readonly onDidReceiveMessage: Event<{ readonly editor: NotebookEditor; readonly message: any }>; /** * Send a message to the renderer of notebook editors. diff --git a/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts b/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts deleted file mode 100644 index f14ca77ac0..0000000000 --- a/src/vscode-dts/vscode.proposed.notebookWorkspaceEdit.d.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/106744 - - /** - * A notebook edit represents edits that should be applied to the contents of a notebook. - */ - export class NotebookEdit { - - /** - * Utility to create a edit that replaces cells in a notebook. - * - * @param range The range of cells to replace - * @param newCells The new notebook cells. - */ - static replaceCells(range: NotebookRange, newCells: NotebookCellData[]): NotebookEdit; - - /** - * Utility to create an edit that replaces cells in a notebook. - * - * @param index The index to insert cells at. - * @param newCells The new notebook cells. - */ - static insertCells(index: number, newCells: NotebookCellData[]): NotebookEdit; - - /** - * Utility to create an edit that deletes cells in a notebook. - * - * @param range The range of cells to delete. - */ - static deleteCells(range: NotebookRange): NotebookEdit; - - /** - * Utility to create an edit that update a cell's metadata. - * - * @param index The index of the cell to update. - * @param newCellMetadata The new metadata for the cell. - */ - static updateCellMetadata(index: number, newCellMetadata: { [key: string]: any }): NotebookEdit; - - /** - * Utility to create an edit that updates the notebook's metadata. - * - * @param newNotebookMetadata The new metadata for the notebook. - */ - static updateNotebookMetadata(newNotebookMetadata: { [key: string]: any }): NotebookEdit; - - /** - * Range of the cells being edited. May be empty. - */ - range: NotebookRange; - - /** - * New cells being inserted. May be empty. - */ - newCells: NotebookCellData[]; - - /** - * Optional new metadata for the cells. - */ - newCellMetadata?: { [key: string]: any }; - - /** - * Optional new metadata for the notebook. - */ - newNotebookMetadata?: { [key: string]: any }; - - constructor(range: NotebookRange, newCells: NotebookCellData[]); - } - - export interface WorkspaceEdit { - /** - * Set (and replace) edits for a resource. - * - * @param uri A resource identifier. - * @param edits An array of text or notebook edits. - */ - set(uri: Uri, edits: TextEdit[] | NotebookEdit[]): void; - } -} diff --git a/src/vscode-dts/vscode.proposed.portsAttributes.d.ts b/src/vscode-dts/vscode.proposed.portsAttributes.d.ts index 28d8d7576f..9bc1153adf 100644 --- a/src/vscode-dts/vscode.proposed.portsAttributes.d.ts +++ b/src/vscode-dts/vscode.proposed.portsAttributes.d.ts @@ -7,21 +7,40 @@ declare module 'vscode' { // https://github.com/microsoft/vscode/issues/115616 @alexr00 + /** + * The action that should be taken when a port is discovered through automatic port forwarding discovery. + */ export enum PortAutoForwardAction { + /** + * Notify the user that the port is being forwarded. This is the default action. + */ Notify = 1, + /** + * Once the port is forwarded, open the browser to the forwarded port. + */ OpenBrowser = 2, + /** + * Once the port is forwarded, open the preview browser to the forwarded port. + */ OpenPreview = 3, + /** + * Forward the port silently. + */ Silent = 4, + /** + * Do not forward the port. + */ Ignore = 5, + /** + * Once the port is forwarded, open the browser to the forwarded port. Only open the browser the first time the port is forwarded in a session. + */ OpenBrowserOnce = 6 } + /** + * The attributes that a forwarded port can have. + */ export class PortAttributes { - /** - * The port number associated with this this set of attributes. - */ - port: number; - /** * The action to be taken when this port is detected for auto forwarding. */ @@ -32,9 +51,12 @@ declare module 'vscode' { * @param port the port number * @param autoForwardAction the action to take when this port is detected */ - constructor(port: number, autoForwardAction: PortAutoForwardAction); + constructor(autoForwardAction: PortAutoForwardAction); } + /** + * A provider of port attributes. Port attributes are used to determine what action should be taken when a port is discovered. + */ export interface PortAttributesProvider { /** * Provides attributes for the given port. For ports that your extension doesn't know about, simply @@ -44,6 +66,21 @@ declare module 'vscode' { providePortAttributes(port: number, pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult; } + /** + * A selector that will be used to filter which {@link PortAttributesProvider} should be called for each port. + */ + export interface PortAttributesSelector { + /** + * Specifying a port range will cause your provider to only be called for ports within the range. + */ + portRange?: [number, number]; + + /** + * Specifying a command pattern will cause your provider to only be called for processes whose command line matches the pattern. + */ + commandPattern?: RegExp; + } + export namespace workspace { /** * If your extension listens on ports, consider registering a PortAttributesProvider to provide information @@ -51,12 +88,12 @@ declare module 'vscode' { * this information with a PortAttributesProvider the extension can tell the editor that these ports should be * ignored, since they don't need to be user facing. * - * @param portSelector If registerPortAttributesProvider is called after you start your process then you may already - * know the range of ports or the pid of your process. All properties of a the portSelector must be true for your - * provider to get called. - * The `portRange` is start inclusive and end exclusive. - * @param provider The PortAttributesProvider + * The results of the PortAttributesProvider are merged with the user setting `remote.portsAttributes`. If the values conflict, the user setting takes precedence. + * + * @param portSelector It is best practice to specify a port selector to avoid unnecessary calls to your provider. + * If you don't specify a port selector your provider will be called for every port, which will result in slower port forwarding for the user. + * @param provider The {@link PortAttributesProvider PortAttributesProvider}. */ - export function registerPortAttributesProvider(portSelector: { pid?: number; portRange?: [number, number]; commandMatcher?: RegExp }, provider: PortAttributesProvider): Disposable; + export function registerPortAttributesProvider(portSelector: PortAttributesSelector, provider: PortAttributesProvider): Disposable; } } diff --git a/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts b/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts new file mode 100644 index 0000000000..99f5a87e02 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.profileContentHandlers.d.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface ProfileContentHandler { + readonly name: string; + readonly description?: string; + saveProfile(name: string, content: string, token: CancellationToken): Thenable<{ readonly id: string; readonly link: Uri } | null>; + readProfile(idOrUri: string | Uri, token: CancellationToken): Thenable; + } + + export namespace window { + export function registerProfileContentHandler(id: string, profileContentHandler: ProfileContentHandler): Disposable; + } + +} diff --git a/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts b/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts new file mode 100644 index 0000000000..d09be5243d --- /dev/null +++ b/src/vscode-dts/vscode.proposed.quickDiffProvider.d.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/169012 + + export namespace window { + export function registerQuickDiffProvider(selector: DocumentSelector, quickDiffProvider: QuickDiffProvider, label: string, rootUri?: Uri): Disposable; + } + + interface QuickDiffProvider { + label?: string; + } +} diff --git a/src/vscode-dts/vscode.proposed.notebookControllerKind.d.ts b/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts similarity index 68% rename from src/vscode-dts/vscode.proposed.notebookControllerKind.d.ts rename to src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts index f7be18ee49..8805607097 100644 --- a/src/vscode-dts/vscode.proposed.notebookControllerKind.d.ts +++ b/src/vscode-dts/vscode.proposed.quickPickItemTooltip.d.ts @@ -5,12 +5,12 @@ declare module 'vscode' { - // https://github.com/microsoft/vscode-jupyter/issues/7373 + // https://github.com/microsoft/vscode/issues/175662 - export interface NotebookController { + export interface QuickPickItem { /** - * The human-readable label used to categorise controllers. + * A tooltip that is rendered when hovering over the item. */ - kind?: string; + tooltip?: string | MarkdownString; } } diff --git a/src/vscode-dts/vscode.proposed.resolvers.d.ts b/src/vscode-dts/vscode.proposed.resolvers.d.ts index bb46c2c423..99e1cfbf5f 100644 --- a/src/vscode-dts/vscode.proposed.resolvers.d.ts +++ b/src/vscode-dts/vscode.proposed.resolvers.d.ts @@ -26,6 +26,23 @@ declare module 'vscode' { constructor(host: string, port: number, connectionToken?: string); } + export interface ManagedMessagePassing { + onDidReceiveMessage: Event; + onDidClose: Event; + onDidEnd: Event; + + send: (data: Uint8Array) => void; + end: () => void; + drain?: () => Thenable; + } + + export class ManagedResolvedAuthority { + readonly makeConnection: () => Thenable; + readonly connectionToken: string | undefined; + + constructor(makeConnection: () => Thenable, connectionToken?: string); + } + export interface ResolvedOptions { extensionHostEnv?: { [key: string]: string | null }; @@ -43,7 +60,14 @@ declare module 'vscode' { label: string; } - export interface TunnelOptions { + export namespace env { + /** Quality of the application. May be undefined if running from sources. */ + export const appQuality: string | undefined; + /** Commit of the application. May be undefined if running from sources. */ + export const appCommit: string | undefined; + } + + interface TunnelOptions { remoteAddress: { port: number; host: string }; // The desired local port. If this port can't be used, then another will be chosen. localAddressPort?: number; @@ -56,7 +80,7 @@ declare module 'vscode' { protocol?: string; } - export interface TunnelDescription { + interface TunnelDescription { remoteAddress: { port: number; host: string }; //The complete local address(ex. localhost:1234) localAddress: { port: number; host: string } | string; @@ -69,7 +93,7 @@ declare module 'vscode' { protocol?: string; } - export interface Tunnel extends TunnelDescription { + interface Tunnel extends TunnelDescription { // Implementers of Tunnel should fire onDidDispose when dispose is called. onDidDispose: Event; dispose(): void | Thenable; @@ -109,7 +133,7 @@ declare module 'vscode' { Output = 2 } - export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; + export type ResolverResult = (ResolvedAuthority | ManagedResolvedAuthority) & ResolvedOptions & TunnelInformation; export class RemoteAuthorityResolverError extends Error { static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError; @@ -164,29 +188,6 @@ declare module 'vscode' { candidatePortSource?: CandidatePortSource; } - export namespace workspace { - /** - * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. - * By default, openTunnel only support localhost; however, RemoteAuthorityResolver:tunnelFactory can be used to support other ips. - * - * @throws When run in an environment without a remote. - * - * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. - */ - export function openTunnel(tunnelOptions: TunnelOptions): Thenable; - - /** - * Gets an array of the currently available tunnels. This does not include environment tunnels, only tunnels that have been created by the user. - * Note that these are of type TunnelDescription and cannot be disposed. - */ - export let tunnels: Thenable; - - /** - * Fired when the list of tunnels has changed. - */ - export const onDidChangeTunnels: Event; - } - export interface ResourceLabelFormatter { scheme: string; authority?: string; @@ -196,7 +197,7 @@ declare module 'vscode' { export interface ResourceLabelFormatting { label: string; // myLabel:/${path} // For historic reasons we use an or string here. Once we finalize this API we should start using enums instead and adopt it in extensions. - // eslint-disable-next-line vscode-dts-literal-or-types + // eslint-disable-next-line local/vscode-dts-literal-or-types, local/vscode-dts-string-type-literals separator: '/' | '\\' | ''; tildify?: boolean; normalizeDriveLetter?: boolean; diff --git a/src/vscode-dts/vscode.proposed.saveEditor.d.ts b/src/vscode-dts/vscode.proposed.saveEditor.d.ts new file mode 100644 index 0000000000..b60e6d3a73 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.saveEditor.d.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/178713 + +declare module 'vscode' { + + export namespace workspace { + + /** + * Saves the editor identified by the given resource and returns the resulting resource or `undefined` + * if save was not successful or no editor with the given resource was found. + * + * **Note** that an editor with the provided resource must be opened in order to be saved. + * + * @param uri the associated uri for the opened editor to save. + * @return A thenable that resolves when the save operation has finished. + */ + export function save(uri: Uri): Thenable; + + /** + * Saves the editor identified by the given resource to a new file name as provided by the user and + * returns the resulting resource or `undefined` if save was not successful or cancelled or no editor + * with the given resource was found. + * + * **Note** that an editor with the provided resource must be opened in order to be saved as. + * + * @param uri the associated uri for the opened editor to save as. + * @return A thenable that resolves when the save-as operation has finished. + */ + export function saveAs(uri: Uri): Thenable; + } +} diff --git a/src/vscode-dts/vscode.proposed.commentsResolvedState.d.ts b/src/vscode-dts/vscode.proposed.scmTextDocument.d.ts similarity index 56% rename from src/vscode-dts/vscode.proposed.commentsResolvedState.d.ts rename to src/vscode-dts/vscode.proposed.scmTextDocument.d.ts index 4209a24c31..60b83750c1 100644 --- a/src/vscode-dts/vscode.proposed.commentsResolvedState.d.ts +++ b/src/vscode-dts/vscode.proposed.scmTextDocument.d.ts @@ -4,21 +4,16 @@ *--------------------------------------------------------------------------------------------*/ declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/127473 + // https://github.com/microsoft/vscode/issues/166615 /** - * The state of a comment thread. + * Represents the input box in the Source Control viewlet. */ - export enum CommentThreadState { - Unresolved = 0, - Resolved = 1 - } + export interface SourceControlInputBox { - export interface CommentThread { /** - * The optional state of a comment thread, which may affect how the comment is displayed. + * The {@link TextDocument text} of the input box. */ - state?: CommentThreadState; + readonly document: TextDocument; } } diff --git a/src/vscode-dts/vscode.proposed.semanticSimilarity.d.ts b/src/vscode-dts/vscode.proposed.semanticSimilarity.d.ts new file mode 100644 index 0000000000..b9d9cc3591 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.semanticSimilarity.d.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + export interface SemanticSimilarityProvider { + /** + * Computes the semantic similarity score between two strings. + * @param string1 The string to compare to all other strings. + * @param comparisons An array of strings to compare string1 to. An array allows you to batch multiple comparisons in one call. + * @param token A cancellation token. + * @return A promise that resolves to the semantic similarity scores between string1 and each string in comparisons. + * The score should be a number between 0 and 1, where 0 means no similarity and 1 means + * perfect similarity. + */ + provideSimilarityScore(string1: string, comparisons: string[], token: CancellationToken): Thenable; + } + export namespace ai { + export function registerSemanticSimilarityProvider(provider: SemanticSimilarityProvider): Disposable; + } +} diff --git a/src/vscode-dts/vscode.proposed.shareProvider.d.ts b/src/vscode-dts/vscode.proposed.shareProvider.d.ts new file mode 100644 index 0000000000..e8c21b6cce --- /dev/null +++ b/src/vscode-dts/vscode.proposed.shareProvider.d.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/176316 @joyceerhl + +declare module 'vscode' { + + /** + * Data about an item which can be shared. + */ + export interface ShareableItem { + /** + * A resource in the workspace that can be shared. + */ + resourceUri: Uri; + + /** + * If present, a selection within the `resourceUri`. + */ + selection?: Range; + } + + /** + * A provider which generates share links for resources in the editor. + */ + export interface ShareProvider { + + /** + * A unique ID for the provider. + * This will be used to activate specific extensions contributing share providers if necessary. + */ + readonly id: string; + + /** + * A label which will be used to present this provider's options in the UI. + */ + readonly label: string; + + /** + * The order in which the provider should be listed in the UI when there are multiple providers. + */ + readonly priority: number; + + /** + * + * @param item Data about an item which can be shared. + * @param token A cancellation token. + * @returns An {@link Uri} which will be copied to the user's clipboard and presented in a confirmation dialog. + */ + provideShare(item: ShareableItem, token: CancellationToken): ProviderResult; + } + + export namespace window { + + /** + * Register a share provider. An extension may register multiple share providers. + * There may be multiple share providers for the same {@link ShareableItem}. + * @param selector A document selector to filter whether the provider should be shown for a {@link ShareableItem}. + * @param provider A share provider. + */ + export function registerShareProvider(selector: DocumentSelector, provider: ShareProvider): Disposable; + } + + export interface TreeItem { + + /** + * An optional property which, when set, inlines a `Share` option in the context menu for this tree item. + */ + shareableItem?: ShareableItem; + } +} diff --git a/src/vscode-dts/vscode.proposed.showLocal.d.ts b/src/vscode-dts/vscode.proposed.showLocal.d.ts new file mode 100644 index 0000000000..e3cf920862 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.showLocal.d.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/131138 + +declare module 'vscode' { + + export interface OpenDialogOptions { + /** + * Controls whether the dialog allows users to select local files via the "Show Local" button. + * Extensions that set this to `true` should check the scheme of the selected file. + * Resources with the `file` scheme come from the same extension host as the extension. + * Resources with the `vscode-local` scheme come from an extension host running in the same place as the UI. + */ + allowUIResources?: boolean; + } +} diff --git a/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts b/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts index 9632754960..f21c17e847 100644 --- a/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts +++ b/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts @@ -12,10 +12,5 @@ declare module 'vscode' { * Controls whether the task is executed in a specific terminal group using split panes. */ group?: string; - - /** - * Controls whether the terminal is closed after executing the task. - */ - close?: boolean; } } diff --git a/src/vscode-dts/vscode.proposed.telemetry.d.ts b/src/vscode-dts/vscode.proposed.telemetry.d.ts index 2be9d94ca3..a12dbb7b56 100644 --- a/src/vscode-dts/vscode.proposed.telemetry.d.ts +++ b/src/vscode-dts/vscode.proposed.telemetry.d.ts @@ -9,15 +9,15 @@ declare module 'vscode' { /** * Whether or not usage telemetry collection is allowed */ - isUsageEnabled: boolean; + readonly isUsageEnabled: boolean; /** * Whether or not crash error telemetry collection is allowed */ - isErrorsEnabled: boolean; + readonly isErrorsEnabled: boolean; /** * Whether or not crash report collection is allowed */ - isCrashEnabled: boolean; + readonly isCrashEnabled: boolean; } export namespace env { diff --git a/src/vscode-dts/vscode.proposed.terminalExitReason.d.ts b/src/vscode-dts/vscode.proposed.terminalExitReason.d.ts deleted file mode 100644 index 1a915eef19..0000000000 --- a/src/vscode-dts/vscode.proposed.terminalExitReason.d.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'vscode' { - - // https://github.com/microsoft/vscode/issues/130231 - - /** - * Terminal exit reason kind. - */ - export enum TerminalExitReason { - /** - * Unknown reason. - */ - Unknown = 0, - - /** - * The window closed/reloaded. - */ - Shutdown = 1, - - /** - * The shell process exited. - */ - Process = 2, - - /** - * The user closed the terminal. - */ - User = 3, - - /** - * An extension disposed the terminal. - */ - Extension = 4, - } - - export interface TerminalExitStatus { - /** - * The reason that triggered the exit of a terminal. - */ - readonly reason: TerminalExitReason; - } - -} diff --git a/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts b/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts new file mode 100644 index 0000000000..d6d5fa9ea7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts @@ -0,0 +1,84 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface TerminalQuickFixProvider { + /** + * Provides terminal quick fixes + * @param commandMatchResult The command match result for which to provide quick fixes + * @param token A cancellation token indicating the result is no longer needed + * @return Terminal quick fix(es) if any + */ + provideTerminalQuickFixes(commandMatchResult: TerminalCommandMatchResult, token: CancellationToken): ProviderResult<(TerminalQuickFixCommand | TerminalQuickFixOpener)[] | TerminalQuickFixCommand | TerminalQuickFixOpener>; + } + + + export interface TerminalCommandMatchResult { + commandLine: string; + commandLineMatch: RegExpMatchArray; + outputMatch?: { + regexMatch: RegExpMatchArray; + outputLines: string[]; + }; + } + + export namespace window { + /** + * @param provider A terminal quick fix provider + * @return A {@link Disposable} that unregisters the provider when being disposed + */ + export function registerTerminalQuickFixProvider(id: string, provider: TerminalQuickFixProvider): Disposable; + } + + export class TerminalQuickFixCommand { + /** + * The terminal command to run + */ + terminalCommand: string; + constructor(terminalCommand: string); + } + export class TerminalQuickFixOpener { + /** + * The uri to open + */ + uri: Uri; + constructor(uri: Uri); + } + + /** + * A matcher that runs on a sub-section of a terminal command's output + */ + interface TerminalOutputMatcher { + /** + * A string or regex to match against the unwrapped line. If this is a regex with the multiline + * flag, it will scan an amount of lines equal to `\n` instances in the regex + 1. + */ + lineMatcher: string | RegExp; + /** + * Which side of the output to anchor the {@link offset} and {@link length} against. + */ + anchor: TerminalOutputAnchor; + /** + * The number of rows above or below the {@link anchor} to start matching against. + */ + offset: number; + /** + * The number of wrapped lines to match against, this should be as small as possible for performance + * reasons. This is capped at 40. + */ + length: number; + } + + enum TerminalOutputAnchor { + Top = 0, + Bottom = 1 + } + + enum TerminalQuickFixType { + Command = 0, + Opener = 1 + } +} diff --git a/src/vscode-dts/vscode.proposed.testInvalidateResults.d.ts b/src/vscode-dts/vscode.proposed.testInvalidateResults.d.ts new file mode 100644 index 0000000000..7a3e75cb13 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.testInvalidateResults.d.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// https://github.com/microsoft/vscode/issues/134970 + +declare module 'vscode' { + + export interface TestController { + + /** + * Marks an item's results as being outdated. This is commonly called when + * code or configuration changes and previous results should no longer + * be considered relevant. The same logic used to mark results as outdated + * may be used to drive {@link TestRunRequest.continuous continuous test runs}. + * + * If an item is passed to this method, test results for the item and all of + * its children will be marked as outdated. If no item is passed, then all + * test owned by the TestController will be marked as outdated. + * + * Any test runs started before the moment this method is called, including + * runs which may still be ongoing, will be marked as outdated and deprioritized + * in the editor's UI. + * + * @param item Item to mark as outdated. If undefined, all the controller's items are marked outdated. + */ + invalidateTestResults(items?: TestItem | readonly TestItem[]): void; + } +} diff --git a/src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts b/src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts new file mode 100644 index 0000000000..2d058fa9a9 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export class TreeItem2 extends TreeItem { + /** + * {@link TreeItemCheckboxState TreeItemCheckboxState} of the tree item. + * {@link TreeDataProvider.onDidChangeTreeData onDidChangeTreeData} should be fired when {@link TreeItem2.checkboxState checkboxState} changes. + */ + checkboxState?: TreeItemCheckboxState | { readonly state: TreeItemCheckboxState; readonly tooltip?: string; readonly accessibilityInformation?: AccessibilityInformation }; + } + + /** + * Checkbox state of the tree item + */ + export enum TreeItemCheckboxState { + /** + * Determines an item is unchecked + */ + Unchecked = 0, + /** + * Determines an item is checked + */ + Checked = 1 + } + + /** + * A data provider that provides tree data + */ + export interface TreeView { + /** + * An event to signal that an element or root has either been checked or unchecked. + */ + onDidChangeCheckboxState: Event>; + } + + export interface TreeCheckboxChangeEvent { + /** + * The items that were checked or unchecked. + */ + readonly items: ReadonlyArray<[T, TreeItemCheckboxState]>; + } + + /** + * Options for creating a {@link TreeView} + */ + export interface TreeViewOptions { + /** + * By default, when the children of a tree item have already been fetched, child checkboxes are automatically managed based on the checked state of the parent tree item. + * If the tree item is collapsed by default (meaning that the children haven't yet been fetched) then child checkboxes will not be updated. + * To override this behavior and manage child and parent checkbox state in the extension, set this to `true`. + */ + manuallyManageCheckboxSelection?: boolean; + } +} diff --git a/src/vscode-dts/vscode.proposed.tunnels.d.ts b/src/vscode-dts/vscode.proposed.tunnels.d.ts new file mode 100644 index 0000000000..84c75a2b09 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.tunnels.d.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // tunnels @alexr00 + + export interface TunnelOptions { + remoteAddress: { port: number; host: string }; + // The desired local port. If this port can't be used, then another will be chosen. + localAddressPort?: number; + label?: string; + /** + * @deprecated Use privacy instead + */ + public?: boolean; + privacy?: string; + protocol?: string; + } + + export interface TunnelDescription { + remoteAddress: { port: number; host: string }; + //The complete local address(ex. localhost:1234) + localAddress: { port: number; host: string } | string; + /** + * @deprecated Use privacy instead + */ + public?: boolean; + privacy?: string; + // If protocol is not provided it is assumed to be http, regardless of the localAddress. + protocol?: string; + } + + export interface Tunnel extends TunnelDescription { + // Implementers of Tunnel should fire onDidDispose when dispose is called. + onDidDispose: Event; + dispose(): void | Thenable; + } + + export namespace workspace { + /** + * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. + * By default, openTunnel only support localhost; however, RemoteAuthorityResolver:tunnelFactory can be used to support other ips. + * + * @throws When run in an environment without a remote. + * + * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. + */ + export function openTunnel(tunnelOptions: TunnelOptions): Thenable; + + /** + * Gets an array of the currently available tunnels. This does not include environment tunnels, only tunnels that have been created by the user. + * Note that these are of type TunnelDescription and cannot be disposed. + */ + export let tunnels: Thenable; + + /** + * Fired when the list of tunnels has changed. + */ + export const onDidChangeTunnels: Event; + } +} diff --git a/src/vscode-dts/vscode.proposed.windowActivity.d.ts b/src/vscode-dts/vscode.proposed.windowActivity.d.ts new file mode 100644 index 0000000000..7185ba2b07 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.windowActivity.d.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/181569 @connor4312 + + export interface WindowState { + /** + * Indicates whether the window has been interacted with recently. This will + * change immediately on activity, or after a short time of user inactivity. + */ + readonly active: boolean; + } +} diff --git a/test/automation/package.json b/test/automation/package.json index 9639729816..4ff47338ba 100644 --- a/test/automation/package.json +++ b/test/automation/package.json @@ -1,6 +1,6 @@ { "name": "vscode-automation", - "version": "1.54.0", + "version": "1.71.0", "description": "VS Code UI automation driver", "author": { "name": "Microsoft Corporation" diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index f351e1f8dd..e9ebc9854d 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -69,7 +69,7 @@ export class Application { } async restart(options?: { workspaceOrFolder?: string; extraArgs?: string[] }): Promise { - await measureAndLog((async () => { + await measureAndLog(() => (async () => { await this.stop(); await this._start(options?.workspaceOrFolder, options?.extraArgs); })(), 'Application#restart()', this.logger); @@ -82,7 +82,7 @@ export class Application { const code = await this.startApplication(extraArgs); // ...and make sure the window is ready to interact - await measureAndLog(this.checkWindowReady(code), 'Application#checkWindowReady()', this.logger); + await measureAndLog(() => this.checkWindowReady(code), 'Application#checkWindowReady()', this.logger); } async stop(): Promise { @@ -116,7 +116,8 @@ export class Application { private async checkWindowReady(code: Code): Promise { // We need a rendered workbench - await measureAndLog(code.waitForElement('.monaco-workbench'), 'Application#checkWindowReady: wait for .monaco-workbench element', this.logger); + await measureAndLog(() => code.didFinishLoad(), 'Application#checkWindowReady: wait for navigation to be committed', this.logger); + await measureAndLog(() => code.waitForElement('.monaco-workbench'), 'Application#checkWindowReady: wait for .monaco-workbench element', this.logger); // {{SQL CARBON EDIT}} Wait for specified status bar items before considering the app ready - we wait for them together to avoid timing @@ -124,7 +125,7 @@ export class Application { const statusbarPromises: Promise[] = []; if (this.remote) { - await measureAndLog(code.waitForTextContent('.monaco-workbench .statusbar-item[id="status.host"]', undefined, statusHostLabel => { + await measureAndLog(() => code.waitForTextContent('.monaco-workbench .statusbar-item[id="status.host"]', undefined, statusHostLabel => { this.logger.log(`checkWindowReady: remote indicator text is ${statusHostLabel}`); // The absence of "Opening Remote" is not a strict diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index 8d38572fc9..dc7cbd29db 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -3,15 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { join } from 'path'; import * as os from 'os'; -import * as cp from 'child_process'; -import { IElement, ILocalizedStrings, ILocaleInfo } from './driver'; -import { launch as launchPlaywrightBrowser } from './playwrightBrowser'; -import { launch as launchPlaywrightElectron } from './playwrightElectron'; -import { Logger, measureAndLog } from './logger'; import * as treekill from 'tree-kill'; -import { teardown } from './processes'; +import { IElement, ILocaleInfo, ILocalizedStrings, ILogFile } from './driver'; +import { Logger, measureAndLog } from './logger'; +import { launch as launchPlaywrightBrowser } from './playwrightBrowser'; import { PlaywrightDriver } from './playwrightDriver'; +import { launch as launchPlaywrightElectron } from './playwrightElectron'; +import { teardown } from './processes'; export interface LaunchOptions { codePath?: string; @@ -71,10 +71,9 @@ export async function launch(options: LaunchOptions): Promise { if (stopped) { throw new Error('Smoke test process has terminated, refusing to spawn Code'); } - // Browser smoke tests if (options.web) { - const { serverProcess, driver } = await measureAndLog(launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger); + const { serverProcess, driver } = await measureAndLog(() => launchPlaywrightBrowser(options), 'launch playwright (browser)', options.logger); registerInstance(serverProcess, options.logger, 'server'); return new Code(driver, options.logger, serverProcess); @@ -82,7 +81,7 @@ export async function launch(options: LaunchOptions): Promise { // Electron smoke tests (playwright) else { - const { electronProcess, driver } = await measureAndLog(launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger); + const { electronProcess, driver } = await measureAndLog(() => launchPlaywrightElectron(options), 'launch playwright (electron)', options.logger); registerInstance(electronProcess, options.logger, 'electron'); return new Code(driver, options.logger, electronProcess); @@ -129,8 +128,12 @@ export class Code { await this.driver.dispatchKeybinding(keybinding); } + async didFinishLoad(): Promise { + return this.driver.didFinishLoad(); + } + async exit(): Promise { - return measureAndLog(new Promise((resolve, reject) => { + return measureAndLog(() => new Promise(resolve => { const pid = this.mainProcess.pid!; let done = false; @@ -144,18 +147,39 @@ export class Code { while (!done) { retries++; - if (retries === 20) { - this.logger.log('Smoke test exit call did not terminate process after 10s, forcefully exiting the application...'); + switch (retries) { - // no need to await since we're polling for the process to die anyways - treekill(pid, err => { - try { - process.kill(pid, 0); // throws an exception if the process doesn't exist anymore - this.logger.log('Failed to kill Electron process tree:', err?.message); - } catch (error) { - // Expected when process is gone - } - }); + // after 5 / 10 seconds: try to exit gracefully again + case 10: + case 20: { + this.logger.log('Smoke test exit call did not terminate process after 5-10s, gracefully trying to exit the application again...'); + this.driver.exitApplication(); + break; + } + + // after 20 seconds: forcefully kill + case 40: { + this.logger.log('Smoke test exit call did not terminate process after 20s, forcefully exiting the application...'); + + // no need to await since we're polling for the process to die anyways + treekill(pid, err => { + try { + process.kill(pid, 0); // throws an exception if the process doesn't exist anymore + this.logger.log('Failed to kill Electron process tree:', err?.message); + } catch (error) { + // Expected when process is gone + } + }); + + break; + } + + // after 30 seconds: give up + case 60: { + done = true; + this.logger.log('Smoke test exit call did not terminate process after 30s, giving up'); + resolve(); + } } try { @@ -165,12 +189,6 @@ export class Code { done = true; resolve(); } - - if (retries === 60) { - done = true; - this.logger.log('Smoke test exit call did not terminate process after 30s, giving up'); - resolve(); - } } })(); }), 'Code#exit()', this.logger); @@ -232,14 +250,18 @@ export class Code { await this.poll(() => this.driver.writeInTerminal(selector, value), () => true, `writeInTerminal '${selector}'`); } - async getLocaleInfo(): Promise { + getLocaleInfo(): Promise { return this.driver.getLocaleInfo(); } - async getLocalizedStrings(): Promise { + getLocalizedStrings(): Promise { return this.driver.getLocalizedStrings(); } + getLogs(): Promise { + return this.driver.getLogs(); + } + private async poll( fn: () => Promise, acceptFn: (result: T) => boolean, diff --git a/test/automation/src/electron.ts b/test/automation/src/electron.ts index 42259f5a0f..6201f82e05 100644 --- a/test/automation/src/electron.ts +++ b/test/automation/src/electron.ts @@ -49,13 +49,22 @@ export async function resolveElectronConfiguration(options: LaunchOptions): Prom args.push('--disable-dev-shm-usage'); } + if (process.platform === 'darwin') { + // On macOS force software based rendering since we are seeing GPU process + // hangs when initializing GL context. This is very likely possible + // that there are new displays available in the CI hardware and + // the relevant drivers couldn't be loaded via the GPU sandbox. + // TODO(deepak1556): remove this switch with Electron update. + args.push('--use-gl=swiftshader'); + } + if (remote) { // Replace workspace path with URI args[0] = `--${workspacePath.endsWith('.code-workspace') ? 'file' : 'folder'}-uri=vscode-remote://test+test/${URI.file(workspacePath).path}`; if (codePath) { // running against a build: copy the test resolver extension - await measureAndLog(copyExtension(root, extensionsPath, 'vscode-test-resolver'), 'copyExtension(vscode-test-resolver)', logger); + await measureAndLog(() => copyExtension(root, extensionsPath, 'vscode-test-resolver'), 'copyExtension(vscode-test-resolver)', logger); } args.push('--enable-proposed-api=vscode.vscode-test-resolver'); const remoteDataDir = `${userDataDir}-server`; diff --git a/test/automation/src/logger.ts b/test/automation/src/logger.ts index a3f77b470f..d2867d36ce 100644 --- a/test/automation/src/logger.ts +++ b/test/automation/src/logger.ts @@ -41,15 +41,15 @@ export class MultiLogger implements Logger { } } -export async function measureAndLog(promise: Promise, name: string, logger: Logger): Promise { +export async function measureAndLog(promiseFactory: () => Promise, name: string, logger: Logger): Promise { const now = Date.now(); - logger.log(`Starting operation '${name}...`); + logger.log(`Starting operation '${name}'...`); let res: T | undefined = undefined; let e: unknown; try { - res = await promise; + res = await promiseFactory(); } catch (error) { e = error; } diff --git a/test/automation/src/notebook.ts b/test/automation/src/notebook.ts index bca8f73938..0a87e7ba16 100644 --- a/test/automation/src/notebook.ts +++ b/test/automation/src/notebook.ts @@ -20,6 +20,7 @@ export class Notebook { async openNotebook() { await this.quickAccess.openFileQuickAccessAndWait('notebook.ipynb', 1); await this.quickInput.selectQuickInputElement(0); + await this.code.waitForElement(activeRowSelector); await this.focusFirstCell(); await this.waitForActiveCellEditorContents('code()'); diff --git a/test/automation/src/playwrightBrowser.ts b/test/automation/src/playwrightBrowser.ts index b9fab48b89..e5af453d71 100644 --- a/test/automation/src/playwrightBrowser.ts +++ b/test/automation/src/playwrightBrowser.ts @@ -22,19 +22,20 @@ export async function launch(options: LaunchOptions): Promise<{ serverProcess: C const { serverProcess, endpoint } = await launchServer(options); // Launch browser - const { browser, context, page } = await launchBrowser(options, endpoint); + const { browser, context, page, pageLoadedPromise } = await launchBrowser(options, endpoint); return { serverProcess, - driver: new PlaywrightDriver(browser, context, page, serverProcess, options) + driver: new PlaywrightDriver(browser, context, page, serverProcess, pageLoadedPromise, options) }; } async function launchServer(options: LaunchOptions) { const { userDataDir, codePath, extensionsPath, logger, logsPath } = options; + const serverLogsPath = join(logsPath, 'server'); const codeServerPath = codePath ?? process.env.VSCODE_REMOTE_SERVER_PATH; const agentFolder = userDataDir; - await measureAndLog(mkdirp(agentFolder), `mkdirp(${agentFolder})`, logger); + await measureAndLog(() => mkdirp(agentFolder), `mkdirp(${agentFolder})`, logger); const env = { VSCODE_REMOTE_SERVER_PATH: codeServerPath, @@ -49,7 +50,7 @@ async function launchServer(options: LaunchOptions) { `--extensions-dir=${extensionsPath}`, `--server-data-dir=${agentFolder}`, '--accept-server-license-terms', - `--logsPath=${logsPath}` + `--logsPath=${serverLogsPath}` ]; if (options.verbose) { @@ -68,7 +69,7 @@ async function launchServer(options: LaunchOptions) { logger.log(`Starting server out of sources from '${serverLocation}'`); } - logger.log(`Storing log files into '${logsPath}'`); + logger.log(`Storing log files into '${serverLogsPath}'`); logger.log(`Command line: '${serverLocation}' ${args.join(' ')}`); const serverProcess = spawn( @@ -81,28 +82,32 @@ async function launchServer(options: LaunchOptions) { return { serverProcess, - endpoint: await measureAndLog(waitForEndpoint(serverProcess, logger), 'waitForEndpoint(serverProcess)', logger) + endpoint: await measureAndLog(() => waitForEndpoint(serverProcess, logger), 'waitForEndpoint(serverProcess)', logger) }; } async function launchBrowser(options: LaunchOptions, endpoint: string) { const { logger, workspacePath, tracing, headless } = options; - const browser = await measureAndLog(playwright[options.browser ?? 'chromium'].launch({ headless: headless ?? false }), 'playwright#launch', logger); + const browser = await measureAndLog(() => playwright[options.browser ?? 'chromium'].launch({ + headless: headless ?? false, + timeout: 0 + }), 'playwright#launch', logger); + browser.on('disconnected', () => logger.log(`Playwright: browser disconnected`)); - const context = await measureAndLog(browser.newContext(), 'browser.newContext', logger); + const context = await measureAndLog(() => browser.newContext(), 'browser.newContext', logger); if (tracing) { try { - await measureAndLog(context.tracing.start({ screenshots: true, /* remaining options are off for perf reasons */ }), 'context.tracing.start()', logger); + await measureAndLog(() => context.tracing.start({ screenshots: true, /* remaining options are off for perf reasons */ }), 'context.tracing.start()', logger); } catch (error) { logger.log(`Playwright (Browser): Failed to start playwright tracing (${error})`); // do not fail the build when this fails } } - const page = await measureAndLog(context.newPage(), 'context.newPage()', logger); - await measureAndLog(page.setViewportSize({ width: 1200, height: 800 }), 'page.setViewportSize', logger); + const page = await measureAndLog(() => context.newPage(), 'context.newPage()', logger); + await measureAndLog(() => page.setViewportSize({ width: 1200, height: 800 }), 'page.setViewportSize', logger); if (options.verbose) { context.on('page', () => logger.log(`Playwright (Browser): context.on('page')`)); @@ -133,9 +138,12 @@ async function launchBrowser(options: LaunchOptions, endpoint: string) { `["logLevel","${options.verbose ? 'trace' : 'info'}"]` ].join(',')}]`; - await measureAndLog(page.goto(`${endpoint}&${workspacePath.endsWith('.code-workspace') ? 'workspace' : 'folder'}=${URI.file(workspacePath!).path}&payload=${payloadParam}`), 'page.goto()', logger); + const gotoPromise = measureAndLog(() => page.goto(`${endpoint}&${workspacePath.endsWith('.code-workspace') ? 'workspace' : 'folder'}=${URI.file(workspacePath!).path}&payload=${payloadParam}`), 'page.goto()', logger); + const pageLoadedPromise = page.waitForLoadState('load'); - return { browser, context, page }; + await gotoPromise; + + return { browser, context, page, pageLoadedPromise }; } function waitForEndpoint(server: ChildProcess, logger: Logger): Promise { diff --git a/test/automation/src/playwrightDriver.ts b/test/automation/src/playwrightDriver.ts index 1088ab5720..41ef6116a7 100644 --- a/test/automation/src/playwrightDriver.ts +++ b/test/automation/src/playwrightDriver.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as playwright from '@playwright/test'; -import { join } from 'path'; +import { dirname, join } from 'path'; +import { promises } from 'fs'; import { IWindowDriver } from './driver'; import { PageFunction } from 'playwright-core/types/structs'; import { measureAndLog } from './logger'; @@ -36,6 +37,7 @@ export class PlaywrightDriver { private readonly context: playwright.BrowserContext, private readonly page: playwright.Page, private readonly serverProcess: ChildProcess | undefined, + private readonly whenLoaded: Promise, private readonly options: LaunchOptions ) { } @@ -46,7 +48,7 @@ export class PlaywrightDriver { } try { - await measureAndLog(this.context.tracing.startChunk({ title: name }), `startTracing for ${name}`, this.options.logger); + await measureAndLog(() => this.context.tracing.startChunk({ title: name }), `startTracing for ${name}`, this.options.logger); } catch (error) { // Ignore } @@ -63,7 +65,7 @@ export class PlaywrightDriver { persistPath = join(this.options.logsPath, `playwright-trace-${PlaywrightDriver.traceCounter++}-${name.replace(/\s+/g, '-')}.zip`); } - await measureAndLog(this.context.tracing.stopChunk({ path: persistPath }), `stopTracing for ${name}`, this.options.logger); + await measureAndLog(() => this.context.tracing.stopChunk({ path: persistPath }), `stopTracing for ${name}`, this.options.logger); // To ensure we have a screenshot at the end where // it failed, also trigger one explicitly. Tracing @@ -77,11 +79,15 @@ export class PlaywrightDriver { } } + async didFinishLoad(): Promise { + await this.whenLoaded; + } + private async takeScreenshot(name: string): Promise { try { const persistPath = join(this.options.logsPath, `playwright-screenshot-${PlaywrightDriver.screenShotCounter++}-${name.replace(/\s+/g, '-')}.png`); - await measureAndLog(this.page.screenshot({ path: persistPath, type: 'png' }), 'takeScreenshot', this.options.logger); + await measureAndLog(() => this.page.screenshot({ path: persistPath, type: 'png' }), 'takeScreenshot', this.options.logger); } catch (error) { // Ignore } @@ -96,16 +102,25 @@ export class PlaywrightDriver { // Stop tracing try { if (this.options.tracing) { - await measureAndLog(this.context.tracing.stop(), 'stop tracing', this.options.logger); + await measureAndLog(() => this.context.tracing.stop(), 'stop tracing', this.options.logger); } } catch (error) { // Ignore } + // Web: Extract client logs + if (this.options.web) { + try { + await measureAndLog(() => this.saveWebClientLogs(), 'saveWebClientLogs()', this.options.logger); + } catch (error) { + this.options.logger.log(`Error saving web client logs (${error})`); + } + } + // Web: exit via `close` method if (this.options.web) { try { - await measureAndLog(this.application.close(), 'playwright.close()', this.options.logger); + await measureAndLog(() => this.application.close(), 'playwright.close()', this.options.logger); } catch (error) { this.options.logger.log(`Error closing appliction (${error})`); } @@ -114,7 +129,7 @@ export class PlaywrightDriver { // Desktop: exit via `driver.exitApplication` else { try { - await measureAndLog(this.evaluateWithDriver(([driver]) => driver.exitApplication()), 'driver.exitApplication()', this.options.logger); + await measureAndLog(() => this.evaluateWithDriver(([driver]) => driver.exitApplication()), 'driver.exitApplication()', this.options.logger); } catch (error) { this.options.logger.log(`Error exiting appliction (${error})`); } @@ -122,7 +137,18 @@ export class PlaywrightDriver { // Server: via `teardown` if (this.serverProcess) { - await measureAndLog(teardown(this.serverProcess, this.options.logger), 'teardown server process', this.options.logger); + await measureAndLog(() => teardown(this.serverProcess!, this.options.logger), 'teardown server process', this.options.logger); + } + } + + private async saveWebClientLogs(): Promise { + const logs = await this.getLogs(); + + for (const log of logs) { + const absoluteLogsPath = join(this.options.logsPath, log.relativePath); + + await promises.mkdir(dirname(absoluteLogsPath), { recursive: true }); + await promises.writeFile(absoluteLogsPath, log.contents); } } @@ -166,7 +192,7 @@ export class PlaywrightDriver { } async getTitle() { - return this.evaluateWithDriver(([driver]) => driver.getTitle()); + return this.page.title(); } async isActiveElement(selector: string) { @@ -201,6 +227,10 @@ export class PlaywrightDriver { return this.evaluateWithDriver(([driver]) => driver.getLocalizedStrings()); } + async getLogs() { + return this.page.evaluate(([driver]) => driver.getLogs(), [await this.getDriverHandle()] as const); + } + private async evaluateWithDriver(pageFunction: PageFunction[], T>) { return this.page.evaluate(pageFunction, [await this.getDriverHandle()]); } diff --git a/test/automation/src/playwrightElectron.ts b/test/automation/src/playwrightElectron.ts index e828da4d93..938ddf0306 100644 --- a/test/automation/src/playwrightElectron.ts +++ b/test/automation/src/playwrightElectron.ts @@ -22,26 +22,30 @@ export async function launch(options: LaunchOptions): Promise<{ electronProcess: return { electronProcess, - driver: new PlaywrightDriver(electron, context, page, undefined /* no server process */, options) + driver: new PlaywrightDriver(electron, context, page, undefined /* no server process */, Promise.resolve() /* Window is open already */, options) }; } async function launchElectron(configuration: IElectronConfiguration, options: LaunchOptions) { const { logger, tracing } = options; - const electron = await measureAndLog(playwright._electron.launch({ + const electron = await measureAndLog(() => playwright._electron.launch({ executablePath: configuration.electronPath, args: configuration.args, - env: configuration.env as { [key: string]: string } + env: configuration.env as { [key: string]: string }, + timeout: 0 }), 'playwright-electron#launch', logger); - const window = await measureAndLog(electron.firstWindow(), 'playwright-electron#firstWindow', logger); + let window = electron.windows()[0]; + if (!window) { + window = await measureAndLog(() => electron.waitForEvent('window', { timeout: 0 }), 'playwright-electron#firstWindow', logger); + } const context = window.context(); if (tracing) { try { - await measureAndLog(context.tracing.start({ screenshots: true, /* remaining options are off for perf reasons */ }), 'context.tracing.start()', logger); + await measureAndLog(() => context.tracing.start({ screenshots: true, /* remaining options are off for perf reasons */ }), 'context.tracing.start()', logger); } catch (error) { logger.log(`Playwright (Electron): Failed to start playwright tracing (${error})`); // do not fail the build when this fails } diff --git a/test/automation/src/problems.ts b/test/automation/src/problems.ts index 14b4788ecd..7be4aa41df 100644 --- a/test/automation/src/problems.ts +++ b/test/automation/src/problems.ts @@ -33,7 +33,7 @@ export class Problems { static getSelectorInProblemsView(problemType: ProblemSeverity): string { const selector = problemType === ProblemSeverity.WARNING ? 'codicon-warning' : 'codicon-error'; - return `div[id="workbench.panel.markers"] .monaco-tl-contents .marker-icon.${selector}`; + return `div[id="workbench.panel.markers"] .monaco-tl-contents .marker-icon .${selector}`; } static getSelectorInEditor(problemType: ProblemSeverity): string { diff --git a/test/automation/src/quickinput.ts b/test/automation/src/quickinput.ts index 1dce9a5e30..8c0827c126 100644 --- a/test/automation/src/quickinput.ts +++ b/test/automation/src/quickinput.ts @@ -11,8 +11,9 @@ export class QuickInput { private static QUICK_INPUT_INPUT = `${QuickInput.QUICK_INPUT} .quick-input-box input`; private static QUICK_INPUT_ROW = `${QuickInput.QUICK_INPUT} .quick-input-list .monaco-list-row`; private static QUICK_INPUT_FOCUSED_ELEMENT = `${QuickInput.QUICK_INPUT_ROW}.focused .monaco-highlighted-label`; - private static QUICK_INPUT_ENTRY_LABEL = `${QuickInput.QUICK_INPUT_ROW} .label-name`; - private static QUICK_INPUT_ENTRY_LABEL_SPAN = `${QuickInput.QUICK_INPUT_ROW} .monaco-highlighted-label span`; + // Note: this only grabs the label and not the description or detail + private static QUICK_INPUT_ENTRY_LABEL = `${QuickInput.QUICK_INPUT_ROW} .quick-input-list-row > .monaco-icon-label .label-name`; + private static QUICK_INPUT_ENTRY_LABEL_SPAN = `${QuickInput.QUICK_INPUT_ROW} .monaco-highlighted-label`; constructor(private code: Code) { } diff --git a/test/automation/src/search.ts b/test/automation/src/search.ts index 2ffafd6264..b6d66c8e83 100644 --- a/test/automation/src/search.ts +++ b/test/automation/src/search.ts @@ -35,7 +35,7 @@ export class Search extends Viewlet { async clearSearchResults(): Promise { await retry( - () => this.code.waitAndClick(`.sidebar .codicon-search-clear-results`), + () => this.code.waitAndClick(`.sidebar .title-actions .codicon-search-clear-results`), () => this.waitForNoResultText(10)); } diff --git a/test/automation/src/terminal.ts b/test/automation/src/terminal.ts index 1a6878cae6..588ea511d6 100644 --- a/test/automation/src/terminal.ts +++ b/test/automation/src/terminal.ts @@ -10,9 +10,9 @@ import { IElement } from './driver'; export enum Selector { TerminalView = `#terminal`, - CommandDecorationPlaceholder = `.terminal-command-decoration.codicon-circle-outline`, - CommandDecorationSuccess = `.terminal-command-decoration.codicon-primitive-dot`, - CommandDecorationError = `.terminal-command-decoration.codicon-error-small`, + CommandDecorationPlaceholder = `.terminal-command-decoration.codicon-terminal-decoration-incomplete`, + CommandDecorationSuccess = `.terminal-command-decoration.codicon-terminal-decoration-success`, + CommandDecorationError = `.terminal-command-decoration.codicon-terminal-decoration-error`, Xterm = `#terminal .terminal-wrapper`, XtermEditor = `.editor-instance .terminal-wrapper`, TabsEntry = '.terminal-tabs-entry', diff --git a/test/automation/tsconfig.json b/test/automation/tsconfig.json index a923ad396f..e5cfdfc140 100644 --- a/test/automation/tsconfig.json +++ b/test/automation/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2017", + "target": "es2020", "strict": true, "noUnusedParameters": false, "noUnusedLocals": true, @@ -9,7 +9,7 @@ "sourceMap": true, "declaration": true, "lib": [ - "es2016", + "es2020", "dom" ] }, diff --git a/test/automation/yarn.lock b/test/automation/yarn.lock index 0583d0ccfc..a3e04cf6d5 100644 --- a/test/automation/yarn.lock +++ b/test/automation/yarn.lock @@ -22,9 +22,9 @@ integrity sha512-aK9jxMypeSrhiYofWWBf/T7O+KwaiAHzM4sveCdWPn71lzUSMimRnKzhXDKfKwV1kWoBo2P1aGgaIYGLf9/ljw== "@types/node@16.x": - version "16.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" - integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== + version "16.18.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc" + integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg== "@types/tmp@0.2.2": version "0.2.2" diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 423eae98a5..418e2defb4 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -13,6 +13,10 @@ import { URI } from 'vscode-uri'; import * as kill from 'tree-kill'; import * as optimistLib from 'optimist'; import { promisify } from 'util'; +import { promises } from 'fs'; + +const root = path.join(__dirname, '..', '..', '..', '..'); +const logsPath = path.join(root, '.build', 'logs', 'integration-tests-browser'); const optimist = optimistLib .describe('workspacePath', 'path to the workspace (folder or *.code-workspace file) to open in the test').string('workspacePath') @@ -35,6 +39,7 @@ type BrowserType = 'chromium' | 'firefox' | 'webkit'; async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWithStringQuery, server: cp.ChildProcess): Promise { const browser = await playwright[browserType].launch({ headless: !Boolean(optimist.argv.debug) }); const context = await browser.newContext(); + const page = await context.newPage(); await page.setViewportSize({ width, height }); @@ -58,26 +63,22 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith console.error('Request Failed', e.url(), e.failure()?.errorText); }); - const host = endpoint.host; - const protocol = 'vscode-remote'; - - const testWorkspacePath = URI.file(path.resolve(optimist.argv.workspacePath)).path; - const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true }); - const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true }); - - const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","3c8520fab514b9f56070214496b26ff68d1b1cb5"],["skipWelcome","true"]]`; - - if (path.extname(testWorkspacePath) === '.code-workspace') { - await page.goto(`${endpoint.href}&workspace=${testWorkspacePath}&payload=${payloadParam}`); - } else { - await page.goto(`${endpoint.href}&folder=${testWorkspacePath}&payload=${payloadParam}`); - } - await page.exposeFunction('codeAutomationLog', (type: string, args: any[]) => { console[type](...args); }); - await page.exposeFunction('codeAutomationExit', async (code: number) => { + await page.exposeFunction('codeAutomationExit', async (code: number, logs: Array<{ readonly relativePath: string; readonly contents: string }>) => { + try { + for (const log of logs) { + const absoluteLogsPath = path.join(logsPath, log.relativePath); + + await promises.mkdir(path.dirname(absoluteLogsPath), { recursive: true }); + await promises.writeFile(absoluteLogsPath, log.contents); + } + } catch (error) { + console.error(`Error saving web client logs (${error})`); + } + try { await browser.close(); } catch (error) { @@ -92,6 +93,21 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith process.exit(code); }); + + const host = endpoint.host; + const protocol = 'vscode-remote'; + + const testWorkspacePath = URI.file(path.resolve(optimist.argv.workspacePath)).path; + const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true }); + const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true }); + + const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","ef65ac1ba57f57f2a3961bfe94aa20481caca4c6"],["skipWelcome","true"]]`; + + if (path.extname(testWorkspacePath) === '.code-workspace') { + await page.goto(`${endpoint.href}&workspace=${testWorkspacePath}&payload=${payloadParam}`); + } else { + await page.goto(`${endpoint.href}&folder=${testWorkspacePath}&payload=${payloadParam}`); + } } function consoleLogFn(msg: playwright.ConsoleMessage) { @@ -122,9 +138,6 @@ async function launchServer(browserType: BrowserType): Promise<{ endpoint: url.U ...process.env }; - const root = path.join(__dirname, '..', '..', '..', '..'); - const logsPath = path.join(root, '.build', 'logs', 'integration-tests-browser'); - const serverArgs = ['--enable-proposed-api', '--disable-telemetry', '--server-data-dir', userDataDir, '--accept-server-license-terms', '--disable-workspace-trust']; let serverLocation: string; @@ -144,8 +157,9 @@ async function launchServer(browserType: BrowserType): Promise<{ endpoint: url.U } } - console.log(`Storing log files into '${logsPath}'`); - serverArgs.push('--logsPath', logsPath); + const serverLogsPath = path.join(logsPath, 'server'); + console.log(`Storing log files into '${serverLogsPath}'`); + serverArgs.push('--logsPath', serverLogsPath); const stdio: cp.StdioOptions = optimist.argv.debug ? 'pipe' : ['ignore', 'pipe', 'ignore']; diff --git a/test/integration/browser/tsconfig.json b/test/integration/browser/tsconfig.json index 6f0b40e93e..262769765a 100644 --- a/test/integration/browser/tsconfig.json +++ b/test/integration/browser/tsconfig.json @@ -4,14 +4,14 @@ "noImplicitAny": false, "removeComments": false, "preserveConstEnums": true, - "target": "es2017", - "strictNullChecks": true, + "target": "es2020", + "strict": true, "noUnusedParameters": false, "noUnusedLocals": true, "outDir": "out", "sourceMap": true, "lib": [ - "es2016", + "es2020", "dom" ] }, diff --git a/test/integration/browser/yarn.lock b/test/integration/browser/yarn.lock index 399fd413a0..94b6a43f70 100644 --- a/test/integration/browser/yarn.lock +++ b/test/integration/browser/yarn.lock @@ -34,9 +34,9 @@ integrity sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ== "@types/node@16.x": - version "16.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" - integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== + version "16.18.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc" + integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg== "@types/optimist@0.0.29": version "0.0.29" diff --git a/test/integration/electron/testrunner.d.ts b/test/integration/electron/testrunner.d.ts new file mode 100644 index 0000000000..414e4c79d4 --- /dev/null +++ b/test/integration/electron/testrunner.d.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { MochaOptions } from 'mocha'; + +export function configure(opts: MochaOptions): void; + +export function run(testsRoot: string[], clb: (error: Error | undefined, failures: number | undefined) => void): void; diff --git a/test/monaco/esm-check/index.js b/test/monaco/esm-check/index.js index 3d7f1d7397..802a2dc94b 100644 --- a/test/monaco/esm-check/index.js +++ b/test/monaco/esm-check/index.js @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -// eslint-disable-next-line code-no-standalone-editor +// eslint-disable-next-line local/code-no-standalone-editor import * as monaco from './out/vs/editor/editor.main.js'; monaco.editor.create(document.getElementById('container'), { diff --git a/test/smoke/package.json b/test/smoke/package.json index 81812c0af5..ef9ec20950 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -11,7 +11,7 @@ "mocha": "node ../node_modules/mocha/bin/mocha" }, "dependencies": { - "@vscode/test-electron": "2.1.4", + "@vscode/test-electron": "^2.2.1", "mkdirp": "^1.0.4", "ncp": "^2.0.0", "node-fetch": "^2.6.7", diff --git a/test/smoke/src/areas/notebook/notebook.test.ts b/test/smoke/src/areas/notebook/notebook.test.ts index 590e7478db..2afef57498 100644 --- a/test/smoke/src/areas/notebook/notebook.test.ts +++ b/test/smoke/src/areas/notebook/notebook.test.ts @@ -8,7 +8,7 @@ import { Application, Logger } from '../../../../automation'; import { installAllHandlers } from '../../utils'; export function setup(logger: Logger) { - describe.skip('Notebooks', () => { // TODO@rebornix https://github.com/microsoft/vscode/issues/140575 + describe.skip('Notebooks', () => { // https://github.com/microsoft/vscode/issues/140575 // Shared before/after handling installAllHandlers(logger); @@ -25,7 +25,7 @@ export function setup(logger: Logger) { cp.execSync('git reset --hard HEAD --quiet', { cwd: app.workspacePathOrFolder }); }); - it.skip('inserts/edits code cell', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139672 + it('inserts/edits code cell', async function () { const app = this.app as Application; await app.workbench.notebook.openNotebook(); await app.workbench.notebook.focusNextCell(); @@ -34,7 +34,7 @@ export function setup(logger: Logger) { await app.workbench.notebook.stopEditingCell(); }); - it.skip('inserts/edits markdown cell', async function () { + it('inserts/edits markdown cell', async function () { const app = this.app as Application; await app.workbench.notebook.openNotebook(); await app.workbench.notebook.focusNextCell(); diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 41815756fc..a70354ae84 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -29,14 +29,16 @@ export function setup(logger: Logger) { it('searches only for *.js files & checks for correct result number', async function () { const app = this.app as Application; - await app.workbench.search.searchFor('body'); - await app.workbench.search.showQueryDetails(); - await app.workbench.search.setFilesToIncludeText('*.js'); - await app.workbench.search.submitSearch(); + try { + await app.workbench.search.setFilesToIncludeText('*.js'); + await app.workbench.search.searchFor('body'); + await app.workbench.search.showQueryDetails(); - await app.workbench.search.waitForResultText('4 results in 1 file'); - await app.workbench.search.setFilesToIncludeText(''); - await app.workbench.search.hideQueryDetails(); + await app.workbench.search.waitForResultText('4 results in 1 file'); + } finally { + await app.workbench.search.setFilesToIncludeText(''); + await app.workbench.search.hideQueryDetails(); + } }); it('dismisses result & checks for correct result number', async function () { diff --git a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts index 25edff081c..c9acfe3e63 100644 --- a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts +++ b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts @@ -51,18 +51,6 @@ export function setup() { await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 1 }); }); }); - describe('Custom configuration', function () { - it('Should update and show custom icons', async () => { - await createShellIntegrationProfile(); - await terminal.assertCommandDecorations({ placeholder: 1, success: 0, error: 0 }); - await terminal.runCommandInTerminal(`echo "foo"`); - await terminal.runCommandInTerminal(`bar`); - await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationIcon', '"zap"'); - await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationIconSuccess', '"zap"'); - await settingsEditor.addUserSetting('terminal.integrated.shellIntegration.decorationIconError', '"zap"'); - await terminal.assertCommandDecorations(undefined, { updatedIcon: "zap", count: 3 }); - }); - }); describe('terminal.integrated.shellIntegration.decorationsEnabled should determine gutter and overview ruler decoration visibility', function () { beforeEach(async () => { await settingsEditor.clearUserSettings(); diff --git a/test/smoke/src/areas/workbench/data-loss.test.ts b/test/smoke/src/areas/workbench/data-loss.test.ts index d9ac3d4300..9ef4d2c54a 100644 --- a/test/smoke/src/areas/workbench/data-loss.test.ts +++ b/test/smoke/src/areas/workbench/data-loss.test.ts @@ -8,7 +8,10 @@ import { Application, ApplicationOptions, Logger, Quality } from '../../../../au import { createApp, timeout, installDiagnosticsHandler, installAppAfterHandler, getRandomUserDataDir, suiteLogsPath, suiteCrashPath } from '../../utils'; export function setup(ensureStableCode: () => string | undefined, logger: Logger) { - describe('Data Loss (insiders -> insiders)', () => { + describe('Data Loss (insiders -> insiders)', function () { + + // Double the timeout since these tests involve 2 startups + this.timeout(4 * 60 * 1000); let app: Application | undefined = undefined; @@ -72,18 +75,18 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger }); it('verifies that "hot exit" works for dirty files (without delay)', function () { - return testHotExit.call(this, 'test_verifies_that_hot_exit_works_for_dirty_files_without_delay', undefined); + return testHotExit.call(this, 'test_verifies_that_hot_exit_works_for_dirty_files_without_delay', undefined, undefined); }); it('verifies that "hot exit" works for dirty files (with delay)', function () { - return testHotExit.call(this, 'test_verifies_that_hot_exit_works_for_dirty_files_with_delay', 2000); + return testHotExit.call(this, 'test_verifies_that_hot_exit_works_for_dirty_files_with_delay', 2000, undefined); }); it('verifies that auto save triggers on shutdown', function () { return testHotExit.call(this, 'test_verifies_that_auto_save_triggers_on_shutdown', undefined, true); }); - async function testHotExit(title: string, restartDelay: number | undefined, autoSave: boolean | undefined) { + async function testHotExit(this: import('mocha').Context, title: string, restartDelay: number | undefined, autoSave: boolean | undefined) { app = createApp({ ...this.defaultOptions, logsPath: suiteLogsPath(this.defaultOptions, title), @@ -130,7 +133,10 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger } }); - describe('Data Loss (stable -> insiders)', () => { + describe('Data Loss (stable -> insiders)', function () { + + // Double the timeout since these tests involve 2 startups + this.timeout(4 * 60 * 1000); let insidersApp: Application | undefined = undefined; let stableApp: Application | undefined = undefined; @@ -203,7 +209,7 @@ export function setup(ensureStableCode: () => string | undefined, logger: Logger return testHotExit.call(this, `test_verifies_that_hot_exit_works_for_dirty_files_with_delay_from_stable`, 2000); }); - async function testHotExit(title: string, restartDelay: number | undefined) { + async function testHotExit(this: import('mocha').Context, title: string, restartDelay: number | undefined) { const stableCodePath = ensureStableCode(); if (!stableCodePath) { this.skip(); diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 38bb784beb..229648d29f 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -329,28 +329,58 @@ async function ensureStableCode(): Promise { let stableCodePath = opts['stable-build']; if (!stableCodePath) { - const { major, minor } = parseVersion(version!); - const majorMinorVersion = `${major}.${minor - 1}`; - const versionsReq = await fetch('https://update.code.visualstudio.com/api/releases/stable', { headers: { 'x-api-version': '2' } }); + const current = parseVersion(version!); + const versionsReq = await retry(() => measureAndLog(() => fetch('https://update.code.visualstudio.com/api/releases/stable'), 'versionReq', logger), 1000, 20); if (!versionsReq.ok) { throw new Error('Could not fetch releases from update server'); } - const versions: { version: string }[] = await versionsReq.json(); - const prefix = `${majorMinorVersion}.`; - const previousVersion = versions.find(v => v.version.startsWith(prefix)); + const versions: string[] = await measureAndLog(() => versionsReq.json(), 'versionReq.json()', logger); + const stableVersion = versions.find(raw => { + const version = parseVersion(raw); + return version.major < current.major || (version.major === current.major && version.minor < current.minor); + }); - if (!previousVersion) { - throw new Error(`Could not find suitable stable version ${majorMinorVersion}`); + if (!stableVersion) { + throw new Error(`Could not find suitable stable version for ${version}`); } - console.log(`*** Found VS Code v${version}, downloading previous VS Code version ${previousVersion.version}...`); + logger.log(`Found VS Code v${version}, downloading previous VS Code version ${stableVersion}...`); - const stableCodeExecutable = await vscodetest.download({ - cachePath: path.join(os.tmpdir(), 'vscode-test'), - version: previousVersion.version - }); + let lastProgressMessage: string | undefined = undefined; + let lastProgressReportedAt = 0; + const stableCodeDestination = path.join(testDataPath, 's'); + const stableCodeExecutable = await retry(() => measureAndLog(() => vscodetest.download({ + cachePath: stableCodeDestination, + version: stableVersion, + extractSync: true, + reporter: { + report: report => { + let progressMessage = `download stable code progress: ${report.stage}`; + const now = Date.now(); + if (progressMessage !== lastProgressMessage || now - lastProgressReportedAt > 10000) { + lastProgressMessage = progressMessage; + lastProgressReportedAt = now; + + if (report.stage === 'downloading') { + progressMessage += ` (${report.bytesSoFar}/${report.totalBytes})`; + } + + logger.log(progressMessage); + } + }, + error: error => logger.log(`download stable code error: ${error}`) + } + }), 'download stable code', logger), 1000, 3, () => new Promise((resolve, reject) => { + rimraf(stableCodeDestination, { maxBusyTries: 10 }, error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + })); if (process.platform === 'darwin') { // Visual Studio Code.app/Contents/MacOS/Electron @@ -365,7 +395,7 @@ async function ensureStableCode(): Promise { throw new Error(`Can't find Stable VSCode at ${stableCodePath}.`); } - console.log(`*** Using stable build ${stableCodePath} for migration tests`); + logger.log(`Using stable build ${stableCodePath} for migration tests`); opts['stable-build'] = stableCodePath; } diff --git a/test/smoke/src/utils.ts b/test/smoke/src/utils.ts index 9807a17b52..7fd4bf2942 100644 --- a/test/smoke/src/utils.ts +++ b/test/smoke/src/utils.ts @@ -19,25 +19,6 @@ export function itRepeat(n: number, description: string, callback: (this: Contex } } -/** - * Defines a test-case that will run but will be skips it if it throws an exception. This is useful - * to get some runs in CI when trying to stabilize a flaky test, without failing the build. Note - * that this only works if something inside the test throws, so a test's overall timeout won't work - * but throwing due to a polling timeout will. - * @param title The test-case title. - * @param callback The test-case callback. - */ -export function itSkipOnFail(title: string, callback: (this: Context) => any): void { - it(title, function () { - return Promise.resolve().then(() => { - return callback.apply(this, arguments); - }).catch(e => { - console.warn(`Test "${title}" failed but was marked as skip on fail:`, e); - this.skip(); - }); - }); -} - export function installAllHandlers(logger: Logger, optionsTransform?: (opts: ApplicationOptions) => ApplicationOptions) { installDiagnosticsHandler(logger); installAppBeforeHandler(optionsTransform); @@ -226,7 +207,7 @@ export async function retry(task: ITask>, delay: number, retries: return await task(); } catch (error) { - lastError = error; + lastError = error as Error; await timeout(delay); } diff --git a/test/smoke/tsconfig.json b/test/smoke/tsconfig.json index 6f0b40e93e..262769765a 100644 --- a/test/smoke/tsconfig.json +++ b/test/smoke/tsconfig.json @@ -4,14 +4,14 @@ "noImplicitAny": false, "removeComments": false, "preserveConstEnums": true, - "target": "es2017", - "strictNullChecks": true, + "target": "es2020", + "strict": true, "noUnusedParameters": false, "noUnusedLocals": true, "outDir": "out", "sourceMap": true, "lib": [ - "es2016", + "es2020", "dom" ] }, diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index a5618bc572..b27d39908a 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -59,9 +59,9 @@ integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ== "@types/node@16.x": - version "16.11.6" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" - integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== + version "16.18.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc" + integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg== "@types/rimraf@3.0.2": version "3.0.2" @@ -71,10 +71,10 @@ "@types/glob" "*" "@types/node" "*" -"@vscode/test-electron@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@vscode/test-electron/-/test-electron-2.1.4.tgz#fa1b8915246d0102e81d4fd664bb6c337ca7092f" - integrity sha512-tHHAWNVwl8C7nyezHAHdNPWkksdXWvmae6bt4k1tJ9hvMm6QIIk95Mkutl82XHcD60mdP46EHDGU+xFsAvygOQ== +"@vscode/test-electron@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@vscode/test-electron/-/test-electron-2.2.1.tgz#6d1ac128e27c18e1d20bcb299e830b50587f74ca" + integrity sha512-DUdwSYVc9p/PbGveaq20dbAAXHfvdq4zQ24ILp6PKizOBxrOfMsOq8Vts5nMzeIo0CxtA/RxZLFyDv001PiUSg== dependencies: http-proxy-agent "^4.0.1" https-proxy-agent "^5.0.0" @@ -483,9 +483,9 @@ mime-types@^2.1.12: mime-db "1.49.0" 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== + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" diff --git a/test/unit/README.md b/test/unit/README.md index 0a1c9f4e0a..5381228509 100644 --- a/test/unit/README.md +++ b/test/unit/README.md @@ -4,7 +4,7 @@ ./scripts/test.[sh|bat] -All unit tests are run inside a electron-browser environment which access to DOM and Nodejs api. This is the closest to the environment in which VS Code itself ships. Notes: +All unit tests are run inside a Electron renderer environment which access to DOM and Nodejs api. This is the closest to the environment in which VS Code itself ships. Notes: - use the `--debug` to see an electron window with dev tools which allows for debugging - to run only a subset of tests use the `--run` or `--glob` options diff --git a/test/unit/assert.js b/test/unit/assert.js index 228016e796..1152dcc597 100644 --- a/test/unit/assert.js +++ b/test/unit/assert.js @@ -467,5 +467,52 @@ assert.doesNotThrow = function(block, /*optional*/message) { }; assert.ifError = function(err) { if (err) {throw err;}}; + +function checkIsPromise(obj) { + return (obj !== null && typeof obj === 'object' && + typeof obj.then === 'function' && + typeof obj.catch === 'function'); +} + +const NO_EXCEPTION_SENTINEL = {}; +async function waitForActual(promiseFn) { + let resultPromise; + if (typeof promiseFn === 'function') { + // Return a rejected promise if `promiseFn` throws synchronously. + resultPromise = promiseFn(); + // Fail in case no promise is returned. + if (!checkIsPromise(resultPromise)) { + throw new Error('ERR_INVALID_RETURN_VALUE: promiseFn did not return Promise. ' + resultPromise); + } + } else if (checkIsPromise(promiseFn)) { + resultPromise = promiseFn; + } else { + throw new Error('ERR_INVALID_ARG_TYPE: promiseFn is not Function or Promise. ' + promiseFn); + } + + try { + await resultPromise; + } catch (e) { + return e; + } + return NO_EXCEPTION_SENTINEL; +} + +function expectsError(shouldHaveError, actual, message) { + if (shouldHaveError && actual === NO_EXCEPTION_SENTINEL) { + fail(undefined, 'Error', `Missing expected rejection${message ? ': ' + message : ''}`) + } else if (!shouldHaveError && actual !== NO_EXCEPTION_SENTINEL) { + fail(actual, undefined, `Got unexpected rejection (${actual.message})${message ? ': ' + message : ''}`) + } +} + +assert.rejects = async function rejects(promiseFn, message) { + expectsError(true, await waitForActual(promiseFn), message); +}; + +assert.doesNotReject = async function doesNotReject(fn, message) { + expectsError(false, await waitForActual(fn), message); +}; + return assert; }); diff --git a/test/unit/browser/index.js b/test/unit/browser/index.js index 67798585c5..0a80438e7a 100644 --- a/test/unit/browser/index.js +++ b/test/unit/browser/index.js @@ -67,7 +67,7 @@ function ensureIsArray(a) { const testModules = (async function () { - const excludeGlob = '**/{node,electron-sandbox,electron-browser,electron-main}/**/*.test.js'; + const excludeGlob = '**/{node,electron-sandbox,electron-main}/**/*.test.js'; let isDefaultModules = true; let promise; @@ -131,15 +131,22 @@ async function runTestsInBrowser(testModules, browserType) { const page = await context.newPage(); const target = url.pathToFileURL(path.join(__dirname, 'renderer.html')); if (argv.build) { - target.search = `?build=true`; + if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + target.search = `?build=true&ci=true`; + } else { + target.search = `?build=true`; + } + } else if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) { + target.search = `?ci=true`; } - await page.goto(target.href); const emitter = new events.EventEmitter(); await page.exposeFunction('mocha_report', (type, data1, data2) => { emitter.emit(type, data1, data2); }); + await page.goto(target.href); + page.on('console', async msg => { consoleLogFn(msg)(msg.text(), await Promise.all(msg.args().map(async arg => await arg.jsonValue()))); }); @@ -176,7 +183,7 @@ async function runTestsInBrowser(testModules, browserType) { await browser.close(); if (failingTests.length > 0) { - let res = `The followings tests are failing:\n - ${failingTests.map(({ title, message }) => `${title} (reason: ${message})`).join('\n - ')}`; + let res = `The followings tests are failing:\n - ${failingTests.map(({ title, message }) => `${title} (reason: ${message})`).join('\n - ')}`; if (failingModuleIds.length > 0) { res += `\n\nTo DEBUG, open ${browserType.toUpperCase()} and navigate to ${target.href}?${failingModuleIds.map(module => `m=${module}`).join('&')}`; diff --git a/test/unit/browser/renderer.html b/test/unit/browser/renderer.html index c3e4c3c99d..acdba48435 100644 --- a/test/unit/browser/renderer.html +++ b/test/unit/browser/renderer.html @@ -31,9 +31,12 @@ } }); + const urlParams = new URLSearchParams(window.location.search); + const isCI = urlParams.get('ci'); + mocha.setup({ ui: 'tdd', - timeout: 5000 + timeout: isCI ? 30000 : 5000 }); @@ -42,7 +45,6 @@