mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 21:09:43 +00:00
Merge remote-tracking branch 'origin' into electron-19.x.y
This commit is contained in:
commit
d620fd3aa7
16
.github/commands.json
vendored
16
.github/commands.json
vendored
|
@ -154,7 +154,7 @@
|
|||
"IllusionMH"
|
||||
],
|
||||
"action": "updateLabels",
|
||||
"addLabel": "~needs more info"
|
||||
"addLabel": "~info-needed"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
|
@ -165,14 +165,14 @@
|
|||
"gjsjohnmurray",
|
||||
"IllusionMH"
|
||||
],
|
||||
"addLabel": "needs more info",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Thanks for creating this issue regarding performance! Please follow this guide to help us diagnose performance issues: https://github.com/microsoft/vscode/wiki/Performance-Issues \n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "comment",
|
||||
"name": "jsDebugLogs",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "needs more info",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Please collect trace logs using the following instructions:\n\n> If you're able to, add `\"trace\": true` to your `launch.json` and reproduce the issue. The location of the log file on your disk will be written to the Debug Console. Share that with us.\n>\n> ⚠️ This log file will not contain source code, but will contain file paths. You can drop it into https://microsoft.github.io/vscode-pwa-analyzer/index.html to see what it contains. If you'd rather not share the log publicly, you can email it to connor@xbox.com"
|
||||
},
|
||||
{
|
||||
|
@ -189,17 +189,17 @@
|
|||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~needs more info",
|
||||
"name": "~info-needed",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "needs more info",
|
||||
"removeLabel": "~needs more info",
|
||||
"addLabel": "info-needed",
|
||||
"removeLabel": "~info-needed",
|
||||
"comment": "Thanks for creating this issue! We figured it's missing some basic information or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "~needs version info",
|
||||
"action": "updateLabels",
|
||||
"addLabel": "needs more info",
|
||||
"addLabel": "info-needed",
|
||||
"removeLabel": "~needs version info",
|
||||
"comment": "Thanks for creating this issue! We figured it's missing some basic information, such as a version number, or in some other way doesn't follow our [issue reporting guidelines](https://aka.ms/vscodeissuereporting). Please take the time to review these and update the issue.\n\nHappy Coding!"
|
||||
},
|
||||
|
@ -416,7 +416,7 @@
|
|||
"IllusionMH"
|
||||
],
|
||||
"action": "comment",
|
||||
"addLabel": "needs more info",
|
||||
"addLabel": "info-needed",
|
||||
"comment": "Thanks for reporting this issue! Unfortunately, it's hard for us to understand what issue you're seeing. Please help us out by providing a screen recording showing exactly what isn't working as expected. While we can work with most standard formats, `.gif` files are preferred as they are displayed inline on GitHub. You may find https://gifcap.dev helpful as a browser-based gif recording tool.\n\nIf the issue depends on keyboard input, you can help us by enabling screencast mode for the recording (`Developer: Toggle Screencast Mode` in the command palette).\n\nHappy coding!"
|
||||
},
|
||||
{
|
||||
|
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
|
@ -5,5 +5,3 @@
|
|||
* Ensure that the code is up-to-date with the `main` branch.
|
||||
* Include a description of the proposed changes and how to test them.
|
||||
-->
|
||||
|
||||
This PR fixes #
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: VSCODE_RUN_UNIT_TESTS
|
||||
type: boolean
|
||||
- name: VSCODE_RUN_INTEGRATION_TESTS
|
||||
|
@ -14,25 +16,43 @@ steps:
|
|||
displayName: Download Electron and Playwright
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
./scripts/test.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
./scripts/test.sh --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
yarn test-node --build
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
- script: |
|
||||
set -e
|
||||
yarn test-node
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
DEBUG=*browser* yarn test-browser-no-install --sequential --build --browser chromium --browser webkit --tfs "Browser Unit Tests"
|
||||
displayName: Run unit tests (Browser, Chromium & Webkit)
|
||||
timeoutInMinutes: 30
|
||||
- script: |
|
||||
set -e
|
||||
DEBUG=*browser* yarn test-browser-no-install --sequential --browser chromium --browser webkit --tfs "Browser Unit Tests"
|
||||
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"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
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"
|
||||
displayName: Run unit tests (Browser, Chromium & Webkit)
|
||||
timeoutInMinutes: 30
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
|
@ -57,38 +77,42 @@ steps:
|
|||
compile-extension:vscode-test-resolver
|
||||
displayName: Build integration tests
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
# Figure out the full absolute path of the product we just built
|
||||
# including the remote server and configure the integration tests
|
||||
# to run with these builds instead of running out of sources.
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)
|
||||
APP_NAME="`ls $APP_ROOT | head -n 1`"
|
||||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \
|
||||
./scripts/test-integration.sh --build --tfs "Integration Tests"
|
||||
displayName: Run integration tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
./scripts/test-integration.sh --tfs "Integration Tests"
|
||||
displayName: Run integration tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \
|
||||
./scripts/test-web-integration.sh --browser webkit
|
||||
displayName: Run integration tests (Browser, Webkit)
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
# Figure out the full absolute path of the product we just built
|
||||
# including the remote server and configure the integration tests
|
||||
# to run with these builds instead of running out of sources.
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)
|
||||
APP_NAME="`ls $APP_ROOT | head -n 1`"
|
||||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \
|
||||
./scripts/test-integration.sh --build --tfs "Integration Tests"
|
||||
displayName: Run integration tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)
|
||||
APP_NAME="`ls $APP_ROOT | head -n 1`"
|
||||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \
|
||||
./scripts/test-remote-integration.sh
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \
|
||||
./scripts/test-web-integration.sh --browser webkit
|
||||
displayName: Run integration tests (Browser, Webkit)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)
|
||||
APP_NAME="`ls $APP_ROOT | head -n 1`"
|
||||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \
|
||||
./scripts/test-remote-integration.sh
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
|
@ -98,35 +122,44 @@ steps:
|
|||
continueOnError: true
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \
|
||||
yarn smoketest-no-compile --web --tracing --headless
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Browser, Chromium)
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
yarn --cwd test/smoke compile
|
||||
displayName: Compile smoke tests
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)
|
||||
APP_NAME="`ls $APP_ROOT | head -n 1`"
|
||||
yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME"
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Electron)
|
||||
- script: |
|
||||
set -e
|
||||
yarn smoketest-no-compile --tracing
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Electron)
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
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"
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Remote)
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)
|
||||
APP_NAME="`ls $APP_ROOT | head -n 1`"
|
||||
yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME"
|
||||
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
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Browser, Chromium)
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
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"
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Remote)
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
ps -ef
|
||||
|
@ -148,7 +181,6 @@ steps:
|
|||
continueOnError: true
|
||||
condition: failed()
|
||||
|
||||
- ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}:
|
||||
# In order to properly symbolify above crash reports
|
||||
# (if any), we need the compiled native modules too
|
||||
- task: PublishPipelineArtifact@0
|
||||
|
@ -164,7 +196,6 @@ steps:
|
|||
continueOnError: true
|
||||
condition: failed()
|
||||
|
||||
- ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}:
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
targetPath: .build/logs
|
||||
|
|
|
@ -11,6 +11,11 @@ parameters:
|
|||
type: boolean
|
||||
|
||||
steps:
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
retryCountOnTaskFailure: 3
|
||||
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "16.x"
|
||||
|
@ -23,16 +28,18 @@ steps:
|
|||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
artifact: Compilation
|
||||
path: $(Build.ArtifactStagingDirectory)
|
||||
displayName: Download compilation output
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- 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
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz
|
||||
displayName: Extract compilation output
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
|
@ -123,11 +130,12 @@ steps:
|
|||
node build/azure-pipelines/mixin
|
||||
displayName: Mix in quality
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \
|
||||
yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci
|
||||
displayName: Build client
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \
|
||||
yarn gulp vscode-darwin-$(VSCODE_ARCH)-min-ci
|
||||
displayName: Build client
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
|
@ -135,17 +143,26 @@ steps:
|
|||
node build/azure-pipelines/mixin --server
|
||||
displayName: Mix in server quality
|
||||
|
||||
- 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
|
||||
- ${{ 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
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \
|
||||
yarn gulp "transpile-client" "transpile-extensions"
|
||||
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
|
||||
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 }}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: VSCODE_RUN_UNIT_TESTS
|
||||
type: boolean
|
||||
- name: VSCODE_RUN_INTEGRATION_TESTS
|
||||
|
@ -13,38 +15,68 @@ steps:
|
|||
yarn npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install"
|
||||
displayName: Download Electron and Playwright
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
ELECTRON_ROOT=.build/electron
|
||||
sudo chown root $APP_ROOT/chrome-sandbox
|
||||
sudo chown root $ELECTRON_ROOT/chrome-sandbox
|
||||
sudo chmod 4755 $APP_ROOT/chrome-sandbox
|
||||
sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox
|
||||
stat $APP_ROOT/chrome-sandbox
|
||||
stat $ELECTRON_ROOT/chrome-sandbox
|
||||
displayName: Change setuid helper binary permission
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
./scripts/test.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1
|
||||
sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb
|
||||
sudo chmod +x /etc/init.d/xvfb
|
||||
sudo update-rc.d xvfb defaults
|
||||
sudo service xvfb start
|
||||
displayName: Setup build environment
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
yarn test-node --build
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
ELECTRON_ROOT=.build/electron
|
||||
sudo chown root $APP_ROOT/chrome-sandbox
|
||||
sudo chown root $ELECTRON_ROOT/chrome-sandbox
|
||||
sudo chmod 4755 $APP_ROOT/chrome-sandbox
|
||||
sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox
|
||||
stat $APP_ROOT/chrome-sandbox
|
||||
stat $ELECTRON_ROOT/chrome-sandbox
|
||||
displayName: Change setuid helper binary permission
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
DEBUG=*browser* yarn test-browser-no-install --build --browser chromium --tfs "Browser Unit Tests"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 15
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
DISPLAY=:10 ./scripts/test.sh --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
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"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
./scripts/test.sh --build --tfs "Unit Tests"
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
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"
|
||||
displayName: Run unit tests (Browser, Chromium)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
|
@ -70,39 +102,57 @@ steps:
|
|||
displayName: Build integration tests
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
# Figure out the full absolute path of the product we just built
|
||||
# including the remote server and configure the integration tests
|
||||
# to run with these builds instead of running out of sources.
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName")
|
||||
INTEGRATION_TEST_APP_NAME="$APP_NAME" \
|
||||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
|
||||
./scripts/test-integration.sh --build --tfs "Integration Tests"
|
||||
displayName: Run integration tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
DISPLAY=:10 ./scripts/test-integration.sh --tfs "Integration Tests"
|
||||
displayName: Run integration tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \
|
||||
./scripts/test-web-integration.sh --browser chromium
|
||||
displayName: Run integration tests (Browser, Chromium)
|
||||
timeoutInMinutes: 20
|
||||
- script: |
|
||||
set -e
|
||||
./scripts/test-web-integration.sh --browser chromium
|
||||
displayName: Run integration tests (Browser, Chromium)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName")
|
||||
INTEGRATION_TEST_APP_NAME="$APP_NAME" \
|
||||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
|
||||
./scripts/test-remote-integration.sh
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
- script: |
|
||||
set -e
|
||||
./scripts/test-remote-integration.sh
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
# Figure out the full absolute path of the product we just built
|
||||
# including the remote server and configure the integration tests
|
||||
# to run with these builds instead of running out of sources.
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName")
|
||||
INTEGRATION_TEST_APP_NAME="$APP_NAME" \
|
||||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
|
||||
./scripts/test-integration.sh --build --tfs "Integration Tests"
|
||||
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
|
||||
displayName: Run integration tests (Browser, Chromium)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName")
|
||||
INTEGRATION_TEST_APP_NAME="$APP_NAME" \
|
||||
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
|
||||
./scripts/test-remote-integration.sh
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
|
@ -114,33 +164,55 @@ steps:
|
|||
continueOnError: true
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- 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"
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Browser, Chromium)
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
yarn --cwd test/smoke compile
|
||||
displayName: Compile smoke tests
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
yarn smoketest-no-compile --tracing --build "$APP_PATH"
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Electron)
|
||||
- script: |
|
||||
set -e
|
||||
yarn smoketest-no-compile --tracing
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Electron)
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
yarn gulp compile-extension:vscode-test-resolver
|
||||
APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
|
||||
yarn smoketest-no-compile --tracing --remote --build "$APP_PATH"
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Remote)
|
||||
- script: |
|
||||
set -e
|
||||
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
|
||||
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"
|
||||
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"
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Browser, Chromium)
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
yarn gulp compile-extension:vscode-test-resolver
|
||||
APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)
|
||||
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
|
||||
yarn smoketest-no-compile --tracing --remote --build "$APP_PATH"
|
||||
timeoutInMinutes: 20
|
||||
displayName: Run smoke tests (Remote)
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- script: |
|
||||
set -e
|
||||
ps -ef
|
||||
|
@ -164,7 +236,6 @@ steps:
|
|||
continueOnError: true
|
||||
condition: failed()
|
||||
|
||||
- ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}:
|
||||
# In order to properly symbolify above crash reports
|
||||
# (if any), we need the compiled native modules too
|
||||
- task: PublishPipelineArtifact@0
|
||||
|
@ -180,7 +251,6 @@ steps:
|
|||
continueOnError: true
|
||||
condition: failed()
|
||||
|
||||
- ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}:
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
targetPath: .build/logs
|
||||
|
|
|
@ -11,6 +11,11 @@ parameters:
|
|||
type: boolean
|
||||
|
||||
steps:
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
retryCountOnTaskFailure: 3
|
||||
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "16.x"
|
||||
|
@ -23,33 +28,37 @@ steps:
|
|||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
artifact: Compilation
|
||||
path: $(Build.ArtifactStagingDirectory)
|
||||
displayName: Download compilation output
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
artifact: Compilation
|
||||
path: $(Build.ArtifactStagingDirectory)
|
||||
displayName: Download compilation output
|
||||
|
||||
- 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'))
|
||||
- ${{ 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: |
|
||||
set -e
|
||||
# Start X server
|
||||
/etc/init.d/xvfb start
|
||||
# Start dbus session
|
||||
DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address)
|
||||
echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT"
|
||||
displayName: Setup system services
|
||||
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'))
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
# Start X server
|
||||
/etc/init.d/xvfb start
|
||||
# Start dbus session
|
||||
DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address)
|
||||
echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT"
|
||||
displayName: Setup system services
|
||||
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'))
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz
|
||||
displayName: Extract compilation output
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz
|
||||
displayName: Extract compilation output
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
|
@ -169,12 +178,13 @@ steps:
|
|||
displayName: Install dependencies
|
||||
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
|
||||
|
||||
- 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'))
|
||||
- ${{ 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: |
|
||||
set -e
|
||||
|
@ -190,11 +200,12 @@ steps:
|
|||
node build/azure-pipelines/mixin
|
||||
displayName: Mix in quality
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \
|
||||
yarn gulp vscode-linux-$(VSCODE_ARCH)-min-ci
|
||||
displayName: Build
|
||||
- ${{ 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
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
|
@ -202,17 +213,26 @@ steps:
|
|||
node build/azure-pipelines/mixin --server
|
||||
displayName: Mix in server quality
|
||||
|
||||
- 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
|
||||
- ${{ 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
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)" \
|
||||
yarn gulp "transpile-client" "transpile-extensions"
|
||||
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
|
||||
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 }}
|
||||
|
|
59
build/azure-pipelines/product-build-pr-cache.yml
Normal file
59
build/azure-pipelines/product-build-pr-cache.yml
Normal file
|
@ -0,0 +1,59 @@
|
|||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
retryCountOnTaskFailure: 3
|
||||
|
||||
- task: NodeTool@0
|
||||
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
|
||||
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: "genericNodeModules | $(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 && 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
|
|
@ -6,15 +6,6 @@ pr:
|
|||
branches:
|
||||
include: ["main", "release/*"]
|
||||
|
||||
resources:
|
||||
containers:
|
||||
- container: centos7-devtoolset8-x64
|
||||
image: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-x64
|
||||
options: --user 0:0 --cap-add SYS_ADMIN
|
||||
- container: vscode-bionic-x64
|
||||
image: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-x64
|
||||
options: --user 0:0 --cap-add SYS_ADMIN
|
||||
|
||||
variables:
|
||||
- name: Codeql.SkipTaskAutoInjection
|
||||
value: true
|
||||
|
@ -22,6 +13,8 @@ variables:
|
|||
value: true
|
||||
- name: ENABLE_TERRAPIN
|
||||
value: false
|
||||
- name: VSCODE_CIBUILD
|
||||
value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }}
|
||||
- name: VSCODE_PUBLISH
|
||||
value: false
|
||||
- name: VSCODE_QUALITY
|
||||
|
@ -30,172 +23,173 @@ variables:
|
|||
value: false
|
||||
|
||||
stages:
|
||||
- stage: Compile
|
||||
jobs:
|
||||
- job: Compile
|
||||
pool: vscode-1es-vscode-linux-18.04
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: product-compile.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
- ${{ if eq(variables['VSCODE_CIBUILD'], true) }}:
|
||||
- stage: MaintainNodeModulesCache
|
||||
displayName: Maintain node_modules cache
|
||||
jobs:
|
||||
- job: MaintainNodeModulesCache
|
||||
displayName: Maintain node_modules cache
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
steps:
|
||||
- template: product-build-pr-cache.yml
|
||||
|
||||
- stage: LinuxServerDependencies
|
||||
dependsOn: []
|
||||
pool: vscode-1es-vscode-linux-18.04
|
||||
jobs:
|
||||
- job: x64
|
||||
container: centos7-devtoolset8-x64
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
NPM_ARCH: x64
|
||||
steps:
|
||||
- template: linux/product-build-linux-server.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
- ${{ if ne(variables['VSCODE_CIBUILD'], true) }}:
|
||||
- stage: Compile
|
||||
displayName: Compile & Hygiene
|
||||
jobs:
|
||||
- job: Compile
|
||||
displayName: Compile & Hygiene
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
variables:
|
||||
VSCODE_ARCH: x64
|
||||
steps:
|
||||
- template: product-compile.yml
|
||||
parameters:
|
||||
VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }}
|
||||
|
||||
- stage: Windows
|
||||
dependsOn:
|
||||
- Compile
|
||||
pool: vscode-1es-vscode-windows-2019
|
||||
jobs:
|
||||
- job: WindowsUnitTests
|
||||
displayName: Unit Tests
|
||||
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: true
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: WindowsIntegrationTests
|
||||
displayName: Integration Tests
|
||||
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: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: true
|
||||
VSCODE_RUN_SMOKE_TESTS: false
|
||||
- job: WindowsSmokeTests
|
||||
displayName: Smoke Tests
|
||||
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: false
|
||||
VSCODE_RUN_INTEGRATION_TESTS: false
|
||||
VSCODE_RUN_SMOKE_TESTS: true
|
||||
- stage: Test
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- job: Linuxx64UnitTest
|
||||
displayName: Linux (Unit Tests)
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
# container: vscode-bionic-x64
|
||||
timeoutInMinutes: 60
|
||||
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: Linux (Integration Tests)
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
# container: vscode-bionic-x64
|
||||
timeoutInMinutes: 60
|
||||
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: Linux (Smoke Tests)
|
||||
pool: vscode-1es-vscode-linux-20.04
|
||||
# container: vscode-bionic-x64
|
||||
timeoutInMinutes: 60
|
||||
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
|
||||
|
||||
- stage: Linux
|
||||
dependsOn:
|
||||
- Compile
|
||||
- LinuxServerDependencies
|
||||
pool: vscode-1es-vscode-linux-18.04
|
||||
jobs:
|
||||
- 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
|
||||
# - job: macOSUnitTest
|
||||
# displayName: macOS (Unit Tests)
|
||||
# pool:
|
||||
# vmImage: macOS-latest
|
||||
# timeoutInMinutes: 60
|
||||
# variables:
|
||||
# BUILDSECMON_OPT_IN: true
|
||||
# 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: macOS (Integration Tests)
|
||||
# pool:
|
||||
# vmImage: macOS-latest
|
||||
# timeoutInMinutes: 60
|
||||
# variables:
|
||||
# BUILDSECMON_OPT_IN: true
|
||||
# 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: macOS (Smoke Tests)
|
||||
# pool:
|
||||
# vmImage: macOS-latest
|
||||
# timeoutInMinutes: 60
|
||||
# variables:
|
||||
# BUILDSECMON_OPT_IN: true
|
||||
# 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
|
||||
pool:
|
||||
vmImage: macOS-latest
|
||||
variables:
|
||||
BUILDSECMON_OPT_IN: true
|
||||
jobs:
|
||||
- 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
|
||||
# - 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
|
||||
# VSCODE_RUN_SMOKE_TESTS: true
|
||||
|
|
|
@ -116,12 +116,13 @@ steps:
|
|||
GITHUB_TOKEN: "$(github-distro-mixin-password)"
|
||||
displayName: Compile & Hygiene
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
yarn --cwd test/smoke compile
|
||||
yarn --cwd test/integration/browser compile
|
||||
displayName: Compile test suites
|
||||
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- script: |
|
||||
set -e
|
||||
yarn --cwd test/smoke compile
|
||||
yarn --cwd test/integration/browser compile
|
||||
displayName: Compile test suites
|
||||
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- task: AzureCLI@2
|
||||
|
@ -151,16 +152,18 @@ steps:
|
|||
./build/azure-pipelines/common/extract-telemetry.sh
|
||||
displayName: Extract Telemetry
|
||||
|
||||
- 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
|
||||
displayName: Compress compilation artifact
|
||||
- ${{ 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
|
||||
displayName: Compress compilation artifact
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz
|
||||
artifactName: Compilation
|
||||
displayName: Publish 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: |
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
parameters:
|
||||
- name: VSCODE_QUALITY
|
||||
type: string
|
||||
- name: VSCODE_RUN_UNIT_TESTS
|
||||
type: boolean
|
||||
- name: VSCODE_RUN_INTEGRATION_TESTS
|
||||
|
@ -15,29 +17,51 @@ steps:
|
|||
displayName: Download Electron and Playwright
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { yarn electron $(VSCODE_ARCH) }
|
||||
exec { .\scripts\test.bat --build --tfs "Unit Tests" }
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
- ${{ 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" }
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { yarn test-node --build }
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { yarn test-node }
|
||||
displayName: Run unit tests (node.js)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}:
|
||||
- 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)
|
||||
timeoutInMinutes: 20
|
||||
- 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)
|
||||
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" }
|
||||
displayName: Run unit tests (Electron)
|
||||
timeoutInMinutes: 15
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { 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)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- powershell: |
|
||||
|
@ -64,38 +88,58 @@ steps:
|
|||
}
|
||||
displayName: Build integration tests
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- powershell: |
|
||||
# Figure out the full absolute path of the product we just built
|
||||
# including the remote server and configure the integration tests
|
||||
# to run with these builds instead of running out of sources.
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)"
|
||||
$AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json
|
||||
$AppNameShort = $AppProductJson.nameShort
|
||||
exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" }
|
||||
displayName: Run integration tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { .\scripts\test-integration.bat --tfs "Integration Tests" }
|
||||
displayName: Run integration tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\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-web-integration.bat --browser firefox }
|
||||
displayName: Run integration tests (Browser, Firefox)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true) }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)"
|
||||
$AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json
|
||||
$AppNameShort = $AppProductJson.nameShort
|
||||
exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-remote-integration.bat }
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { .\scripts\test-remote-integration.bat }
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- powershell: |
|
||||
# Figure out the full absolute path of the product we just built
|
||||
# including the remote server and configure the integration tests
|
||||
# to run with these builds instead of running out of sources.
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)"
|
||||
$AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json
|
||||
$AppNameShort = $AppProductJson.nameShort
|
||||
exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-integration.bat --build --tfs "Integration Tests" }
|
||||
displayName: Run integration tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)"; .\scripts\test-web-integration.bat --browser firefox }
|
||||
displayName: Run integration tests (Browser, Firefox)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)"
|
||||
$AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json
|
||||
$AppNameShort = $AppProductJson.nameShort
|
||||
exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\scripts\test-remote-integration.bat }
|
||||
displayName: Run integration tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- powershell: |
|
||||
|
@ -105,36 +149,47 @@ steps:
|
|||
continueOnError: true
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- 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 }
|
||||
displayName: Run smoke tests (Browser, Chromium)
|
||||
timeoutInMinutes: 20
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { yarn --cwd test/smoke compile }
|
||||
displayName: Compile smoke tests
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)"
|
||||
exec { yarn smoketest-no-compile --tracing --build "$AppRoot" }
|
||||
displayName: Run smoke tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { yarn smoketest-no-compile --tracing }
|
||||
displayName: Run smoke tests (Electron)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- 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" }
|
||||
displayName: Run smoke tests (Remote)
|
||||
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" }
|
||||
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 }
|
||||
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" }
|
||||
displayName: Run smoke tests (Remote)
|
||||
timeoutInMinutes: 20
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_RUN_SMOKE_TESTS, true) }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
exec {.\build\azure-pipelines\win32\listprocesses.bat }
|
||||
|
@ -156,7 +211,6 @@ steps:
|
|||
continueOnError: true
|
||||
condition: failed()
|
||||
|
||||
- ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}:
|
||||
# In order to properly symbolify above crash reports
|
||||
# (if any), we need the compiled native modules too
|
||||
- task: PublishPipelineArtifact@0
|
||||
|
@ -172,7 +226,6 @@ steps:
|
|||
continueOnError: true
|
||||
condition: failed()
|
||||
|
||||
- ${{ if or(eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}:
|
||||
- task: PublishPipelineArtifact@0
|
||||
inputs:
|
||||
targetPath: .build\logs
|
||||
|
|
|
@ -11,6 +11,11 @@ parameters:
|
|||
type: boolean
|
||||
|
||||
steps:
|
||||
- ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
retryCountOnTaskFailure: 3
|
||||
|
||||
- task: NodeTool@0
|
||||
inputs:
|
||||
versionSpec: "16.x"
|
||||
|
@ -28,17 +33,19 @@ steps:
|
|||
KeyVaultName: vscode
|
||||
SecretsFilter: "github-distro-mixin-password,ESRP-PKI,esrp-aad-username,esrp-aad-password"
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
artifact: Compilation
|
||||
path: $(Build.ArtifactStagingDirectory)
|
||||
displayName: Download compilation output
|
||||
- ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
inputs:
|
||||
artifact: Compilation
|
||||
path: $(Build.ArtifactStagingDirectory)
|
||||
displayName: Download compilation output
|
||||
|
||||
- task: ExtractFiles@1
|
||||
displayName: Extract compilation output
|
||||
inputs:
|
||||
archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz"
|
||||
cleanDestinationFolder: false
|
||||
- ${{ 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: |
|
||||
|
@ -69,6 +76,7 @@ steps:
|
|||
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
|
||||
|
@ -127,20 +135,29 @@ steps:
|
|||
exec { node build/azure-pipelines/mixin }
|
||||
displayName: Mix in quality
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { node build\lib\policies }
|
||||
displayName: Generate Group Policy definitions
|
||||
condition: and(succeeded(), ne(variables['VSCODE_PUBLISH'], 'false'))
|
||||
- ${{ if eq(parameters.VSCODE_PUBLISH, true) }}:
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
exec { node build\lib\policies }
|
||||
displayName: Generate Group Policy definitions
|
||||
|
||||
- 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)"
|
||||
displayName: Build
|
||||
- ${{ 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" }
|
||||
displayName: Transpile
|
||||
|
||||
- ${{ 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-win32-$(VSCODE_ARCH)-min-ci" }
|
||||
echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(agent.builddirectory)/VSCode-win32-$(VSCODE_ARCH)"
|
||||
displayName: Build
|
||||
|
||||
- ${{ if eq(parameters.VSCODE_PUBLISH, true) }}:
|
||||
- powershell: |
|
||||
|
@ -158,19 +175,21 @@ steps:
|
|||
displayName: Mix in quality
|
||||
condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64'))
|
||||
|
||||
- powershell: |
|
||||
. build/azure-pipelines/win32/exec.ps1
|
||||
$ErrorActionPreference = "Stop"
|
||||
$env:VSCODE_MIXIN_PASSWORD="$(github-distro-mixin-password)"
|
||||
exec { yarn gulp "vscode-reh-win32-$(VSCODE_ARCH)-min-ci" }
|
||||
exec { yarn gulp "vscode-reh-web-win32-$(VSCODE_ARCH)-min-ci" }
|
||||
echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(agent.builddirectory)/vscode-reh-win32-$(VSCODE_ARCH)"
|
||||
displayName: Build Server
|
||||
condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64'))
|
||||
- ${{ 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
|
||||
condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64'))
|
||||
|
||||
- ${{ if or(eq(parameters.VSCODE_RUN_UNIT_TESTS, true), eq(parameters.VSCODE_RUN_INTEGRATION_TESTS, true), eq(parameters.VSCODE_RUN_SMOKE_TESTS, true)) }}:
|
||||
- template: product-build-win32-test.yml
|
||||
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 }}
|
||||
|
|
|
@ -16,6 +16,9 @@ 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 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);
|
||||
|
@ -74,12 +77,16 @@ function prepareDebPackage(arch) {
|
|||
let size = 0;
|
||||
const control = code.pipe(es.through(
|
||||
function (f) { size += f.isDirectory() ? 4096 : f.contents.length; },
|
||||
function () {
|
||||
async function () {
|
||||
const that = this;
|
||||
const sysroot = await sysrootInstaller.getSysroot(debArch);
|
||||
const dependencies = debianDependenciesGenerator.getDependencies(binaryDir, product.applicationName, debArch, sysroot);
|
||||
gulp.src('resources/linux/debian/control.template', { base: '.' })
|
||||
.pipe(replace('@@NAME@@', product.applicationName))
|
||||
.pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision))
|
||||
.pipe(replace('@@ARCHITECTURE@@', debArch))
|
||||
.pipe(replace('@@DEPENDS@@', dependencies.join(', ')))
|
||||
.pipe(replace('@@RECOMMENDS@@', debianRecommendedDependencies.join(', ')))
|
||||
.pipe(replace('@@INSTALLEDSIZE@@', Math.ceil(size / 1024)))
|
||||
.pipe(rename('DEBIAN/control'))
|
||||
.pipe(es.through(function (f) { that.emit('data', f); }, function () { that.emit('end'); }));
|
||||
|
|
|
@ -188,6 +188,15 @@ class MonacoGenerator {
|
|||
}
|
||||
}
|
||||
function generateApiProposalNames() {
|
||||
let eol;
|
||||
try {
|
||||
const src = fs.readFileSync('src/vs/workbench/services/extensions/common/extensionsApiProposals.ts', 'utf-8');
|
||||
const match = /\r?\n/m.exec(src);
|
||||
eol = match ? match[0] : os.EOL;
|
||||
}
|
||||
catch {
|
||||
eol = os.EOL;
|
||||
}
|
||||
const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/;
|
||||
const proposalNames = new Set();
|
||||
const input = es.through();
|
||||
|
@ -214,7 +223,7 @@ function generateApiProposalNames() {
|
|||
'});',
|
||||
'export type ApiProposalName = keyof typeof allApiProposals;',
|
||||
'',
|
||||
].join(os.EOL);
|
||||
].join(eol);
|
||||
this.emit('data', new File({
|
||||
path: 'vs/workbench/services/extensions/common/extensionsApiProposals.ts',
|
||||
contents: Buffer.from(contents)
|
||||
|
|
|
@ -226,6 +226,16 @@ class MonacoGenerator {
|
|||
}
|
||||
|
||||
function generateApiProposalNames() {
|
||||
let eol: string;
|
||||
|
||||
try {
|
||||
const src = fs.readFileSync('src/vs/workbench/services/extensions/common/extensionsApiProposals.ts', 'utf-8');
|
||||
const match = /\r?\n/m.exec(src);
|
||||
eol = match ? match[0] : os.EOL;
|
||||
} catch {
|
||||
eol = os.EOL;
|
||||
}
|
||||
|
||||
const pattern = /vscode\.proposed\.([a-zA-Z]+)\.d\.ts$/;
|
||||
const proposalNames = new Set<string>();
|
||||
|
||||
|
@ -254,7 +264,7 @@ function generateApiProposalNames() {
|
|||
'});',
|
||||
'export type ApiProposalName = keyof typeof allApiProposals;',
|
||||
'',
|
||||
].join(os.EOL);
|
||||
].join(eol);
|
||||
|
||||
this.emit('data', new File({
|
||||
path: 'vs/workbench/services/extensions/common/extensionsApiProposals.ts',
|
||||
|
|
|
@ -89,6 +89,8 @@ function discoverAndReadFiles(ts, options) {
|
|||
const in_queue = Object.create(null);
|
||||
const queue = [];
|
||||
const enqueue = (moduleId) => {
|
||||
// To make the treeshaker work on windows...
|
||||
moduleId = moduleId.replace(/\\/g, '/');
|
||||
if (in_queue[moduleId]) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -142,6 +142,8 @@ function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeSha
|
|||
const queue: string[] = [];
|
||||
|
||||
const enqueue = (moduleId: string) => {
|
||||
// To make the treeshaker work on windows...
|
||||
moduleId = moduleId.replace(/\\/g, '/');
|
||||
if (in_queue[moduleId]) {
|
||||
return;
|
||||
}
|
||||
|
|
156
build/linux/debian/dep-lists.js
Normal file
156
build/linux/debian/dep-lists.js
Normal file
|
@ -0,0 +1,156 @@
|
|||
"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.referenceGeneratedDepsByArch = exports.bundledDeps = 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 = [
|
||||
'ca-certificates',
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3',
|
||||
'xdg-utils (>= 1.0.2)' // OS integration
|
||||
];
|
||||
// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/manual_recommends
|
||||
// Dependencies that we can only recommend
|
||||
// for now since some of the older distros don't support them.
|
||||
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)',
|
||||
'libatk-bridge2.0-0 (>= 2.5.3)',
|
||||
'libatk1.0-0 (>= 2.2.0)',
|
||||
'libatspi2.0-0 (>= 2.9.90)',
|
||||
'libc6 (>= 2.14)',
|
||||
'libc6 (>= 2.17)',
|
||||
'libc6 (>= 2.2.5)',
|
||||
'libcairo2 (>= 1.6.0)',
|
||||
'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3',
|
||||
'libdbus-1-3 (>= 1.5.12)',
|
||||
'libdrm2 (>= 2.4.38)',
|
||||
'libexpat1 (>= 2.0.1)',
|
||||
'libgbm1 (>= 8.1~0)',
|
||||
'libgcc1 (>= 1:3.0)',
|
||||
'libglib2.0-0 (>= 2.16.0)',
|
||||
'libglib2.0-0 (>= 2.39.4)',
|
||||
'libgtk-3-0 (>= 3.9.10)',
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnspr4 (>= 2:4.9-2~)',
|
||||
'libnss3 (>= 2:3.22)',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libpango-1.0-0 (>= 1.14.0)',
|
||||
'libsecret-1-0 (>= 0.18)',
|
||||
'libx11-6',
|
||||
'libx11-6 (>= 2:1.4.99.1)',
|
||||
'libxcb1 (>= 1.9.2)',
|
||||
'libxcomposite1 (>= 1:0.4.4-1)',
|
||||
'libxdamage1 (>= 1:1.1)',
|
||||
'libxext6',
|
||||
'libxfixes3',
|
||||
'libxkbcommon0 (>= 0.4.1)',
|
||||
'libxkbfile1',
|
||||
'libxrandr2',
|
||||
'xdg-utils (>= 1.0.2)'
|
||||
],
|
||||
'armhf': [
|
||||
'ca-certificates',
|
||||
'libasound2 (>= 1.0.16)',
|
||||
'libatk-bridge2.0-0 (>= 2.5.3)',
|
||||
'libatk1.0-0 (>= 2.2.0)',
|
||||
'libatspi2.0-0 (>= 2.9.90)',
|
||||
'libc6 (>= 2.17)',
|
||||
'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)',
|
||||
'libexpat1 (>= 2.0.1)',
|
||||
'libgbm1 (>= 8.1~0)',
|
||||
'libgcc1 (>= 1:3.0)',
|
||||
'libgcc1 (>= 1:3.5)',
|
||||
'libglib2.0-0 (>= 2.16.0)',
|
||||
'libglib2.0-0 (>= 2.39.4)',
|
||||
'libgtk-3-0 (>= 3.9.10)',
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnspr4 (>= 2:4.9-2~)',
|
||||
'libnss3 (>= 2:3.22)',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libpango-1.0-0 (>= 1.14.0)',
|
||||
'libsecret-1-0 (>= 0.18)',
|
||||
'libstdc++6 (>= 4.1.1)',
|
||||
'libstdc++6 (>= 5)',
|
||||
'libstdc++6 (>= 5.2)',
|
||||
'libstdc++6 (>= 6)',
|
||||
'libx11-6',
|
||||
'libx11-6 (>= 2:1.4.99.1)',
|
||||
'libxcb1 (>= 1.9.2)',
|
||||
'libxcomposite1 (>= 1:0.4.4-1)',
|
||||
'libxdamage1 (>= 1:1.1)',
|
||||
'libxext6',
|
||||
'libxfixes3',
|
||||
'libxkbcommon0 (>= 0.4.1)',
|
||||
'libxkbfile1',
|
||||
'libxrandr2',
|
||||
'xdg-utils (>= 1.0.2)'
|
||||
],
|
||||
'arm64': [
|
||||
'ca-certificates',
|
||||
'libasound2 (>= 1.0.16)',
|
||||
'libatk-bridge2.0-0 (>= 2.5.3)',
|
||||
'libatk1.0-0 (>= 2.2.0)',
|
||||
'libatspi2.0-0 (>= 2.9.90)',
|
||||
'libc6 (>= 2.17)',
|
||||
'libcairo2 (>= 1.6.0)',
|
||||
'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3',
|
||||
'libdbus-1-3 (>= 1.0.2)',
|
||||
'libdrm2 (>= 2.4.38)',
|
||||
'libexpat1 (>= 2.0.1)',
|
||||
'libgbm1 (>= 8.1~0)',
|
||||
'libgcc1 (>= 1:3.0)',
|
||||
'libgcc1 (>= 1:4.2)',
|
||||
'libgcc1 (>= 1:4.5)',
|
||||
'libglib2.0-0 (>= 2.16.0)',
|
||||
'libglib2.0-0 (>= 2.39.4)',
|
||||
'libgtk-3-0 (>= 3.9.10)',
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnspr4 (>= 2:4.9-2~)',
|
||||
'libnss3 (>= 2:3.22)',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libpango-1.0-0 (>= 1.14.0)',
|
||||
'libsecret-1-0 (>= 0.18)',
|
||||
'libstdc++6 (>= 4.1.1)',
|
||||
'libstdc++6 (>= 5)',
|
||||
'libstdc++6 (>= 5.2)',
|
||||
'libstdc++6 (>= 6)',
|
||||
'libx11-6',
|
||||
'libx11-6 (>= 2:1.4.99.1)',
|
||||
'libxcb1 (>= 1.9.2)',
|
||||
'libxcomposite1 (>= 1:0.4.4-1)',
|
||||
'libxdamage1 (>= 1:1.1)',
|
||||
'libxext6',
|
||||
'libxfixes3',
|
||||
'libxkbcommon0 (>= 0.4.1)',
|
||||
'libxkbfile1',
|
||||
'libxrandr2',
|
||||
'xdg-utils (>= 1.0.2)'
|
||||
]
|
||||
};
|
157
build/linux/debian/dep-lists.ts
Normal file
157
build/linux/debian/dep-lists.ts
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps
|
||||
// Additional dependencies not in the dpkg-shlibdeps output.
|
||||
export const additionalDeps = [
|
||||
'ca-certificates', // Make sure users have SSL certificates.
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', // For Breakpad crash reports.
|
||||
'xdg-utils (>= 1.0.2)' // OS integration
|
||||
];
|
||||
|
||||
// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/manual_recommends
|
||||
// Dependencies that we can only recommend
|
||||
// for now since some of the older distros don't support them.
|
||||
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)',
|
||||
'libatk-bridge2.0-0 (>= 2.5.3)',
|
||||
'libatk1.0-0 (>= 2.2.0)',
|
||||
'libatspi2.0-0 (>= 2.9.90)',
|
||||
'libc6 (>= 2.14)',
|
||||
'libc6 (>= 2.17)',
|
||||
'libc6 (>= 2.2.5)',
|
||||
'libcairo2 (>= 1.6.0)',
|
||||
'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3',
|
||||
'libdbus-1-3 (>= 1.5.12)',
|
||||
'libdrm2 (>= 2.4.38)',
|
||||
'libexpat1 (>= 2.0.1)',
|
||||
'libgbm1 (>= 8.1~0)',
|
||||
'libgcc1 (>= 1:3.0)',
|
||||
'libglib2.0-0 (>= 2.16.0)',
|
||||
'libglib2.0-0 (>= 2.39.4)',
|
||||
'libgtk-3-0 (>= 3.9.10)',
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnspr4 (>= 2:4.9-2~)',
|
||||
'libnss3 (>= 2:3.22)',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libpango-1.0-0 (>= 1.14.0)',
|
||||
'libsecret-1-0 (>= 0.18)',
|
||||
'libx11-6',
|
||||
'libx11-6 (>= 2:1.4.99.1)',
|
||||
'libxcb1 (>= 1.9.2)',
|
||||
'libxcomposite1 (>= 1:0.4.4-1)',
|
||||
'libxdamage1 (>= 1:1.1)',
|
||||
'libxext6',
|
||||
'libxfixes3',
|
||||
'libxkbcommon0 (>= 0.4.1)',
|
||||
'libxkbfile1',
|
||||
'libxrandr2',
|
||||
'xdg-utils (>= 1.0.2)'
|
||||
],
|
||||
'armhf': [
|
||||
'ca-certificates',
|
||||
'libasound2 (>= 1.0.16)',
|
||||
'libatk-bridge2.0-0 (>= 2.5.3)',
|
||||
'libatk1.0-0 (>= 2.2.0)',
|
||||
'libatspi2.0-0 (>= 2.9.90)',
|
||||
'libc6 (>= 2.17)',
|
||||
'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)',
|
||||
'libexpat1 (>= 2.0.1)',
|
||||
'libgbm1 (>= 8.1~0)',
|
||||
'libgcc1 (>= 1:3.0)',
|
||||
'libgcc1 (>= 1:3.5)',
|
||||
'libglib2.0-0 (>= 2.16.0)',
|
||||
'libglib2.0-0 (>= 2.39.4)',
|
||||
'libgtk-3-0 (>= 3.9.10)',
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnspr4 (>= 2:4.9-2~)',
|
||||
'libnss3 (>= 2:3.22)',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libpango-1.0-0 (>= 1.14.0)',
|
||||
'libsecret-1-0 (>= 0.18)',
|
||||
'libstdc++6 (>= 4.1.1)',
|
||||
'libstdc++6 (>= 5)',
|
||||
'libstdc++6 (>= 5.2)',
|
||||
'libstdc++6 (>= 6)',
|
||||
'libx11-6',
|
||||
'libx11-6 (>= 2:1.4.99.1)',
|
||||
'libxcb1 (>= 1.9.2)',
|
||||
'libxcomposite1 (>= 1:0.4.4-1)',
|
||||
'libxdamage1 (>= 1:1.1)',
|
||||
'libxext6',
|
||||
'libxfixes3',
|
||||
'libxkbcommon0 (>= 0.4.1)',
|
||||
'libxkbfile1',
|
||||
'libxrandr2',
|
||||
'xdg-utils (>= 1.0.2)'
|
||||
],
|
||||
'arm64': [
|
||||
'ca-certificates',
|
||||
'libasound2 (>= 1.0.16)',
|
||||
'libatk-bridge2.0-0 (>= 2.5.3)',
|
||||
'libatk1.0-0 (>= 2.2.0)',
|
||||
'libatspi2.0-0 (>= 2.9.90)',
|
||||
'libc6 (>= 2.17)',
|
||||
'libcairo2 (>= 1.6.0)',
|
||||
'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3',
|
||||
'libdbus-1-3 (>= 1.0.2)',
|
||||
'libdrm2 (>= 2.4.38)',
|
||||
'libexpat1 (>= 2.0.1)',
|
||||
'libgbm1 (>= 8.1~0)',
|
||||
'libgcc1 (>= 1:3.0)',
|
||||
'libgcc1 (>= 1:4.2)',
|
||||
'libgcc1 (>= 1:4.5)',
|
||||
'libglib2.0-0 (>= 2.16.0)',
|
||||
'libglib2.0-0 (>= 2.39.4)',
|
||||
'libgtk-3-0 (>= 3.9.10)',
|
||||
'libgtk-3-0 (>= 3.9.10) | libgtk-4-1',
|
||||
'libnspr4 (>= 2:4.9-2~)',
|
||||
'libnss3 (>= 2:3.22)',
|
||||
'libnss3 (>= 3.26)',
|
||||
'libpango-1.0-0 (>= 1.14.0)',
|
||||
'libsecret-1-0 (>= 0.18)',
|
||||
'libstdc++6 (>= 4.1.1)',
|
||||
'libstdc++6 (>= 5)',
|
||||
'libstdc++6 (>= 5.2)',
|
||||
'libstdc++6 (>= 6)',
|
||||
'libx11-6',
|
||||
'libx11-6 (>= 2:1.4.99.1)',
|
||||
'libxcb1 (>= 1.9.2)',
|
||||
'libxcomposite1 (>= 1:0.4.4-1)',
|
||||
'libxdamage1 (>= 1:1.1)',
|
||||
'libxext6',
|
||||
'libxfixes3',
|
||||
'libxkbcommon0 (>= 0.4.1)',
|
||||
'libxkbfile1',
|
||||
'libxrandr2',
|
||||
'xdg-utils (>= 1.0.2)'
|
||||
]
|
||||
};
|
129
build/linux/debian/dependencies-generator.js
Normal file
129
build/linux/debian/dependencies-generator.js
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'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");
|
||||
// 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 = true;
|
||||
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));
|
||||
});
|
||||
const referenceGeneratedDeps = dep_lists_1.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;
|
||||
}
|
145
build/linux/debian/dependencies-generator.ts
Normal file
145
build/linux/debian/dependencies-generator.ts
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. 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';
|
||||
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;
|
||||
|
||||
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<string>[] = 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));
|
||||
});
|
||||
|
||||
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<string> {
|
||||
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<string>[]): Set<string> {
|
||||
// For now, see if directly appending the dependencies helps.
|
||||
const requires = new Set<string>();
|
||||
for (const depSet of inputDeps) {
|
||||
for (const dep of depSet) {
|
||||
const trimmedDependency = dep.trim();
|
||||
if (trimmedDependency.length && !trimmedDependency.startsWith('#')) {
|
||||
requires.add(trimmedDependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
return requires;
|
||||
}
|
81
build/linux/debian/install-sysroot.js
Normal file
81
build/linux/debian/install-sysroot.js
Normal file
|
@ -0,0 +1,81 @@
|
|||
"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.getSysroot = void 0;
|
||||
const child_process_1 = require("child_process");
|
||||
const crypto_1 = require("crypto");
|
||||
const os_1 = require("os");
|
||||
const fs = require("fs");
|
||||
const https = require("https");
|
||||
const path = require("path");
|
||||
const sysroots_1 = require("./sysroots");
|
||||
// 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';
|
||||
function getSha(filename) {
|
||||
const hash = (0, crypto_1.createHash)('sha1');
|
||||
// Read file 1 MB at a time
|
||||
const fd = fs.openSync(filename, 'r');
|
||||
const buffer = Buffer.alloc(1024 * 1024);
|
||||
let position = 0;
|
||||
let bytesRead = 0;
|
||||
while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) {
|
||||
hash.update(buffer);
|
||||
position += bytesRead;
|
||||
}
|
||||
hash.update(buffer.slice(0, bytesRead));
|
||||
return hash.digest('hex');
|
||||
}
|
||||
async function getSysroot(arch) {
|
||||
const sysrootDict = sysroots_1.sysrootInfo[arch];
|
||||
const tarballFilename = sysrootDict['Tarball'];
|
||||
const tarballSha = sysrootDict['Sha1Sum'];
|
||||
const sysroot = path.join((0, os_1.tmpdir)(), sysrootDict['SysrootDir']);
|
||||
const url = [URL_PREFIX, URL_PATH, tarballSha, tarballFilename].join('/');
|
||||
const stamp = path.join(sysroot, '.stamp');
|
||||
if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) {
|
||||
return sysroot;
|
||||
}
|
||||
console.log(`Installing Debian ${arch} root image: ${sysroot}`);
|
||||
fs.rmSync(sysroot, { recursive: true, force: true });
|
||||
fs.mkdirSync(sysroot);
|
||||
const tarball = path.join(sysroot, tarballFilename);
|
||||
console.log(`Downloading ${url}`);
|
||||
let downloadSuccess = false;
|
||||
for (let i = 0; i < 3 && !downloadSuccess; i++) {
|
||||
await new Promise((c) => {
|
||||
https.get(url, (res) => {
|
||||
const chunks = [];
|
||||
res.on('data', (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
res.on('end', () => {
|
||||
fs.writeFileSync(tarball, Buffer.concat(chunks));
|
||||
downloadSuccess = true;
|
||||
c();
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
console.error('Encountered an error during the download attempt: ' + err.message);
|
||||
c();
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!downloadSuccess) {
|
||||
throw new Error('Failed to download ' + url);
|
||||
}
|
||||
const sha = getSha(tarball);
|
||||
if (sha !== tarballSha) {
|
||||
throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`);
|
||||
}
|
||||
const proc = (0, child_process_1.spawnSync)('tar', ['xf', tarball, '-C', sysroot]);
|
||||
if (proc.status) {
|
||||
throw new Error('Tarball extraction failed with code ' + proc.status);
|
||||
}
|
||||
fs.rmSync(tarball);
|
||||
fs.writeFileSync(stamp, url);
|
||||
return sysroot;
|
||||
}
|
||||
exports.getSysroot = getSysroot;
|
90
build/linux/debian/install-sysroot.ts
Normal file
90
build/linux/debian/install-sysroot.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { spawnSync } from 'child_process';
|
||||
import { createHash } from 'crypto';
|
||||
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';
|
||||
|
||||
// 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';
|
||||
|
||||
function getSha(filename: fs.PathLike): string {
|
||||
const hash = createHash('sha1');
|
||||
// Read file 1 MB at a time
|
||||
const fd = fs.openSync(filename, 'r');
|
||||
const buffer = Buffer.alloc(1024 * 1024);
|
||||
let position = 0;
|
||||
let bytesRead = 0;
|
||||
while ((bytesRead = fs.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) {
|
||||
hash.update(buffer);
|
||||
position += bytesRead;
|
||||
}
|
||||
hash.update(buffer.slice(0, bytesRead));
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
type SysrootDictEntry = {
|
||||
Sha1Sum: string;
|
||||
SysrootDir: string;
|
||||
Tarball: string;
|
||||
};
|
||||
|
||||
export async function getSysroot(arch: ArchString): Promise<string> {
|
||||
const sysrootDict: SysrootDictEntry = sysrootInfo[arch];
|
||||
const tarballFilename = sysrootDict['Tarball'];
|
||||
const tarballSha = sysrootDict['Sha1Sum'];
|
||||
const sysroot = path.join(tmpdir(), sysrootDict['SysrootDir']);
|
||||
const url = [URL_PREFIX, URL_PATH, tarballSha, tarballFilename].join('/');
|
||||
const stamp = path.join(sysroot, '.stamp');
|
||||
if (fs.existsSync(stamp) && fs.readFileSync(stamp).toString() === url) {
|
||||
return sysroot;
|
||||
}
|
||||
|
||||
console.log(`Installing Debian ${arch} root image: ${sysroot}`);
|
||||
fs.rmSync(sysroot, { recursive: true, force: true });
|
||||
fs.mkdirSync(sysroot);
|
||||
const tarball = path.join(sysroot, tarballFilename);
|
||||
console.log(`Downloading ${url}`);
|
||||
let downloadSuccess = false;
|
||||
for (let i = 0; i < 3 && !downloadSuccess; i++) {
|
||||
await new Promise<void>((c) => {
|
||||
https.get(url, (res) => {
|
||||
const chunks: Uint8Array[] = [];
|
||||
res.on('data', (chunk) => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
res.on('end', () => {
|
||||
fs.writeFileSync(tarball, Buffer.concat(chunks));
|
||||
downloadSuccess = true;
|
||||
c();
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
console.error('Encountered an error during the download attempt: ' + err.message);
|
||||
c();
|
||||
});
|
||||
});
|
||||
}
|
||||
if (!downloadSuccess) {
|
||||
throw new Error('Failed to download ' + url);
|
||||
}
|
||||
const sha = getSha(tarball);
|
||||
if (sha !== tarballSha) {
|
||||
throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`);
|
||||
}
|
||||
|
||||
const proc = spawnSync('tar', ['xf', tarball, '-C', sysroot]);
|
||||
if (proc.status) {
|
||||
throw new Error('Tarball extraction failed with code ' + proc.status);
|
||||
}
|
||||
fs.rmSync(tarball);
|
||||
fs.writeFileSync(stamp, url);
|
||||
return sysroot;
|
||||
}
|
26
build/linux/debian/sysroots.js
Normal file
26
build/linux/debian/sysroots.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
"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.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'
|
||||
}
|
||||
};
|
24
build/linux/debian/sysroots.ts
Normal file
24
build/linux/debian/sysroots.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
// 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'
|
||||
}
|
||||
};
|
6
build/linux/debian/types.js
Normal file
6
build/linux/debian/types.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
"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 });
|
6
build/linux/debian/types.ts
Normal file
6
build/linux/debian/types.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export type ArchString = 'amd64' | 'armhf' | 'arm64';
|
|
@ -81,7 +81,7 @@ function calculatePackageDeps(binaryPath) {
|
|||
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
|
||||
// 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) {
|
||||
|
|
|
@ -92,7 +92,7 @@ function calculatePackageDeps(binaryPath: string): Set<string> {
|
|||
return requires;
|
||||
}
|
||||
|
||||
// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py
|
||||
// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py.
|
||||
function mergePackageDeps(inputDeps: Set<string>[]): Set<string> {
|
||||
const requires = new Set<string>();
|
||||
for (const depSet of inputDeps) {
|
||||
|
|
|
@ -1494,7 +1494,7 @@ begin
|
|||
Permissions := '/grant:r "*S-1-5-18:(OI)(CI)F" /grant:r "*S-1-5-32-544:(OI)(CI)F" /grant:r "*S-1-5-11:(OI)(CI)RX" /grant:r "*S-1-5-32-545:(OI)(CI)RX"';
|
||||
|
||||
#if "user" == InstallTarget
|
||||
Permissions := Permissions + ' /grant:r "*S-1-3-0:(OI)(CI)F"';
|
||||
Permissions := Permissions + Format(' /grant:r "*S-1-3-0:(OI)(CI)F" /grant:r "%s:(OI)(CI)F"', [GetUserNameString()]);
|
||||
#endif
|
||||
|
||||
Exec(ExpandConstant('{sys}\icacls.exe'), ExpandConstant('"{app}" /inheritancelevel:r ') + Permissions, '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||
|
|
|
@ -40,8 +40,8 @@ function registerVariableCompletions(pattern: string): vscode.Disposable {
|
|||
provideCompletionItems(document, position, _token) {
|
||||
const location = getLocation(document.getText(), document.offsetAt(position));
|
||||
if (isCompletingInsidePropertyStringValue(document, location, position)) {
|
||||
let range = document.getWordRangeAtPosition(position, /\$\{[^\}]*\}/);
|
||||
if (!range || range.end.isEqual(position) || range.start.isEqual(position)) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -96,8 +96,8 @@ export class SettingsDocument {
|
|||
return completions;
|
||||
}
|
||||
|
||||
let range = this.document.getWordRangeAtPosition(pos, /\$\{[^\}]*\}/);
|
||||
if (!range || range.end.isEqual(pos) || range.start.isEqual(pos)) {
|
||||
let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/);
|
||||
if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) {
|
||||
range = new vscode.Range(pos, pos);
|
||||
}
|
||||
|
||||
|
|
|
@ -73,6 +73,20 @@ suite('Completions in settings.json', () => {
|
|||
const expected = { label: '${activeEditorMedium}', resultText };
|
||||
await testCompletion(testFile, 'jsonc', content, expected);
|
||||
}
|
||||
{ // replacing a partial variable
|
||||
const content = [
|
||||
'{',
|
||||
' "window.title": "${a|"',
|
||||
'}',
|
||||
].join('\n');
|
||||
const resultText = [
|
||||
'{',
|
||||
' "window.title": "${dirty}"',
|
||||
'}',
|
||||
].join('\n');
|
||||
const expected = { label: '${dirty}', resultText };
|
||||
await testCompletion(testFile, 'jsonc', content, expected);
|
||||
}
|
||||
{ // inserting a literal
|
||||
const content = [
|
||||
'{',
|
||||
|
@ -382,6 +396,32 @@ suite('Completions in launch.json', () => {
|
|||
const expected = { label: '${cwd}', resultText };
|
||||
await testCompletion(testFile, 'jsonc', content, expected);
|
||||
}
|
||||
{
|
||||
const content = [
|
||||
'{',
|
||||
' "version": "0.2.0",',
|
||||
' "configurations": [',
|
||||
' {',
|
||||
' "name": "Do It",',
|
||||
' "program": "${workspace|"',
|
||||
' }',
|
||||
' ]',
|
||||
'}',
|
||||
].join('\n');
|
||||
const resultText = [
|
||||
'{',
|
||||
' "version": "0.2.0",',
|
||||
' "configurations": [',
|
||||
' {',
|
||||
' "name": "Do It",',
|
||||
' "program": "${cwd}"',
|
||||
' }',
|
||||
' ]',
|
||||
'}',
|
||||
].join('\n');
|
||||
const expected = { label: '${cwd}', resultText };
|
||||
await testCompletion(testFile, 'jsonc', content, expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -601,7 +601,7 @@
|
|||
"command": "git.acceptMerge",
|
||||
"title": "%command.git.acceptMerge%",
|
||||
"category": "Git",
|
||||
"enablement": "isMergeEditor"
|
||||
"enablement": "isMergeEditor && mergeEditorResultUri in git.mergeChanges"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
|
@ -1549,7 +1549,7 @@
|
|||
"merge/toolbar": [
|
||||
{
|
||||
"command": "git.acceptMerge",
|
||||
"when": "isMergeEditor && baseResourceScheme =~ /^git$|^file$/"
|
||||
"when": "isMergeEditor && mergeEditorBaseUri =~ /^(git|file):/ && mergeEditorResultUri in git.mergeChanges"
|
||||
}
|
||||
],
|
||||
"scm/change/title": [
|
||||
|
@ -2522,7 +2522,7 @@
|
|||
},
|
||||
"git.mergeEditor": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"default": true,
|
||||
"markdownDescription": "%config.mergeEditor%",
|
||||
"scope": "window"
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ interface ActionButtonState {
|
|||
readonly HEAD: Branch | undefined;
|
||||
readonly isCommitInProgress: boolean;
|
||||
readonly isMergeInProgress: boolean;
|
||||
readonly isRebaseInProgress: boolean;
|
||||
readonly isSyncInProgress: boolean;
|
||||
readonly repositoryHasChangesToCommit: boolean;
|
||||
}
|
||||
|
@ -43,6 +44,7 @@ export class ActionButtonCommand {
|
|||
HEAD: undefined,
|
||||
isCommitInProgress: false,
|
||||
isMergeInProgress: false,
|
||||
isRebaseInProgress: false,
|
||||
isSyncInProgress: false,
|
||||
repositoryHasChangesToCommit: false
|
||||
};
|
||||
|
@ -70,7 +72,7 @@ export class ActionButtonCommand {
|
|||
}
|
||||
|
||||
get button(): SourceControlActionButton | undefined {
|
||||
if (!this.state.HEAD || !this.state.HEAD.name) { return undefined; }
|
||||
if (!this.state.HEAD) { return undefined; }
|
||||
|
||||
let actionButton: SourceControlActionButton | undefined;
|
||||
|
||||
|
@ -90,7 +92,25 @@ export class ActionButtonCommand {
|
|||
// The button is disabled
|
||||
if (!showActionButton.commit) { return undefined; }
|
||||
|
||||
let title: string, tooltip: string, commandArg: string;
|
||||
return {
|
||||
command: this.getCommitActionButtonPrimaryCommand(),
|
||||
secondaryCommands: this.getCommitActionButtonSecondaryCommands(),
|
||||
enabled: this.state.repositoryHasChangesToCommit && !this.state.isCommitInProgress && !this.state.isMergeInProgress
|
||||
};
|
||||
}
|
||||
|
||||
private getCommitActionButtonPrimaryCommand(): Command {
|
||||
let commandArg = '';
|
||||
let title = localize('scm button commit title', "{0} Commit", '$(check)');
|
||||
let tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes");
|
||||
|
||||
// Rebase Continue
|
||||
if (this.state.isRebaseInProgress) {
|
||||
return { command: 'git.commit', title, tooltip, arguments: [this.repository.sourceControl, commandArg] };
|
||||
}
|
||||
|
||||
// Commit
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const postCommitCommand = config.get<string>('postCommitCommand');
|
||||
|
||||
// Branch protection
|
||||
|
@ -133,49 +153,36 @@ export class ActionButtonCommand {
|
|||
break;
|
||||
}
|
||||
default: {
|
||||
commandArg = '';
|
||||
title = localize('scm button commit title', "{0} Commit", icon ?? '$(check)');
|
||||
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");
|
||||
} else {
|
||||
tooltip = this.state.isCommitInProgress ?
|
||||
localize('scm button committing tooltip', "Committing Changes...") :
|
||||
localize('scm button commit tooltip', "Commit Changes");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
command: {
|
||||
command: 'git.commit',
|
||||
title: title,
|
||||
tooltip: tooltip,
|
||||
arguments: [this.repository.sourceControl, commandArg],
|
||||
},
|
||||
secondaryCommands: this.getCommitActionButtonSecondaryCommands(),
|
||||
enabled: this.state.repositoryHasChangesToCommit && !this.state.isCommitInProgress && !this.state.isMergeInProgress
|
||||
};
|
||||
return { command: 'git.commit', title, tooltip, arguments: [this.repository.sourceControl, commandArg] };
|
||||
}
|
||||
|
||||
private getCommitActionButtonSecondaryCommands(): 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,
|
||||
arguments: [this.repository.sourceControl, c.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") });
|
||||
if (commandGroups.length > 0) {
|
||||
commandGroups[0].splice(0, 0, { command: 'git.commit', title: localize('scm secondary button commit', "Commit") });
|
||||
}
|
||||
}
|
||||
|
||||
return commandGroups;
|
||||
|
@ -185,8 +192,8 @@ 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 is in progress, or the button is disabled
|
||||
if (this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || !showActionButton.publish) { return undefined; }
|
||||
// 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; }
|
||||
|
||||
return {
|
||||
command: {
|
||||
|
@ -206,8 +213,8 @@ export class ActionButtonCommand {
|
|||
const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true });
|
||||
const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0;
|
||||
|
||||
// Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge is in progress, or the button is disabled
|
||||
if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || !showActionButton.sync) { return undefined; }
|
||||
// Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge/rebase is in progress, or the button is disabled
|
||||
if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.sync) { return undefined; }
|
||||
|
||||
const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : '';
|
||||
const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : '';
|
||||
|
@ -229,7 +236,8 @@ export class ActionButtonCommand {
|
|||
|
||||
private onDidChangeOperations(): void {
|
||||
const isCommitInProgress =
|
||||
this.repository.operations.isRunning(Operation.Commit);
|
||||
this.repository.operations.isRunning(Operation.Commit) ||
|
||||
this.repository.operations.isRunning(Operation.RebaseContinue);
|
||||
|
||||
const isSyncInProgress =
|
||||
this.repository.operations.isRunning(Operation.Sync) ||
|
||||
|
@ -251,6 +259,7 @@ export class ActionButtonCommand {
|
|||
...this.state,
|
||||
HEAD: this.repository.HEAD,
|
||||
isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0,
|
||||
isRebaseInProgress: !!this.repository.rebaseCommit,
|
||||
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
|
||||
};
|
||||
}
|
||||
|
|
|
@ -432,11 +432,11 @@ export class CommandCenter {
|
|||
]);
|
||||
// ours (current branch and commit)
|
||||
ours.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', ');
|
||||
ours.description = head.hash.substring(0, 7);
|
||||
ours.description = '$(git-commit) ' + head.hash.substring(0, 7);
|
||||
|
||||
// theirs
|
||||
theirs.detail = rebaseOrMergeHead.refNames.join(', ');
|
||||
theirs.description = rebaseOrMergeHead.hash.substring(0, 7);
|
||||
theirs.description = '$(git-commit) ' + rebaseOrMergeHead.hash.substring(0, 7);
|
||||
|
||||
} catch (error) {
|
||||
// not so bad, can continue with just uris
|
||||
|
@ -469,7 +469,7 @@ export class CommandCenter {
|
|||
/* __GDPR__
|
||||
"clone" : {
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' });
|
||||
|
@ -495,7 +495,7 @@ export class CommandCenter {
|
|||
/* __GDPR__
|
||||
"clone" : {
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' });
|
||||
|
@ -554,8 +554,8 @@ export class CommandCenter {
|
|||
/* __GDPR__
|
||||
"clone" : {
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" },
|
||||
"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 });
|
||||
|
@ -574,7 +574,7 @@ export class CommandCenter {
|
|||
/* __GDPR__
|
||||
"clone" : {
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
|
||||
|
@ -584,7 +584,7 @@ export class CommandCenter {
|
|||
/* __GDPR__
|
||||
"clone" : {
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
|
||||
|
@ -1524,7 +1524,7 @@ export class CommandCenter {
|
|||
}
|
||||
|
||||
if (opts.all === undefined) {
|
||||
opts = { all: noStagedChanges };
|
||||
opts = { ...opts, all: noStagedChanges };
|
||||
} else if (!opts.all && noStagedChanges && !opts.empty) {
|
||||
opts = { ...opts, all: true };
|
||||
}
|
||||
|
@ -3066,7 +3066,7 @@ export class CommandCenter {
|
|||
/* __GDPR__
|
||||
"git.command" : {
|
||||
"owner": "lszomoru",
|
||||
"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The command id of the command being executed" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('git.command', { command: id });
|
||||
|
|
|
@ -1475,7 +1475,7 @@ export class Repository {
|
|||
const args = ['rebase', '--continue'];
|
||||
|
||||
try {
|
||||
await this.exec(args);
|
||||
await this.exec(args, { env: { GIT_EDITOR: 'true' } });
|
||||
} catch (commitErr) {
|
||||
await this.handleCommitError(commitErr);
|
||||
}
|
||||
|
|
|
@ -617,7 +617,7 @@ class ResourceCommandResolver {
|
|||
|
||||
if (!resource.leftUri) {
|
||||
const bothModified = resource.type === Status.BOTH_MODIFIED;
|
||||
if (resource.rightUri && bothModified && workspace.getConfiguration('git').get<boolean>('mergeEditor', false)) {
|
||||
if (resource.rightUri && workspace.getConfiguration('git').get<boolean>('mergeEditor', false) && (bothModified || resource.type === Status.BOTH_ADDED)) {
|
||||
return {
|
||||
command: '_git.openMergeEditor',
|
||||
title: localize('open.merge', "Open Merge"),
|
||||
|
@ -1894,9 +1894,9 @@ export class Repository implements Disposable {
|
|||
/* __GDPR__
|
||||
"statusLimit" : {
|
||||
"owner": "lszomoru",
|
||||
"ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"limit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
"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" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('statusLimit', { ignoreSubmodules: String(ignoreSubmodules) }, { limit, statusLength });
|
||||
|
@ -2028,6 +2028,9 @@ export class Repository implements Disposable {
|
|||
// 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();
|
||||
|
|
|
@ -36,6 +36,12 @@
|
|||
"scopeName": "text.html.handlebars",
|
||||
"path": "./syntaxes/Handlebars.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"htmlLanguageParticipants": [
|
||||
{
|
||||
"languageId": "handlebars",
|
||||
"autoInsert": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
import { window, workspace, Disposable, TextDocument, Position, SnippetString, TextDocumentChangeEvent, TextDocumentChangeReason, TextDocumentContentChangeEvent } from 'vscode';
|
||||
import { Runtime } from './htmlClient';
|
||||
import { LanguageParticipants } from './languageParticipants';
|
||||
|
||||
export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => Thenable<string>, supportedLanguages: { [id: string]: boolean }, runtime: Runtime): Disposable {
|
||||
export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position) => Thenable<string>, languageParticipants: LanguageParticipants, runtime: Runtime): Disposable {
|
||||
const disposables: Disposable[] = [];
|
||||
workspace.onDidChangeTextDocument(onDidChangeTextDocument, null, disposables);
|
||||
|
||||
|
@ -33,7 +34,7 @@ export function activateAutoInsertion(provider: (kind: 'autoQuote' | 'autoClose'
|
|||
return;
|
||||
}
|
||||
const document = editor.document;
|
||||
if (!supportedLanguages[document.languageId]) {
|
||||
if (!languageParticipants.useAutoInsert(document.languageId)) {
|
||||
return;
|
||||
}
|
||||
const configurations = workspace.getConfiguration(undefined, document.uri);
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, ExtensionContext, Uri } from 'vscode';
|
||||
import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient';
|
||||
import { startClient, LanguageClientConstructor } from '../htmlClient';
|
||||
import { LanguageClientOptions } from 'vscode-languageclient';
|
||||
import { startClient, LanguageClientConstructor, AsyncDisposable } from '../htmlClient';
|
||||
import { LanguageClient } from 'vscode-languageclient/browser';
|
||||
|
||||
declare const Worker: {
|
||||
|
@ -15,7 +15,7 @@ declare const TextDecoder: {
|
|||
new(encoding?: string): { decode(buffer: ArrayBuffer): string };
|
||||
};
|
||||
|
||||
let client: BaseLanguageClient | undefined;
|
||||
let client: AsyncDisposable | undefined;
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export async function activate(context: ExtensionContext) {
|
||||
|
@ -42,7 +42,7 @@ export async function activate(context: ExtensionContext) {
|
|||
|
||||
export async function deactivate(): Promise<void> {
|
||||
if (client) {
|
||||
await client.stop();
|
||||
await client.dispose();
|
||||
client = undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ function collectInWorkspaces(workspaceUris: Set<string>): Set<string> {
|
|||
}
|
||||
|
||||
function collectInExtensions(localExtensionUris: Set<string>, externalUris: Set<string>): void {
|
||||
for (const extension of extensions.all) {
|
||||
for (const extension of extensions.allAcrossExtensionHosts) {
|
||||
const customData = extension.packageJSON?.contributes?.html?.customData;
|
||||
if (Array.isArray(customData)) {
|
||||
for (const uriOrPath of customData) {
|
||||
|
|
|
@ -9,7 +9,7 @@ const localize = nls.loadMessageBundle();
|
|||
import {
|
||||
languages, ExtensionContext, Position, TextDocument, Range, CompletionItem, CompletionItemKind, SnippetString, workspace, extensions,
|
||||
Disposable, FormattingOptions, CancellationToken, ProviderResult, TextEdit, CompletionContext, CompletionList, SemanticTokensLegend,
|
||||
DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, SemanticTokens, window, commands
|
||||
DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider, SemanticTokens, window, commands, OutputChannel
|
||||
} from 'vscode';
|
||||
import {
|
||||
LanguageClientOptions, RequestType, DocumentRangeFormattingParams,
|
||||
|
@ -18,6 +18,7 @@ import {
|
|||
import { FileSystemProvider, serveFileSystemRequests } from './requests';
|
||||
import { getCustomDataSource } from './customData';
|
||||
import { activateAutoInsertion } from './autoInsertion';
|
||||
import { getLanguageParticipants, LanguageParticipants } from './languageParticipants';
|
||||
|
||||
namespace CustomDataChangedNotification {
|
||||
export const type: NotificationType<string[]> = new NotificationType('html/customDataChanged');
|
||||
|
@ -74,6 +75,8 @@ export interface TelemetryReporter {
|
|||
|
||||
export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient;
|
||||
|
||||
export const languageServerDescription = localize('htmlserver.name', 'HTML Language Server');
|
||||
|
||||
export interface Runtime {
|
||||
TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string } };
|
||||
fileFs?: FileSystemProvider;
|
||||
|
@ -83,11 +86,69 @@ export interface Runtime {
|
|||
};
|
||||
}
|
||||
|
||||
export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise<BaseLanguageClient> {
|
||||
export interface AsyncDisposable {
|
||||
dispose(): Promise<void>;
|
||||
}
|
||||
|
||||
const toDispose = context.subscriptions;
|
||||
export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise<AsyncDisposable> {
|
||||
|
||||
const documentSelector = ['html', 'handlebars'];
|
||||
const outputChannel = window.createOutputChannel(languageServerDescription);
|
||||
|
||||
const languageParticipants = getLanguageParticipants();
|
||||
context.subscriptions.push(languageParticipants);
|
||||
|
||||
let client: Disposable | undefined = await startClientWithParticipants(languageParticipants, newLanguageClient, outputChannel, runtime);
|
||||
|
||||
const promptForLinkedEditingKey = 'html.promptForLinkedEditing';
|
||||
if (extensions.getExtension('formulahendry.auto-rename-tag') !== undefined && (context.globalState.get(promptForLinkedEditingKey) !== false)) {
|
||||
const config = workspace.getConfiguration('editor', { languageId: 'html' });
|
||||
if (!config.get('linkedEditing') && !config.get('renameOnType')) {
|
||||
const activeEditorListener = window.onDidChangeActiveTextEditor(async e => {
|
||||
if (e && languageParticipants.hasLanguage(e.document.languageId)) {
|
||||
context.globalState.update(promptForLinkedEditingKey, false);
|
||||
activeEditorListener.dispose();
|
||||
const configure = localize('configureButton', 'Configure');
|
||||
const res = await window.showInformationMessage(localize('linkedEditingQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure);
|
||||
if (res === configure) {
|
||||
commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing);
|
||||
}
|
||||
}
|
||||
});
|
||||
context.subscriptions.push(activeEditorListener);
|
||||
}
|
||||
}
|
||||
|
||||
let restartTrigger: Disposable | undefined;
|
||||
languageParticipants.onDidChange(() => {
|
||||
if (restartTrigger) {
|
||||
restartTrigger.dispose();
|
||||
}
|
||||
restartTrigger = runtime.timer.setTimeout(async () => {
|
||||
if (client) {
|
||||
outputChannel.appendLine('Extensions have changed, restarting HTML server...');
|
||||
outputChannel.appendLine('');
|
||||
const oldClient = client;
|
||||
client = undefined;
|
||||
await oldClient.dispose();
|
||||
client = await startClientWithParticipants(languageParticipants, newLanguageClient, outputChannel, runtime);
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
return {
|
||||
dispose: async () => {
|
||||
restartTrigger?.dispose();
|
||||
await client?.dispose();
|
||||
outputChannel.dispose();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function startClientWithParticipants(languageParticipants: LanguageParticipants, newLanguageClient: LanguageClientConstructor, outputChannel: OutputChannel, runtime: Runtime): Promise<AsyncDisposable> {
|
||||
|
||||
const toDispose: Disposable[] = [];
|
||||
|
||||
const documentSelector = languageParticipants.documentSelector;
|
||||
const embeddedLanguages = { css: true, javascript: true };
|
||||
|
||||
let rangeFormatting: Disposable | undefined = undefined;
|
||||
|
@ -129,22 +190,23 @@ export async function startClient(context: ExtensionContext, newLanguageClient:
|
|||
}
|
||||
}
|
||||
};
|
||||
clientOptions.outputChannel = outputChannel;
|
||||
|
||||
// Create the language client and start the client.
|
||||
const client = newLanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), clientOptions);
|
||||
const client = newLanguageClient('html', languageServerDescription, clientOptions);
|
||||
client.registerProposedFeatures();
|
||||
|
||||
await client.start();
|
||||
|
||||
toDispose.push(serveFileSystemRequests(client, runtime));
|
||||
|
||||
const customDataSource = getCustomDataSource(runtime, context.subscriptions);
|
||||
const customDataSource = getCustomDataSource(runtime, toDispose);
|
||||
|
||||
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
|
||||
customDataSource.onDidChange(() => {
|
||||
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
|
||||
});
|
||||
client.onRequest(CustomDataContent.type, customDataSource.getContent);
|
||||
}, undefined, toDispose);
|
||||
toDispose.push(client.onRequest(CustomDataContent.type, customDataSource.getContent));
|
||||
|
||||
|
||||
const insertRequestor = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position): Promise<string> => {
|
||||
|
@ -155,7 +217,8 @@ export async function startClient(context: ExtensionContext, newLanguageClient:
|
|||
};
|
||||
return client.sendRequest(AutoInsertRequest.type, param);
|
||||
};
|
||||
const disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime);
|
||||
|
||||
const disposable = activateAutoInsertion(insertRequestor, languageParticipants, runtime);
|
||||
toDispose.push(disposable);
|
||||
|
||||
const disposable2 = client.onTelemetry(e => {
|
||||
|
@ -193,7 +256,6 @@ export async function startClient(context: ExtensionContext, newLanguageClient:
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
function updateFormatterRegistration() {
|
||||
const formatEnabled = workspace.getConfiguration().get(SettingIds.formatEnable);
|
||||
if (!formatEnabled && rangeFormatting) {
|
||||
|
@ -278,25 +340,12 @@ export async function startClient(context: ExtensionContext, newLanguageClient:
|
|||
}
|
||||
}));
|
||||
|
||||
const promptForLinkedEditingKey = 'html.promptForLinkedEditing';
|
||||
if (extensions.getExtension('formulahendry.auto-rename-tag') !== undefined && (context.globalState.get(promptForLinkedEditingKey) !== false)) {
|
||||
const config = workspace.getConfiguration('editor', { languageId: 'html' });
|
||||
if (!config.get('linkedEditing') && !config.get('renameOnType')) {
|
||||
const activeEditorListener = window.onDidChangeActiveTextEditor(async e => {
|
||||
if (e && documentSelector.indexOf(e.document.languageId) !== -1) {
|
||||
context.globalState.update(promptForLinkedEditingKey, false);
|
||||
activeEditorListener.dispose();
|
||||
const configure = localize('configureButton', 'Configure');
|
||||
const res = await window.showInformationMessage(localize('linkedEditingQuestion', 'VS Code now has built-in support for auto-renaming tags. Do you want to enable it?'), configure);
|
||||
if (res === configure) {
|
||||
commands.executeCommand('workbench.action.openSettings', SettingIds.linkedEditing);
|
||||
}
|
||||
}
|
||||
});
|
||||
toDispose.push(activeEditorListener);
|
||||
return {
|
||||
dispose: async () => {
|
||||
await client.stop();
|
||||
toDispose.forEach(d => d.dispose());
|
||||
rangeFormatting?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. 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<void>;
|
||||
readonly documentSelector: DocumentSelector;
|
||||
hasLanguage(languageId: string): boolean;
|
||||
useAutoInsert(languageId: string): boolean;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export function getLanguageParticipants(): LanguageParticipants {
|
||||
const onDidChangeEmmiter = new EventEmitter<void>();
|
||||
let languages = new Set<string>();
|
||||
let autoInsert = new Set<string>();
|
||||
|
||||
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<T>(s1: Set<T>, s2: Set<T>) {
|
||||
if (s1.size !== s2.size) {
|
||||
return false;
|
||||
}
|
||||
for (const e of s1) {
|
||||
if (!s2.has(e)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -5,15 +5,15 @@
|
|||
|
||||
import { getNodeFileFS } from './nodeFs';
|
||||
import { Disposable, ExtensionContext } from 'vscode';
|
||||
import { startClient, LanguageClientConstructor } from '../htmlClient';
|
||||
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node';
|
||||
import { startClient, LanguageClientConstructor, AsyncDisposable } from '../htmlClient';
|
||||
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node';
|
||||
import { TextDecoder } from 'util';
|
||||
import * as fs from 'fs';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
|
||||
|
||||
let telemetry: TelemetryReporter | undefined;
|
||||
let client: BaseLanguageClient | undefined;
|
||||
let client: AsyncDisposable | undefined;
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export async function activate(context: ExtensionContext) {
|
||||
|
@ -50,7 +50,7 @@ export async function activate(context: ExtensionContext) {
|
|||
|
||||
export async function deactivate(): Promise<void> {
|
||||
if (client) {
|
||||
await client.stop();
|
||||
await client.dispose();
|
||||
client = undefined;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"../../../src/vscode-dts/vscode.d.ts"
|
||||
"../../../src/vscode-dts/vscode.d.ts",
|
||||
"../../../src/vscode-dts/vscode.proposed.extensionsAny.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@
|
|||
"onLanguage:html",
|
||||
"onLanguage:handlebars"
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"extensionsAny"
|
||||
],
|
||||
"main": "./client/out/node/htmlClientMain",
|
||||
"browser": "./client/dist/browser/htmlClientMain",
|
||||
"capabilities": {
|
||||
|
|
|
@ -13,6 +13,23 @@
|
|||
"type": "string",
|
||||
"description": "Relative path to a HTML custom data file"
|
||||
}
|
||||
},
|
||||
"htmlLanguageParticipants": {
|
||||
"type": "array",
|
||||
"description": "A list of languages that participate with the HTML language server.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"languageId": {
|
||||
"type": "string",
|
||||
"description": "The id of the language that participates with HTML language server."
|
||||
},
|
||||
"autoInsert": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the language participates with HTML auto insertions. If not specified, defaults to <code>true</code>."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"main": "./out/node/htmlServerMain",
|
||||
"dependencies": {
|
||||
"vscode-css-languageservice": "^6.0.1",
|
||||
"vscode-html-languageservice": "^5.0.0",
|
||||
"vscode-html-languageservice": "^5.0.1",
|
||||
"vscode-languageserver": "^8.0.2-next.4",
|
||||
"vscode-languageserver-textdocument": "^1.0.4",
|
||||
"vscode-nls": "^5.0.1",
|
||||
|
|
|
@ -24,6 +24,10 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp
|
|||
|
||||
return {
|
||||
resolveReference: (ref: string, base = documentUri) => {
|
||||
if (ref.match(/^\w[\w\d+.-]*:/)) {
|
||||
// starts with a schema
|
||||
return ref;
|
||||
}
|
||||
if (ref[0] === '/') { // resolve absolute path against the current workspace folder
|
||||
const folderUri = getRootFolder();
|
||||
if (folderUri) {
|
||||
|
|
|
@ -22,10 +22,10 @@ vscode-css-languageservice@^6.0.1:
|
|||
vscode-nls "^5.0.1"
|
||||
vscode-uri "^3.0.3"
|
||||
|
||||
vscode-html-languageservice@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.0.tgz#c68613f836d7fcff125183d78e6f1f0ff326fa55"
|
||||
integrity sha512-KJG13z54aLszskp3ETf8b1EKDypr2Sf5RUsfR6OXmKqEl2ZUfyIxsWz4gbJWjPzoJZx/bGH0ZXVwxJ1rg8OKRQ==
|
||||
vscode-html-languageservice@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.1.tgz#bdf7847d27a453a9e98ae2836ead7594784c5c1c"
|
||||
integrity sha512-OYsyn5HGAhxs0OIG+M0jc34WnftLtD67Wg7+TfrYwvf0waOkkr13zUqtdrVm2JPNQ6fJx+qnuM+vTbq7o1dCdQ==
|
||||
dependencies:
|
||||
vscode-languageserver-textdocument "^1.0.4"
|
||||
vscode-languageserver-types "^3.17.1"
|
||||
|
|
47
extensions/ipynb/esbuild.js
Normal file
47
extensions/ipynb/esbuild.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
//@ts-check
|
||||
|
||||
const path = require('path');
|
||||
const fse = require('fs-extra');
|
||||
const esbuild = require('esbuild');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const isWatch = args.indexOf('--watch') >= 0;
|
||||
|
||||
let outputRoot = __dirname;
|
||||
const outputRootIndex = args.indexOf('--outputRoot');
|
||||
if (outputRootIndex >= 0) {
|
||||
outputRoot = args[outputRootIndex + 1];
|
||||
}
|
||||
|
||||
const srcDir = path.join(__dirname, 'src');
|
||||
const outDir = path.join(outputRoot, 'out');
|
||||
|
||||
async function build() {
|
||||
await esbuild.build({
|
||||
entryPoints: [
|
||||
path.join(srcDir, 'cellAttachmentRenderer.ts'),
|
||||
],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
sourcemap: false,
|
||||
format: 'esm',
|
||||
outdir: outDir,
|
||||
platform: 'browser',
|
||||
target: ['es2020'],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
build().catch(() => process.exit(1));
|
||||
|
||||
if (isWatch) {
|
||||
const watcher = require('@parcel/watcher');
|
||||
watcher.subscribe(srcDir, () => {
|
||||
return build();
|
||||
});
|
||||
}
|
|
@ -51,6 +51,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": "./out/cellAttachmentRenderer.js"
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"file/newFile": [
|
||||
{
|
||||
|
@ -70,8 +80,9 @@
|
|||
}
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "npx gulp compile-extension:ipynb",
|
||||
"watch": "npx gulp watch-extension:ipynb"
|
||||
"compile": "npx gulp compile-extension:ipynb && npm run build-notebook",
|
||||
"watch": "npx gulp watch-extension:ipynb",
|
||||
"build-notebook": "node ./esbuild"
|
||||
},
|
||||
"dependencies": {
|
||||
"@enonic/fnv-plus": "^1.3.0",
|
||||
|
@ -80,6 +91,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@jupyterlab/nbformat": "^3.2.9",
|
||||
"@types/markdown-it": "12.2.3",
|
||||
"@types/uuid": "^8.3.1"
|
||||
},
|
||||
"repository": {
|
||||
|
|
39
extensions/ipynb/src/cellAttachmentRenderer.ts
Normal file
39
extensions/ipynb/src/cellAttachmentRenderer.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as MarkdownIt from 'markdown-it';
|
||||
import type * as MarkdownItToken from 'markdown-it/lib/token';
|
||||
import type { RendererContext } from 'vscode-notebook-renderer';
|
||||
|
||||
interface MarkdownItRenderer {
|
||||
extendMarkdownIt(fn: (md: MarkdownIt) => void): void;
|
||||
}
|
||||
|
||||
export async function activate(ctx: RendererContext<void>) {
|
||||
const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as MarkdownItRenderer | any;
|
||||
if (!markdownItRenderer) {
|
||||
throw new Error(`Could not load 'vscode.markdown-it-renderer'`);
|
||||
}
|
||||
|
||||
markdownItRenderer.extendMarkdownIt((md: MarkdownIt) => {
|
||||
const original = md.renderer.rules.image;
|
||||
md.renderer.rules.image = (tokens: MarkdownItToken[], idx: number, options, env, self) => {
|
||||
const token = tokens[idx];
|
||||
const src = token.attrGet('src');
|
||||
const attachments: Record<string, Record<string, string>> = env.outputItem.metadata?.custom?.attachments;
|
||||
if (attachments && src) {
|
||||
const [attachmentKey, attachmentVal] = Object.entries(attachments[src.replace('attachment:', '')])[0];
|
||||
const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal;
|
||||
token.attrSet('src', b64Markdown);
|
||||
}
|
||||
|
||||
if (original) {
|
||||
return original(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
|
@ -8,21 +8,39 @@
|
|||
integrity sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==
|
||||
|
||||
"@jupyterlab/nbformat@^3.2.9":
|
||||
version "3.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz#e7d854719612133498af4280d9a8caa0873205b0"
|
||||
integrity sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.4.3.tgz#cbab1bf507677b7f0f309d8353fc83fe5a973c82"
|
||||
integrity sha512-i/yADrwhhAJJCUOTa+fEBMyJO7fvX9Y73I0B7V6dQhGcrmrEKLC3wk4yOo63+jRntd5+dupbiOtz3w1ncIXwIA==
|
||||
dependencies:
|
||||
"@lumino/coreutils" "^1.5.3"
|
||||
"@lumino/coreutils" "^1.11.0"
|
||||
|
||||
"@lumino/coreutils@^1.5.3":
|
||||
"@lumino/coreutils@^1.11.0":
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-1.12.0.tgz#fbdef760f736eaf2bd396a5c6fc3a68a4b449b15"
|
||||
integrity sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==
|
||||
|
||||
"@types/linkify-it@*":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9"
|
||||
integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==
|
||||
|
||||
"@types/markdown-it@12.2.3":
|
||||
version "12.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51"
|
||||
integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==
|
||||
dependencies:
|
||||
"@types/linkify-it" "*"
|
||||
"@types/mdurl" "*"
|
||||
|
||||
"@types/mdurl@*":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
|
||||
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
|
||||
|
||||
"@types/uuid@^8.3.1":
|
||||
version "8.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
|
||||
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||
|
||||
detect-indent@^6.0.0:
|
||||
version "6.1.0"
|
||||
|
|
|
@ -398,16 +398,27 @@
|
|||
"description": "%configuration.markdown.suggest.paths.enabled.description%",
|
||||
"scope": "resource"
|
||||
},
|
||||
"markdown.trace": {
|
||||
"markdown.trace.extension": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"off",
|
||||
"verbose"
|
||||
],
|
||||
"default": "off",
|
||||
"description": "%markdown.trace.desc%",
|
||||
"description": "%markdown.trace.extension.desc%",
|
||||
"scope": "window"
|
||||
},
|
||||
"markdown.trace.server": {
|
||||
"type": "string",
|
||||
"scope": "window",
|
||||
"enum": [
|
||||
"off",
|
||||
"messages",
|
||||
"verbose"
|
||||
],
|
||||
"default": "off",
|
||||
"description": "%markdown.trace.server.desc%"
|
||||
},
|
||||
"markdown.editor.drop.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
|
@ -478,6 +489,7 @@
|
|||
"type": "string",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description%",
|
||||
"default": "ignore",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
|
@ -563,7 +575,8 @@
|
|||
"@types/vscode-notebook-renderer": "^1.60.0",
|
||||
"@types/vscode-webview": "^1.57.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"vscode-languageserver-types": "^3.17.2"
|
||||
"vscode-languageserver-types": "^3.17.2",
|
||||
"vscode-markdown-languageservice": "^0.0.0-alpha.10"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
"markdown.showSource.title": "Show Source",
|
||||
"markdown.styles.dec": "A list of URLs or local paths to CSS style sheets to use from the Markdown preview. Relative paths are interpreted relative to the folder open in the Explorer. If there is no open folder, they are interpreted relative to the location of the Markdown file. All '\\' need to be written as '\\\\'.",
|
||||
"markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings",
|
||||
"markdown.trace.desc": "Enable debug logging for the Markdown extension.",
|
||||
"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.preview.refresh.title": "Refresh Preview",
|
||||
"markdown.preview.toggleLock.title": "Toggle Preview Locking",
|
||||
"markdown.findAllFileReferences": "Find File References",
|
||||
|
|
12
extensions/markdown-language-features/server/.npmignore
Normal file
12
extensions/markdown-language-features/server/.npmignore
Normal file
|
@ -0,0 +1,12 @@
|
|||
.vscode/
|
||||
.github/
|
||||
out/test/
|
||||
src/
|
||||
.eslintrc.js
|
||||
.gitignore
|
||||
tsconfig*.json
|
||||
*.tsbuildinfo
|
||||
*.map
|
||||
example.cjs
|
||||
CODE_OF_CONDUCT.md
|
||||
SECURITY.md
|
|
@ -6,9 +6,11 @@
|
|||
"name": "Attach",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 7692,
|
||||
"port": 7997,
|
||||
"sourceMaps": true,
|
||||
"outFiles": ["${workspaceFolder}/out/**/*.js"]
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
2
extensions/markdown-language-features/server/.vscode/settings.json
vendored
Normal file
2
extensions/markdown-language-features/server/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
120
extensions/markdown-language-features/server/README.md
Normal file
120
extensions/markdown-language-features/server/README.md
Normal file
|
@ -0,0 +1,120 @@
|
|||
# Markdown Language Server
|
||||
|
||||
> **❗ Import** This is still in development. While the language server is being used by VS Code, it has not yet been tested with other clients.
|
||||
|
||||
The Markdown language server powers VS Code's built-in markdown support, providing tools for writing and browsing Markdown files. It runs as a separate executable and implements the [language server protocol](https://microsoft.github.io/language-server-protocol/overview).
|
||||
|
||||
This server uses the [Markdown Language Service](https://github.com/microsoft/vscode-markdown-languageservice) to implement almost all of the language features. You can use that library if you need a library for working with Markdown instead of a full language server.
|
||||
|
||||
|
||||
## Server capabilities
|
||||
|
||||
- [Completions](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for Markdown links.
|
||||
|
||||
- [Folding](https://microsoft.github.io/language-server-protocol/specification#textDocument_foldingRange) of Markdown regions, block elements, and header sections.
|
||||
|
||||
- [Smart selection](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange) for inline elements, block elements, and header sections.
|
||||
|
||||
- [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to headers in a document.
|
||||
|
||||
- [Workspace Symbols](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_symbol) for quick navigation to headers in the workspace
|
||||
|
||||
- [Document links](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_documentLink) for making Markdown links in a document clickable.
|
||||
|
||||
- [Find all references](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_references) to headers and links across all Markdown files in the workspace.
|
||||
|
||||
- [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.
|
||||
|
||||
|
||||
|
||||
## Client requirements
|
||||
|
||||
### Initialization options
|
||||
|
||||
The client can send the following initialization options to the server:
|
||||
|
||||
- `markdownFileExtensions` Array file extensions that should be considered as Markdown. These should not include the leading `.`. For example: `['md', 'mdown', 'markdown']`.
|
||||
|
||||
### Settings
|
||||
|
||||
Clients may send a `workspace/didChangeConfiguration` notification to notify the server of settings changes.
|
||||
The server supports the following settings:
|
||||
|
||||
- `markdown`
|
||||
- `suggest`
|
||||
- `paths`
|
||||
- `enabled` — Enable/disable path suggestions.
|
||||
- `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.
|
||||
|
||||
### Custom requests
|
||||
|
||||
To support all of the features of the language server, the client needs to implement a few custom request types. The definitions of these request types can be found in [`protocol.ts`](./src/protocol.ts)
|
||||
|
||||
#### `markdown/parse`
|
||||
|
||||
Get the tokens for a Markdown file. Clients are expected to use [Markdown-it](https://github.com/markdown-it/markdown-it) for this.
|
||||
|
||||
We require that clients bring their own version of Markdown-it so that they can customize/extend Markdown-it.
|
||||
|
||||
#### `markdown/fs/readFile`
|
||||
|
||||
Read the contents of a file in the workspace.
|
||||
|
||||
#### `markdown/fs/readDirectory`
|
||||
|
||||
Read the contents of a directory in the workspace.
|
||||
|
||||
#### `markdown/fs/stat`
|
||||
|
||||
Check if a given file/directory exists in the workspace.
|
||||
|
||||
#### `markdown/fs/watcher/create`
|
||||
|
||||
Create a file watcher. This is needed for diagnostics support.
|
||||
|
||||
#### `markdown/fs/watcher/delete`
|
||||
|
||||
Delete a previously created file watcher.
|
||||
|
||||
#### `markdown/findMarkdownFilesInWorkspace`
|
||||
|
||||
Get a list of all markdown files in the workspace.
|
||||
|
||||
|
||||
## Contribute
|
||||
|
||||
The source code of the Markdown language server can be found in the [VSCode repository](https://github.com/microsoft/vscode) at [extensions/markdown-language-features/server](https://github.com/microsoft/vscode/tree/master/extensions/markdown-language-features/server).
|
||||
|
||||
File issues and pull requests in the [VSCode GitHub Issues](https://github.com/microsoft/vscode/issues). See the document [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) on how to build and run from source.
|
||||
|
||||
Most of the functionality of the server is located in libraries:
|
||||
|
||||
- [vscode-markdown-languageservice](https://github.com/microsoft/vscode-markdown-languageservice) contains the implementation of all features as a reusable library.
|
||||
- [vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node) contains the implementation of language server for NodeJS.
|
||||
|
||||
Help on any of these projects is very welcome.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Licensed under the [MIT](https://github.com/microsoft/vscode/blob/master/LICENSE.txt) License.
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "vscode-markdown-languageserver",
|
||||
"description": "Markdown language server",
|
||||
"version": "1.0.0",
|
||||
"version": "0.0.0-alpha-1",
|
||||
"author": "Microsoft Corporation",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -13,7 +13,7 @@
|
|||
"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.8",
|
||||
"vscode-markdown-languageservice": "^0.0.0-alpha.11",
|
||||
"vscode-uri": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Connection, Emitter } from 'vscode-languageserver';
|
||||
import { Disposable } from './util/dispose';
|
||||
|
||||
export type ValidateEnabled = 'ignore' | 'warning' | 'error';
|
||||
|
||||
interface Settings {
|
||||
readonly markdown: {
|
||||
readonly suggest: {
|
||||
readonly paths: {
|
||||
readonly enabled: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
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[];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export class ConfigurationManager extends Disposable {
|
||||
|
||||
private readonly _onDidChangeConfiguration = this._register(new Emitter<Settings>());
|
||||
public readonly onDidChangeConfiguration = this._onDidChangeConfiguration.event;
|
||||
|
||||
private _settings?: Settings;
|
||||
|
||||
constructor(connection: Connection) {
|
||||
super();
|
||||
|
||||
// The settings have changed. Is send on server activation as well.
|
||||
this._register(connection.onDidChangeConfiguration((change) => {
|
||||
this._settings = change.settings;
|
||||
this._onDidChangeConfiguration.fire(this._settings!);
|
||||
}));
|
||||
}
|
||||
|
||||
public getSettings(): Settings | undefined {
|
||||
return this._settings;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Connection, FullDocumentDiagnosticReport, 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';
|
||||
|
||||
const defaultDiagnosticOptions: md.DiagnosticOptions = {
|
||||
validateFileLinks: md.DiagnosticLevel.ignore,
|
||||
validateReferences: md.DiagnosticLevel.ignore,
|
||||
validateFragmentLinks: md.DiagnosticLevel.ignore,
|
||||
validateMarkdownFileLinkFragments: md.DiagnosticLevel.ignore,
|
||||
ignoreLinks: [],
|
||||
};
|
||||
|
||||
function convertDiagnosticLevel(enabled: ValidateEnabled): md.DiagnosticLevel | undefined {
|
||||
switch (enabled) {
|
||||
case 'error': return md.DiagnosticLevel.error;
|
||||
case 'warning': return md.DiagnosticLevel.warning;
|
||||
case 'ignore': return md.DiagnosticLevel.ignore;
|
||||
default: return md.DiagnosticLevel.ignore;
|
||||
}
|
||||
}
|
||||
|
||||
function getDiagnosticsOptions(config: ConfigurationManager): md.DiagnosticOptions {
|
||||
const settings = config.getSettings();
|
||||
if (!settings) {
|
||||
return defaultDiagnosticOptions;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
export function registerValidateSupport(
|
||||
connection: Connection,
|
||||
workspace: VsCodeClientWorkspace,
|
||||
ls: md.IMdLanguageService,
|
||||
config: ConfigurationManager,
|
||||
): Disposable {
|
||||
let diagnosticOptions: md.DiagnosticOptions = defaultDiagnosticOptions;
|
||||
function updateDiagnosticsSetting(): void {
|
||||
diagnosticOptions = getDiagnosticsOptions(config);
|
||||
}
|
||||
|
||||
|
||||
const subs: Disposable[] = [];
|
||||
const manager = ls.createPullDiagnosticsManager();
|
||||
subs.push(manager);
|
||||
|
||||
subs.push(manager.onLinkedToFileChanged(() => {
|
||||
// TODO: We only need to refresh certain files
|
||||
connection.languages.diagnostics.refresh();
|
||||
}));
|
||||
|
||||
connection.languages.diagnostics.on(async (params, token): Promise<FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport> => {
|
||||
if (!config.getSettings()?.markdown.experimental.validate.enabled) {
|
||||
return { kind: 'full', items: [] };
|
||||
}
|
||||
|
||||
const document = await workspace.openMarkdownDocument(URI.parse(params.textDocument.uri));
|
||||
if (!document) {
|
||||
return { kind: 'full', items: [] };
|
||||
}
|
||||
|
||||
const diagnostics = await manager.computeDiagnostics(document, diagnosticOptions, token);
|
||||
return {
|
||||
kind: 'full',
|
||||
items: diagnostics,
|
||||
};
|
||||
});
|
||||
|
||||
updateDiagnosticsSetting();
|
||||
subs.push(config.onDidChangeConfiguration(() => {
|
||||
updateDiagnosticsSetting();
|
||||
connection.languages.diagnostics.refresh();
|
||||
}));
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
disposeAll(subs);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ILogger } from 'vscode-markdown-languageservice';
|
||||
import { ILogger, LogLevel } from 'vscode-markdown-languageservice';
|
||||
|
||||
export class LogFunctionLogger implements ILogger {
|
||||
|
||||
|
@ -31,8 +31,9 @@ export class LogFunctionLogger implements ILogger {
|
|||
private readonly _logFn: typeof console.log
|
||||
) { }
|
||||
|
||||
public verbose(title: string, message: string, data?: any): void {
|
||||
this.appendLine(`[Verbose ${LogFunctionLogger.now()}] ${title}: ${message}`);
|
||||
|
||||
public log(level: LogLevel, title: string, message: string, data?: any): void {
|
||||
this.appendLine(`[${level} ${LogFunctionLogger.now()}] ${title}: ${message}`);
|
||||
if (data) {
|
||||
this.appendLine(LogFunctionLogger.data2String(data));
|
||||
}
|
||||
|
|
|
@ -4,15 +4,24 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RequestType } from 'vscode-languageserver';
|
||||
import * as md from 'vscode-markdown-languageservice';
|
||||
import * as lsp from 'vscode-languageserver-types';
|
||||
import type * as lsp from 'vscode-languageserver-types';
|
||||
import type * as md from 'vscode-markdown-languageservice';
|
||||
|
||||
// From server
|
||||
export const parseRequestType: RequestType<{ uri: string }, md.Token[], any> = new RequestType('markdown/parse');
|
||||
export const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile');
|
||||
export const statFileRequestType: RequestType<{ uri: string }, md.FileStat | undefined, any> = new RequestType('markdown/statFile');
|
||||
export const readDirectoryRequestType: RequestType<{ uri: string }, [string, md.FileStat][], any> = new RequestType('markdown/readDirectory');
|
||||
export const findFilesRequestTypes: RequestType<{}, string[], any> = new RequestType('markdown/findFiles');
|
||||
//#region From server
|
||||
export const parse = new RequestType<{ uri: string }, md.Token[], any>('markdown/parse');
|
||||
|
||||
// To server
|
||||
export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile');
|
||||
export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory');
|
||||
export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat');
|
||||
|
||||
export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any>('markdown/fs/watcher/create');
|
||||
export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete');
|
||||
|
||||
export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace');
|
||||
//#endregion
|
||||
|
||||
//#region To server
|
||||
export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace');
|
||||
|
||||
export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange');
|
||||
//#endregion
|
||||
|
|
|
@ -7,8 +7,11 @@ import { CancellationToken, Connection, InitializeParams, InitializeResult, Note
|
|||
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 { registerValidateSupport } from './languageFeatures/diagnostics';
|
||||
import { LogFunctionLogger } from './logging';
|
||||
import * as protocol from './protocol';
|
||||
import { VsCodeClientWorkspace } from './workspace';
|
||||
|
@ -17,12 +20,17 @@ export async function startServer(connection: Connection) {
|
|||
const documents = new TextDocuments(TextDocument);
|
||||
const notebooks = new NotebookDocuments(documents);
|
||||
|
||||
const configurationManager = new ConfigurationManager(connection);
|
||||
|
||||
let provider: md.IMdLanguageService | undefined;
|
||||
let workspace: VsCodeClientWorkspace | undefined;
|
||||
|
||||
connection.onInitialize((params: InitializeParams): InitializeResult => {
|
||||
const parser = new class implements md.IMdParser {
|
||||
slugifier = md.githubSlugifier;
|
||||
|
||||
async tokenize(document: md.ITextDocument): Promise<md.Token[]> {
|
||||
return await connection.sendRequest(protocol.parseRequestType, { uri: document.uri.toString() });
|
||||
return await connection.sendRequest(protocol.parse, { uri: document.uri.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -30,8 +38,8 @@ export async function startServer(connection: Connection) {
|
|||
markdownFileExtensions: params.initializationOptions.markdownFileExtensions,
|
||||
});
|
||||
|
||||
const workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks);
|
||||
const logger = new LogFunctionLogger(connection.console.log.bind(connection.console));
|
||||
workspace = new VsCodeClientWorkspace(connection, config, documents, notebooks, logger);
|
||||
provider = md.createLanguageService({
|
||||
workspace,
|
||||
parser,
|
||||
|
@ -39,9 +47,18 @@ export async function startServer(connection: Connection) {
|
|||
markdownFileExtensions: config.markdownFileExtensions,
|
||||
});
|
||||
|
||||
registerCompletionsSupport(connection, documents, provider, configurationManager);
|
||||
registerValidateSupport(connection, workspace, provider, configurationManager);
|
||||
|
||||
workspace.workspaceFolders = (params.workspaceFolders ?? []).map(x => URI.parse(x.uri));
|
||||
return {
|
||||
capabilities: {
|
||||
diagnosticProvider: {
|
||||
documentSelector: null,
|
||||
identifier: 'markdown',
|
||||
interFileDependencies: true,
|
||||
workspaceDiagnostics: false,
|
||||
},
|
||||
completionProvider: { triggerCharacters: ['.', '/', '#'] },
|
||||
definitionProvider: true,
|
||||
documentLinkProvider: { resolveProvider: true },
|
||||
|
@ -61,8 +78,6 @@ export async function startServer(connection: Connection) {
|
|||
});
|
||||
|
||||
|
||||
let provider: md.IMdLanguageService | undefined;
|
||||
|
||||
connection.onDocumentLinks(async (params, token): Promise<lsp.DocumentLink[]> => {
|
||||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
|
@ -129,18 +144,6 @@ export async function startServer(connection: Connection) {
|
|||
return [];
|
||||
});
|
||||
|
||||
connection.onCompletion(async (params, token): Promise<lsp.CompletionItem[]> => {
|
||||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return await provider!.getCompletionItems(document, params.position, params.context!, token);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e.stack);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
connection.onReferences(async (params, token): Promise<lsp.Location[]> => {
|
||||
try {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
|
@ -204,3 +207,46 @@ export async function startServer(connection: Connection) {
|
|||
notebooks.listen(connection);
|
||||
connection.listen();
|
||||
}
|
||||
|
||||
|
||||
function registerCompletionsSupport(
|
||||
connection: Connection,
|
||||
documents: TextDocuments<TextDocument>,
|
||||
ls: md.IMdLanguageService,
|
||||
config: ConfigurationManager,
|
||||
): IDisposable {
|
||||
// let registration: Promise<IDisposable> | 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<lsp.CompletionItem[]> => {
|
||||
try {
|
||||
const settings = config.getSettings();
|
||||
if (!settings?.markdown.suggest.paths.enabled) {
|
||||
return [];
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
update();
|
||||
return config.onDidChangeConfiguration(() => update());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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<IDisposable>) {
|
||||
const errors: any[] = [];
|
||||
|
||||
for (const disposable of disposables) {
|
||||
try {
|
||||
disposable.dispose();
|
||||
} catch (e) {
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length === 1) {
|
||||
throw errors[0];
|
||||
} else if (errors.length > 1) {
|
||||
throw new MultiDisposeError(errors);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export abstract class Disposable {
|
||||
private _isDisposed = false;
|
||||
|
||||
protected _disposables: IDisposable[] = [];
|
||||
|
||||
public dispose(): any {
|
||||
if (this._isDisposed) {
|
||||
return;
|
||||
}
|
||||
this._isDisposed = true;
|
||||
disposeAll(this._disposables);
|
||||
}
|
||||
|
||||
protected _register<T extends IDisposable>(value: T): T {
|
||||
if (this._isDisposed) {
|
||||
value.dispose();
|
||||
} else {
|
||||
this._disposables.push(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
protected get isDisposed() {
|
||||
return this._isDisposed;
|
||||
}
|
||||
}
|
||||
|
||||
export class DisposableStore extends Disposable {
|
||||
private readonly items = new Set<IDisposable>();
|
||||
|
||||
public override dispose() {
|
||||
super.dispose();
|
||||
disposeAll(this.items);
|
||||
this.items.clear();
|
||||
}
|
||||
|
||||
public add<T extends IDisposable>(item: T): T {
|
||||
if (this.isDisposed) {
|
||||
console.warn('Adding to disposed store. Item will be leaked');
|
||||
}
|
||||
|
||||
this.items.add(item);
|
||||
return item;
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
import { Connection, Emitter, FileChangeType, NotebookDocuments, TextDocuments } from 'vscode-languageserver';
|
||||
import { TextDocument } from 'vscode-languageserver-textdocument';
|
||||
import * as md from 'vscode-markdown-languageservice';
|
||||
import { ContainingDocumentContext } from 'vscode-markdown-languageservice/out/workspace';
|
||||
import { ContainingDocumentContext, FileWatcherOptions, IFileSystemWatcher } from 'vscode-markdown-languageservice/out/workspace';
|
||||
import { URI } from 'vscode-uri';
|
||||
import { LsConfiguration } from './config';
|
||||
import * as protocol from './protocol';
|
||||
|
@ -18,7 +18,7 @@ import { Schemes } from './util/schemes';
|
|||
|
||||
declare const TextDecoder: any;
|
||||
|
||||
export class VsCodeClientWorkspace implements md.IWorkspace {
|
||||
export class VsCodeClientWorkspace implements md.IWorkspaceWithWatching {
|
||||
|
||||
private readonly _onDidCreateMarkdownDocument = new Emitter<md.ITextDocument>();
|
||||
public readonly onDidCreateMarkdownDocument = this._onDidCreateMarkdownDocument.event;
|
||||
|
@ -33,11 +33,21 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
|
||||
private readonly _utf8Decoder = new TextDecoder('utf-8');
|
||||
|
||||
private _watcherPool = 0;
|
||||
private readonly _watchers = new Map<number, {
|
||||
readonly resource: URI;
|
||||
readonly options: FileWatcherOptions;
|
||||
readonly onDidChange: Emitter<URI>;
|
||||
readonly onDidCreate: Emitter<URI>;
|
||||
readonly onDidDelete: Emitter<URI>;
|
||||
}>();
|
||||
|
||||
constructor(
|
||||
private readonly connection: Connection,
|
||||
private readonly config: LsConfiguration,
|
||||
private readonly documents: TextDocuments<TextDocument>,
|
||||
private readonly notebooks: NotebookDocuments<TextDocument>,
|
||||
private readonly logger: md.ILogger,
|
||||
) {
|
||||
documents.onDidOpen(e => {
|
||||
this._documentCache.delete(URI.parse(e.document.uri));
|
||||
|
@ -59,6 +69,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
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}`);
|
||||
switch (change.type) {
|
||||
case FileChangeType.Changed: {
|
||||
this._documentCache.delete(resource);
|
||||
|
@ -83,6 +94,21 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
connection.onRequest(protocol.fs_watcher_onChange, params => {
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: fs_watcher_onChange', `${params.kind}: ${params.uri}`);
|
||||
|
||||
const watcher = this._watchers.get(params.id);
|
||||
if (!watcher) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (params.kind) {
|
||||
case 'create': watcher.onDidCreate.fire(URI.parse(params.uri)); return;
|
||||
case 'change': watcher.onDidChange.fire(URI.parse(params.uri)); return;
|
||||
case 'delete': watcher.onDidDelete.fire(URI.parse(params.uri)); return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public listen() {
|
||||
|
@ -108,7 +134,7 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
const limiter = new Limiter<md.ITextDocument | undefined>(maxConcurrent);
|
||||
|
||||
// Add files on disk
|
||||
const resources = await this.connection.sendRequest(protocol.findFilesRequestTypes, {});
|
||||
const resources = await this.connection.sendRequest(protocol.findMarkdownFilesInWorkspace, {});
|
||||
const onDiskResults = await Promise.all(resources.map(strResource => {
|
||||
return limiter.queue(async () => {
|
||||
const resource = URI.parse(strResource);
|
||||
|
@ -148,13 +174,13 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
}
|
||||
|
||||
try {
|
||||
const response = await this.connection.sendRequest(protocol.readFileRequestType, { uri: resource.toString() });
|
||||
const response = await this.connection.sendRequest(protocol.fs_readFile, { uri: resource.toString() });
|
||||
// TODO: LSP doesn't seem to handle Array buffers well
|
||||
const bytes = new Uint8Array(response);
|
||||
|
||||
// We assume that markdown is in UTF-8
|
||||
const text = this._utf8Decoder.decode(bytes);
|
||||
const doc = new md.InMemoryDocument(resource, text, 0);
|
||||
const doc = TextDocument.create(resource.toString(), 'markdown', 0, text);
|
||||
this._documentCache.set(resource, doc);
|
||||
return doc;
|
||||
} catch (e) {
|
||||
|
@ -163,14 +189,16 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
}
|
||||
|
||||
async stat(resource: URI): Promise<md.FileStat | undefined> {
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: stat', `${resource}`);
|
||||
if (this._documentCache.has(resource) || this.documents.get(resource.toString())) {
|
||||
return { isDirectory: false };
|
||||
}
|
||||
return this.connection.sendRequest(protocol.statFileRequestType, { uri: resource.toString() });
|
||||
return this.connection.sendRequest(protocol.fs_stat, { uri: resource.toString() });
|
||||
}
|
||||
|
||||
async readDirectory(resource: URI): Promise<[string, md.FileStat][]> {
|
||||
return this.connection.sendRequest(protocol.readDirectoryRequestType, { uri: resource.toString() });
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: readDir', `${resource}`);
|
||||
return this.connection.sendRequest(protocol.fs_readDirectory, { uri: resource.toString() });
|
||||
}
|
||||
|
||||
getContainingDocument(resource: URI): ContainingDocumentContext | undefined {
|
||||
|
@ -186,6 +214,37 @@ export class VsCodeClientWorkspace implements md.IWorkspace {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
watchFile(resource: URI, options: FileWatcherOptions): IFileSystemWatcher {
|
||||
const id = this._watcherPool++;
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: watchFile', `(${id}) ${resource}`);
|
||||
|
||||
const entry = {
|
||||
resource,
|
||||
options,
|
||||
onDidCreate: new Emitter<URI>(),
|
||||
onDidChange: new Emitter<URI>(),
|
||||
onDidDelete: new Emitter<URI>(),
|
||||
};
|
||||
this._watchers.set(id, entry);
|
||||
|
||||
this.connection.sendRequest(protocol.fs_watcher_create, {
|
||||
id,
|
||||
uri: resource.toString(),
|
||||
options,
|
||||
});
|
||||
|
||||
return {
|
||||
onDidCreate: entry.onDidCreate.event,
|
||||
onDidChange: entry.onDidChange.event,
|
||||
onDidDelete: entry.onDidDelete.event,
|
||||
dispose: () => {
|
||||
this.logger.log(md.LogLevel.Trace, 'VsCodeClientWorkspace: disposeWatcher', `(${id}) ${resource}`);
|
||||
this.connection.sendRequest(protocol.fs_watcher_delete, { id });
|
||||
this._watchers.delete(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private isRelevantMarkdownDocument(doc: TextDocument) {
|
||||
return isMarkdownFile(doc) && URI.parse(doc.uri).scheme !== 'vscode-bulkeditpreview';
|
||||
}
|
||||
|
|
|
@ -7,6 +7,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.43.tgz#555e5a743f76b6b897d47f945305b618525ddbe6"
|
||||
integrity sha512-GqWykok+3uocgfAJM8imbozrqLnPyTrpFlrryURQlw1EesPUCx5XxTiucWDSFF9/NUEXDuD4bnvHm8xfVGWTpQ==
|
||||
|
||||
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"
|
||||
|
@ -42,15 +47,22 @@ vscode-languageserver@^8.0.2-next.5`:
|
|||
dependencies:
|
||||
vscode-languageserver-protocol "3.17.2-next.6"
|
||||
|
||||
vscode-markdown-languageservice@^0.0.0-alpha.8:
|
||||
version "0.0.0-alpha.8"
|
||||
resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.8.tgz#05d4f86cf0514fd71479847eef742fcc8cdbe87f"
|
||||
integrity sha512-si8weZsY4LtyonyZwxpFYk8WucRFiKJisErNTt1HDjUCglSDIZqsMNuMIcz3t0nVNfG0LrpdMFVLGhmET5D71Q==
|
||||
vscode-markdown-languageservice@^0.0.0-alpha.11:
|
||||
version "0.0.0-alpha.11"
|
||||
resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.11.tgz#9dcdfc23f9dcaeaca3b2a2c76fc504619eaeb284"
|
||||
integrity sha512-syFamf99xx+Q9DkA66+8fbSz2A2LJkyOV+nSziGgAzdDPv4jkb7eWF6l+nUteHTGbRLQ+q0tfkj0A7OovueCSQ==
|
||||
dependencies:
|
||||
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-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"
|
||||
|
|
|
@ -3,22 +3,16 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Token = require('markdown-it/lib/token');
|
||||
import * as vscode from 'vscode';
|
||||
import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType, RequestType } from 'vscode-languageclient';
|
||||
import { BaseLanguageClient, LanguageClientOptions, NotebookDocumentSyncRegistrationType } from 'vscode-languageclient';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { IMdParser } from './markdownEngine';
|
||||
import { markdownFileExtensions } from './util/file';
|
||||
import * as proto from './protocol';
|
||||
import { looksLikeMarkdownPath, markdownFileExtensions } from './util/file';
|
||||
import { IMdWorkspace } from './workspace';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
const parseRequestType: RequestType<{ uri: string }, Token[], any> = new RequestType('markdown/parse');
|
||||
const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile');
|
||||
const statFileRequestType: RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any> = new RequestType('markdown/statFile');
|
||||
const readDirectoryRequestType: RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any> = new RequestType('markdown/readDirectory');
|
||||
const findFilesRequestTypes: RequestType<{}, string[], any> = new RequestType('markdown/findFiles');
|
||||
|
||||
export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient;
|
||||
|
||||
|
||||
|
@ -34,7 +28,16 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
|
|||
},
|
||||
initializationOptions: {
|
||||
markdownFileExtensions,
|
||||
}
|
||||
},
|
||||
diagnosticPullOptions: {
|
||||
onChange: true,
|
||||
onSave: true,
|
||||
onTabs: true,
|
||||
match(_documentSelector, resource) {
|
||||
return looksLikeMarkdownPath(resource);
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
const client = factory('markdown', localize('markdownServer.name', 'Markdown Language Server'), clientOptions);
|
||||
|
@ -54,7 +57,7 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
|
|||
});
|
||||
}
|
||||
|
||||
client.onRequest(parseRequestType, async (e) => {
|
||||
client.onRequest(proto.parse, async (e) => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
const doc = await workspace.getOrLoadMarkdownDocument(uri);
|
||||
if (doc) {
|
||||
|
@ -64,12 +67,12 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
|
|||
}
|
||||
});
|
||||
|
||||
client.onRequest(readFileRequestType, async (e): Promise<number[]> => {
|
||||
client.onRequest(proto.fs_readFile, async (e): Promise<number[]> => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
return Array.from(await vscode.workspace.fs.readFile(uri));
|
||||
});
|
||||
|
||||
client.onRequest(statFileRequestType, async (e): Promise<{ isDirectory: boolean } | undefined> => {
|
||||
client.onRequest(proto.fs_stat, async (e): Promise<{ isDirectory: boolean } | undefined> => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
try {
|
||||
const stat = await vscode.workspace.fs.stat(uri);
|
||||
|
@ -79,16 +82,32 @@ export async function startClient(factory: LanguageClientConstructor, workspace:
|
|||
}
|
||||
});
|
||||
|
||||
client.onRequest(readDirectoryRequestType, async (e): Promise<[string, { isDirectory: boolean }][]> => {
|
||||
client.onRequest(proto.fs_readDirectory, async (e): Promise<[string, { isDirectory: boolean }][]> => {
|
||||
const uri = vscode.Uri.parse(e.uri);
|
||||
const result = await vscode.workspace.fs.readDirectory(uri);
|
||||
return result.map(([name, type]) => [name, { isDirectory: type === vscode.FileType.Directory }]);
|
||||
});
|
||||
|
||||
client.onRequest(findFilesRequestTypes, async (): Promise<string[]> => {
|
||||
client.onRequest(proto.findMarkdownFilesInWorkspace, async (): Promise<string[]> => {
|
||||
return (await vscode.workspace.findFiles(mdFileGlob, '**/node_modules/**')).map(x => x.toString());
|
||||
});
|
||||
|
||||
const watchers = new Map<number, vscode.FileSystemWatcher>();
|
||||
|
||||
client.onRequest(proto.fs_watcher_create, async (params): Promise<void> => {
|
||||
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' }); });
|
||||
});
|
||||
|
||||
client.onRequest(proto.fs_watcher_delete, async (params): Promise<void> => {
|
||||
watchers.get(params.id)?.dispose();
|
||||
watchers.delete(params.id);
|
||||
});
|
||||
|
||||
await client.start();
|
||||
|
||||
return client;
|
||||
|
|
|
@ -26,6 +26,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
context.subscriptions.push(workspace);
|
||||
|
||||
const client = await startServer(context, workspace, engine);
|
||||
context.subscriptions.push({
|
||||
dispose: () => client.stop()
|
||||
});
|
||||
activateShared(context, client, workspace, engine, logger, contributions);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,12 +9,10 @@ import { CommandManager } from './commandManager';
|
|||
import * as commands from './commands/index';
|
||||
import { registerPasteSupport } from './languageFeatures/copyPaste';
|
||||
import { registerDiagnosticSupport } from './languageFeatures/diagnostics';
|
||||
import { MdLinkProvider } from './languageFeatures/documentLinks';
|
||||
import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor';
|
||||
import { registerFindFileReferenceSupport } from './languageFeatures/fileReferences';
|
||||
import { MdReferencesProvider } from './languageFeatures/references';
|
||||
import { ILogger } from './logging';
|
||||
import { IMdParser, MarkdownItEngine, MdParsingProvider } from './markdownEngine';
|
||||
import { MarkdownItEngine, MdParsingProvider } from './markdownEngine';
|
||||
import { MarkdownContributionProvider } from './markdownExtensions';
|
||||
import { MdDocumentRenderer } from './preview/documentRenderer';
|
||||
import { MarkdownPreviewManager } from './preview/previewManager';
|
||||
|
@ -45,7 +43,7 @@ export function activateShared(
|
|||
const previewManager = new MarkdownPreviewManager(contentProvider, workspace, logger, contributions, tocProvider);
|
||||
context.subscriptions.push(previewManager);
|
||||
|
||||
context.subscriptions.push(registerMarkdownLanguageFeatures(client, parser, workspace, commandManager, tocProvider, logger));
|
||||
context.subscriptions.push(registerMarkdownLanguageFeatures(client, commandManager));
|
||||
context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine, tocProvider));
|
||||
|
||||
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
|
||||
|
@ -55,23 +53,12 @@ export function activateShared(
|
|||
|
||||
function registerMarkdownLanguageFeatures(
|
||||
client: BaseLanguageClient,
|
||||
parser: IMdParser,
|
||||
workspace: IMdWorkspace,
|
||||
commandManager: CommandManager,
|
||||
tocProvider: MdTableOfContentsProvider,
|
||||
logger: ILogger,
|
||||
): vscode.Disposable {
|
||||
const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' };
|
||||
|
||||
const linkProvider = new MdLinkProvider(parser, workspace, logger);
|
||||
const referencesProvider = new MdReferencesProvider(parser, workspace, tocProvider, logger);
|
||||
|
||||
return vscode.Disposable.from(
|
||||
linkProvider,
|
||||
referencesProvider,
|
||||
|
||||
// Language features
|
||||
registerDiagnosticSupport(selector, workspace, linkProvider, commandManager, referencesProvider, tocProvider, logger),
|
||||
registerDiagnosticSupport(selector, commandManager),
|
||||
registerDropIntoEditorSupport(selector),
|
||||
registerFindFileReferenceSupport(commandManager, client),
|
||||
registerPasteSupport(selector),
|
||||
|
|
|
@ -26,6 +26,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||
context.subscriptions.push(workspace);
|
||||
|
||||
const client = await startServer(context, workspace, engine);
|
||||
context.subscriptions.push({
|
||||
dispose: () => client.stop()
|
||||
});
|
||||
activateShared(context, client, workspace, engine, logger, contributions);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,609 +3,20 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as picomatch from 'picomatch';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { CommandManager } from '../commandManager';
|
||||
import { ILogger } from '../logging';
|
||||
import { MdTableOfContentsProvider } from '../tableOfContents';
|
||||
import { ITextDocument } from '../types/textDocument';
|
||||
import { Delayer } from '../util/async';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { isMarkdownFile, looksLikeMarkdownPath } from '../util/file';
|
||||
import { Limiter } from '../util/limiter';
|
||||
import { ResourceMap } from '../util/resourceMap';
|
||||
import { MdTableOfContentsWatcher } from '../util/tableOfContentsWatcher';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { InternalHref, LinkDefinitionSet, MdLink, MdLinkProvider, MdLinkSource } from './documentLinks';
|
||||
import { MdReferencesProvider, tryResolveLinkPath } from './references';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface DiagnosticConfiguration {
|
||||
/**
|
||||
* Fired when the configuration changes.
|
||||
*/
|
||||
readonly onDidChange: vscode.Event<void>;
|
||||
|
||||
getOptions(resource: vscode.Uri): DiagnosticOptions;
|
||||
// Copied from markdown language service
|
||||
export enum DiagnosticCode {
|
||||
link_noSuchReferences = 'link.no-such-reference',
|
||||
link_noSuchHeaderInOwnFile = 'link.no-such-header-in-own-file',
|
||||
link_noSuchFile = 'link.no-such-file',
|
||||
link_noSuchHeaderInFile = 'link.no-such-header-in-file',
|
||||
}
|
||||
|
||||
export enum DiagnosticLevel {
|
||||
ignore = 'ignore',
|
||||
warning = 'warning',
|
||||
error = 'error',
|
||||
}
|
||||
|
||||
export interface DiagnosticOptions {
|
||||
readonly enabled: boolean;
|
||||
readonly validateReferences: DiagnosticLevel | undefined;
|
||||
readonly validateFragmentLinks: DiagnosticLevel | undefined;
|
||||
readonly validateFileLinks: DiagnosticLevel | undefined;
|
||||
readonly validateMarkdownFileLinkFragments: DiagnosticLevel | undefined;
|
||||
readonly ignoreLinks: readonly string[];
|
||||
}
|
||||
|
||||
function toSeverity(level: DiagnosticLevel | undefined): vscode.DiagnosticSeverity | undefined {
|
||||
switch (level) {
|
||||
case DiagnosticLevel.error: return vscode.DiagnosticSeverity.Error;
|
||||
case DiagnosticLevel.warning: return vscode.DiagnosticSeverity.Warning;
|
||||
case DiagnosticLevel.ignore: return undefined;
|
||||
case undefined: return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConfiguration {
|
||||
|
||||
private readonly _onDidChange = this._register(new vscode.EventEmitter<void>());
|
||||
public readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._register(vscode.workspace.onDidChangeConfiguration(e => {
|
||||
if (
|
||||
e.affectsConfiguration('markdown.experimental.validate.enabled')
|
||||
|| e.affectsConfiguration('markdown.experimental.validate.referenceLinks.enabled')
|
||||
|| e.affectsConfiguration('markdown.experimental.validate.fragmentLinks.enabled')
|
||||
|| e.affectsConfiguration('markdown.experimental.validate.fileLinks.enabled')
|
||||
|| e.affectsConfiguration('markdown.experimental.validate.fileLinks.markdownFragmentLinks')
|
||||
|| e.affectsConfiguration('markdown.experimental.validate.ignoreLinks')
|
||||
) {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public getOptions(resource: vscode.Uri): DiagnosticOptions {
|
||||
const config = vscode.workspace.getConfiguration('markdown', resource);
|
||||
const validateFragmentLinks = config.get<DiagnosticLevel>('experimental.validate.fragmentLinks.enabled');
|
||||
return {
|
||||
enabled: config.get<boolean>('experimental.validate.enabled', false),
|
||||
validateReferences: config.get<DiagnosticLevel>('experimental.validate.referenceLinks.enabled'),
|
||||
validateFragmentLinks,
|
||||
validateFileLinks: config.get<DiagnosticLevel>('experimental.validate.fileLinks.enabled'),
|
||||
validateMarkdownFileLinkFragments: config.get<DiagnosticLevel | undefined>('markdown.experimental.validate.fileLinks.markdownFragmentLinks', validateFragmentLinks),
|
||||
ignoreLinks: config.get('experimental.validate.ignoreLinks', []),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class InflightDiagnosticRequests {
|
||||
|
||||
private readonly inFlightRequests = new ResourceMap<{ readonly cts: vscode.CancellationTokenSource }>();
|
||||
|
||||
public async trigger(resource: vscode.Uri, compute: (token: vscode.CancellationToken) => Promise<void>): Promise<void> {
|
||||
this.cancel(resource);
|
||||
|
||||
const cts = new vscode.CancellationTokenSource();
|
||||
const entry = { cts };
|
||||
this.inFlightRequests.set(resource, entry);
|
||||
|
||||
try {
|
||||
return await compute(cts.token);
|
||||
} finally {
|
||||
if (this.inFlightRequests.get(resource) === entry) {
|
||||
this.inFlightRequests.delete(resource);
|
||||
}
|
||||
cts.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public cancel(resource: vscode.Uri) {
|
||||
const existing = this.inFlightRequests.get(resource);
|
||||
if (existing) {
|
||||
existing.cts.cancel();
|
||||
this.inFlightRequests.delete(resource);
|
||||
}
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public clear() {
|
||||
for (const { cts } of this.inFlightRequests.values()) {
|
||||
cts.dispose();
|
||||
}
|
||||
this.inFlightRequests.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class LinkWatcher extends Disposable {
|
||||
|
||||
private readonly _onDidChangeLinkedToFile = this._register(new vscode.EventEmitter<Iterable<vscode.Uri>>);
|
||||
/**
|
||||
* Event fired with a list of document uri when one of the links in the document changes
|
||||
*/
|
||||
public readonly onDidChangeLinkedToFile = this._onDidChangeLinkedToFile.event;
|
||||
|
||||
private readonly _watchers = new ResourceMap<{
|
||||
/**
|
||||
* Watcher for this link path
|
||||
*/
|
||||
readonly watcher: vscode.Disposable;
|
||||
|
||||
/**
|
||||
* List of documents that reference the link
|
||||
*/
|
||||
readonly documents: ResourceMap</* document resource*/ vscode.Uri>;
|
||||
}>();
|
||||
|
||||
override dispose() {
|
||||
super.dispose();
|
||||
|
||||
for (const entry of this._watchers.values()) {
|
||||
entry.watcher.dispose();
|
||||
}
|
||||
this._watchers.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the known links in a markdown document, adding and removing file watchers as needed
|
||||
*/
|
||||
updateLinksForDocument(document: vscode.Uri, links: readonly MdLink[]) {
|
||||
const linkedToResource = new Set<vscode.Uri>(
|
||||
links
|
||||
.filter(link => link.href.kind === 'internal')
|
||||
.map(link => (link.href as InternalHref).path));
|
||||
|
||||
// First decrement watcher counter for previous document state
|
||||
for (const entry of this._watchers.values()) {
|
||||
entry.documents.delete(document);
|
||||
}
|
||||
|
||||
// Then create/update watchers for new document state
|
||||
for (const path of linkedToResource) {
|
||||
let entry = this._watchers.get(path);
|
||||
if (!entry) {
|
||||
entry = {
|
||||
watcher: this.startWatching(path),
|
||||
documents: new ResourceMap(),
|
||||
};
|
||||
this._watchers.set(path, entry);
|
||||
}
|
||||
|
||||
entry.documents.set(document, document);
|
||||
}
|
||||
|
||||
// Finally clean up watchers for links that are no longer are referenced anywhere
|
||||
for (const [key, value] of this._watchers) {
|
||||
if (value.documents.size === 0) {
|
||||
value.watcher.dispose();
|
||||
this._watchers.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteDocument(resource: vscode.Uri) {
|
||||
this.updateLinksForDocument(resource, []);
|
||||
}
|
||||
|
||||
private startWatching(path: vscode.Uri): vscode.Disposable {
|
||||
const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(path, '*'), false, true, false);
|
||||
const handler = (resource: vscode.Uri) => this.onLinkedResourceChanged(resource);
|
||||
return vscode.Disposable.from(
|
||||
watcher,
|
||||
watcher.onDidDelete(handler),
|
||||
watcher.onDidCreate(handler),
|
||||
);
|
||||
}
|
||||
|
||||
private onLinkedResourceChanged(resource: vscode.Uri) {
|
||||
const entry = this._watchers.get(resource);
|
||||
if (entry) {
|
||||
this._onDidChangeLinkedToFile.fire(entry.documents.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LinkDoesNotExistDiagnostic extends vscode.Diagnostic {
|
||||
|
||||
public readonly link: string;
|
||||
|
||||
constructor(range: vscode.Range, message: string, severity: vscode.DiagnosticSeverity, link: string) {
|
||||
super(range, message, severity);
|
||||
this.link = link;
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class DiagnosticReporter extends Disposable {
|
||||
private readonly pending = new Set<Promise<any>>();
|
||||
|
||||
public clear(): void {
|
||||
this.pending.clear();
|
||||
}
|
||||
|
||||
public abstract set(uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]): void;
|
||||
|
||||
public abstract delete(uri: vscode.Uri): void;
|
||||
|
||||
public abstract isOpen(uri: vscode.Uri): boolean;
|
||||
|
||||
public abstract getOpenDocuments(): ITextDocument[];
|
||||
|
||||
public addWorkItem(promise: Promise<any>): Promise<any> {
|
||||
this.pending.add(promise);
|
||||
promise.finally(() => this.pending.delete(promise));
|
||||
return promise;
|
||||
}
|
||||
|
||||
public async waitPendingWork(): Promise<void> {
|
||||
await Promise.all([...this.pending.values()]);
|
||||
}
|
||||
}
|
||||
|
||||
export class DiagnosticCollectionReporter extends DiagnosticReporter {
|
||||
|
||||
private readonly collection: vscode.DiagnosticCollection;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.collection = this._register(vscode.languages.createDiagnosticCollection('markdown'));
|
||||
}
|
||||
|
||||
public override clear(): void {
|
||||
super.clear();
|
||||
this.collection.clear();
|
||||
}
|
||||
|
||||
public set(uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]): void {
|
||||
this.collection.set(uri, this.isOpen(uri) ? diagnostics : []);
|
||||
}
|
||||
|
||||
public isOpen(uri: vscode.Uri): boolean {
|
||||
const tabs = this.getTabResources();
|
||||
return tabs.has(uri);
|
||||
}
|
||||
|
||||
public delete(uri: vscode.Uri): void {
|
||||
this.collection.delete(uri);
|
||||
}
|
||||
|
||||
public getOpenDocuments(): ITextDocument[] {
|
||||
const tabs = this.getTabResources();
|
||||
return vscode.workspace.textDocuments.filter(doc => tabs.has(doc.uri));
|
||||
}
|
||||
|
||||
private getTabResources(): ResourceMap<void> {
|
||||
const openedTabDocs = new ResourceMap<void>();
|
||||
for (const group of vscode.window.tabGroups.all) {
|
||||
for (const tab of group.tabs) {
|
||||
if (tab.input instanceof vscode.TabInputText) {
|
||||
openedTabDocs.set(tab.input.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
return openedTabDocs;
|
||||
}
|
||||
}
|
||||
|
||||
export class DiagnosticManager extends Disposable {
|
||||
|
||||
private readonly diagnosticDelayer: Delayer<void>;
|
||||
private readonly pendingDiagnostics = new Set<vscode.Uri>();
|
||||
private readonly inFlightDiagnostics = this._register(new InflightDiagnosticRequests());
|
||||
|
||||
private readonly linkWatcher = this._register(new LinkWatcher());
|
||||
private readonly tableOfContentsWatcher: MdTableOfContentsWatcher;
|
||||
|
||||
public readonly ready: Promise<void>;
|
||||
|
||||
constructor(
|
||||
private readonly workspace: IMdWorkspace,
|
||||
private readonly computer: DiagnosticComputer,
|
||||
private readonly configuration: DiagnosticConfiguration,
|
||||
private readonly reporter: DiagnosticReporter,
|
||||
private readonly referencesProvider: MdReferencesProvider,
|
||||
tocProvider: MdTableOfContentsProvider,
|
||||
private readonly logger: ILogger,
|
||||
delay = 300,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.diagnosticDelayer = this._register(new Delayer(delay));
|
||||
|
||||
this._register(this.configuration.onDidChange(() => {
|
||||
this.rebuild();
|
||||
}));
|
||||
|
||||
this._register(workspace.onDidCreateMarkdownDocument(doc => {
|
||||
this.triggerDiagnostics(doc.uri);
|
||||
// Links in other files may have become valid
|
||||
this.triggerForReferencingFiles(doc.uri);
|
||||
}));
|
||||
|
||||
this._register(workspace.onDidChangeMarkdownDocument(doc => {
|
||||
this.triggerDiagnostics(doc.uri);
|
||||
}));
|
||||
|
||||
this._register(workspace.onDidDeleteMarkdownDocument(uri => {
|
||||
this.triggerForReferencingFiles(uri);
|
||||
}));
|
||||
|
||||
this._register(vscode.workspace.onDidCloseTextDocument(({ uri }) => {
|
||||
this.pendingDiagnostics.delete(uri);
|
||||
this.inFlightDiagnostics.cancel(uri);
|
||||
this.linkWatcher.deleteDocument(uri);
|
||||
this.reporter.delete(uri);
|
||||
}));
|
||||
|
||||
this._register(this.linkWatcher.onDidChangeLinkedToFile(changedDocuments => {
|
||||
for (const resource of changedDocuments) {
|
||||
const doc = vscode.workspace.textDocuments.find(doc => doc.uri.toString() === resource.toString());
|
||||
if (doc && isMarkdownFile(doc)) {
|
||||
this.triggerDiagnostics(doc.uri);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.tableOfContentsWatcher = this._register(new MdTableOfContentsWatcher(workspace, tocProvider, delay / 2));
|
||||
this._register(this.tableOfContentsWatcher.onTocChanged(e => {
|
||||
return this.triggerForReferencingFiles(e.uri);
|
||||
}));
|
||||
|
||||
this.ready = this.rebuild();
|
||||
}
|
||||
|
||||
private triggerForReferencingFiles(uri: vscode.Uri): Promise<void> {
|
||||
return this.reporter.addWorkItem(
|
||||
(async () => {
|
||||
const triggered = new ResourceMap<Promise<void>>();
|
||||
for (const ref of await this.referencesProvider.getReferencesToFileInDocs(uri, this.reporter.getOpenDocuments(), noopToken)) {
|
||||
const file = ref.location.uri;
|
||||
if (!triggered.has(file)) {
|
||||
triggered.set(file, this.triggerDiagnostics(file));
|
||||
}
|
||||
}
|
||||
await Promise.all(triggered.values());
|
||||
})());
|
||||
}
|
||||
|
||||
public override dispose() {
|
||||
super.dispose();
|
||||
this.pendingDiagnostics.clear();
|
||||
}
|
||||
|
||||
private async recomputeDiagnosticState(doc: ITextDocument, token: vscode.CancellationToken): Promise<{ diagnostics: readonly vscode.Diagnostic[]; links: readonly MdLink[]; config: DiagnosticOptions }> {
|
||||
this.logger.verbose('DiagnosticManager', `recomputeDiagnosticState - ${doc.uri}`);
|
||||
|
||||
const config = this.configuration.getOptions(doc.uri);
|
||||
if (!config.enabled) {
|
||||
return { diagnostics: [], links: [], config };
|
||||
}
|
||||
return { ...await this.computer.getDiagnostics(doc, config, token), config };
|
||||
}
|
||||
|
||||
private async recomputePendingDiagnostics(): Promise<void> {
|
||||
const pending = [...this.pendingDiagnostics];
|
||||
this.pendingDiagnostics.clear();
|
||||
|
||||
await Promise.all(pending.map(async resource => {
|
||||
const doc = await this.workspace.getOrLoadMarkdownDocument(resource);
|
||||
if (doc) {
|
||||
await this.inFlightDiagnostics.trigger(doc.uri, async (token) => {
|
||||
if (this.reporter.isOpen(doc.uri)) {
|
||||
const state = await this.recomputeDiagnosticState(doc, token);
|
||||
this.linkWatcher.updateLinksForDocument(doc.uri, state.config.enabled && state.config.validateFileLinks ? state.links : []);
|
||||
this.reporter.set(doc.uri, state.diagnostics);
|
||||
} else {
|
||||
this.linkWatcher.deleteDocument(doc.uri);
|
||||
this.reporter.delete(doc.uri);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private rebuild(): Promise<void> {
|
||||
this.reporter.clear();
|
||||
this.pendingDiagnostics.clear();
|
||||
this.inFlightDiagnostics.clear();
|
||||
|
||||
return this.reporter.addWorkItem(
|
||||
Promise.all(Array.from(this.reporter.getOpenDocuments(), doc => this.triggerDiagnostics(doc.uri)))
|
||||
);
|
||||
}
|
||||
|
||||
private async triggerDiagnostics(uri: vscode.Uri): Promise<void> {
|
||||
this.inFlightDiagnostics.cancel(uri);
|
||||
|
||||
this.pendingDiagnostics.add(uri);
|
||||
return this.reporter.addWorkItem(
|
||||
this.diagnosticDelayer.trigger(() => this.recomputePendingDiagnostics())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of file paths to markdown links to that file.
|
||||
*/
|
||||
class FileLinkMap {
|
||||
|
||||
private readonly _filesToLinksMap = new ResourceMap<{
|
||||
readonly outgoingLinks: Array<{
|
||||
readonly source: MdLinkSource;
|
||||
readonly fragment: string;
|
||||
}>;
|
||||
}>();
|
||||
|
||||
constructor(links: Iterable<MdLink>) {
|
||||
for (const link of links) {
|
||||
if (link.href.kind !== 'internal') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingFileEntry = this._filesToLinksMap.get(link.href.path);
|
||||
const linkData = { source: link.source, fragment: link.href.fragment };
|
||||
if (existingFileEntry) {
|
||||
existingFileEntry.outgoingLinks.push(linkData);
|
||||
} else {
|
||||
this._filesToLinksMap.set(link.href.path, { outgoingLinks: [linkData] });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this._filesToLinksMap.size;
|
||||
}
|
||||
|
||||
public entries() {
|
||||
return this._filesToLinksMap.entries();
|
||||
}
|
||||
}
|
||||
|
||||
export class DiagnosticComputer {
|
||||
|
||||
constructor(
|
||||
private readonly workspace: IMdWorkspace,
|
||||
private readonly linkProvider: MdLinkProvider,
|
||||
private readonly tocProvider: MdTableOfContentsProvider,
|
||||
) { }
|
||||
|
||||
public async getDiagnostics(doc: ITextDocument, options: DiagnosticOptions, token: vscode.CancellationToken): Promise<{ readonly diagnostics: vscode.Diagnostic[]; readonly links: readonly MdLink[] }> {
|
||||
const { links, definitions } = await this.linkProvider.getLinks(doc);
|
||||
if (token.isCancellationRequested || !options.enabled) {
|
||||
return { links, diagnostics: [] };
|
||||
}
|
||||
|
||||
return {
|
||||
links,
|
||||
diagnostics: (await Promise.all([
|
||||
this.validateFileLinks(options, links, token),
|
||||
Array.from(this.validateReferenceLinks(options, links, definitions)),
|
||||
this.validateFragmentLinks(doc, options, links, token),
|
||||
])).flat()
|
||||
};
|
||||
}
|
||||
|
||||
private async validateFragmentLinks(doc: ITextDocument, options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise<vscode.Diagnostic[]> {
|
||||
const severity = toSeverity(options.validateFragmentLinks);
|
||||
if (typeof severity === 'undefined') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const toc = await this.tocProvider.getForDocument(doc);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const diagnostics: vscode.Diagnostic[] = [];
|
||||
for (const link of links) {
|
||||
if (link.href.kind === 'internal'
|
||||
&& link.source.hrefText.startsWith('#')
|
||||
&& link.href.path.toString() === doc.uri.toString()
|
||||
&& link.href.fragment
|
||||
&& !toc.lookup(link.href.fragment)
|
||||
) {
|
||||
if (!this.isIgnoredLink(options, link.source.hrefText)) {
|
||||
diagnostics.push(new LinkDoesNotExistDiagnostic(
|
||||
link.source.hrefRange,
|
||||
localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment),
|
||||
severity,
|
||||
link.source.hrefText));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
private *validateReferenceLinks(options: DiagnosticOptions, links: readonly MdLink[], definitions: LinkDefinitionSet): Iterable<vscode.Diagnostic> {
|
||||
const severity = toSeverity(options.validateReferences);
|
||||
if (typeof severity === 'undefined') {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const link of links) {
|
||||
if (link.href.kind === 'reference' && !definitions.lookup(link.href.ref)) {
|
||||
yield new vscode.Diagnostic(
|
||||
link.source.hrefRange,
|
||||
localize('invalidReferenceLink', 'No link definition found: \'{0}\'', link.href.ref),
|
||||
severity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async validateFileLinks(options: DiagnosticOptions, links: readonly MdLink[], token: vscode.CancellationToken): Promise<vscode.Diagnostic[]> {
|
||||
const pathErrorSeverity = toSeverity(options.validateFileLinks);
|
||||
if (typeof pathErrorSeverity === 'undefined') {
|
||||
return [];
|
||||
}
|
||||
const fragmentErrorSeverity = toSeverity(typeof options.validateMarkdownFileLinkFragments === 'undefined' ? options.validateFragmentLinks : options.validateMarkdownFileLinkFragments);
|
||||
|
||||
// We've already validated our own fragment links in `validateOwnHeaderLinks`
|
||||
const linkSet = new FileLinkMap(links.filter(link => !link.source.hrefText.startsWith('#')));
|
||||
if (linkSet.size === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const limiter = new Limiter(10);
|
||||
|
||||
const diagnostics: vscode.Diagnostic[] = [];
|
||||
await Promise.all(
|
||||
Array.from(linkSet.entries()).map(([path, { outgoingLinks: links }]) => {
|
||||
return limiter.queue(async () => {
|
||||
if (token.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedHrefPath = await tryResolveLinkPath(path, this.workspace);
|
||||
if (!resolvedHrefPath) {
|
||||
const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.fsPath);
|
||||
for (const link of links) {
|
||||
if (!this.isIgnoredLink(options, link.source.pathText)) {
|
||||
diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, pathErrorSeverity, link.source.pathText));
|
||||
}
|
||||
}
|
||||
} else if (typeof fragmentErrorSeverity !== 'undefined' && this.isMarkdownPath(resolvedHrefPath)) {
|
||||
// Validate each of the links to headers in the file
|
||||
const fragmentLinks = links.filter(x => x.fragment);
|
||||
if (fragmentLinks.length) {
|
||||
const toc = await this.tocProvider.get(resolvedHrefPath);
|
||||
for (const link of fragmentLinks) {
|
||||
if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.pathText) && !this.isIgnoredLink(options, link.source.hrefText)) {
|
||||
const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment);
|
||||
const range = link.source.fragmentRange?.with({ start: link.source.fragmentRange.start.translate(0, -1) }) ?? link.source.hrefRange;
|
||||
diagnostics.push(new LinkDoesNotExistDiagnostic(range, msg, fragmentErrorSeverity, link.source.hrefText));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
private isMarkdownPath(resolvedHrefPath: vscode.Uri) {
|
||||
return this.workspace.hasMarkdownDocument(resolvedHrefPath) || looksLikeMarkdownPath(resolvedHrefPath);
|
||||
}
|
||||
|
||||
private isIgnoredLink(options: DiagnosticOptions, link: string): boolean {
|
||||
return options.ignoreLinks.some(glob => picomatch.isMatch(link, glob));
|
||||
}
|
||||
}
|
||||
|
||||
class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider {
|
||||
|
||||
|
@ -636,17 +47,26 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider {
|
|||
const fixes: vscode.CodeAction[] = [];
|
||||
|
||||
for (const diagnostic of context.diagnostics) {
|
||||
if (diagnostic instanceof LinkDoesNotExistDiagnostic) {
|
||||
const fix = new vscode.CodeAction(
|
||||
localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", diagnostic.link),
|
||||
vscode.CodeActionKind.QuickFix);
|
||||
switch (diagnostic.code) {
|
||||
case DiagnosticCode.link_noSuchReferences:
|
||||
case DiagnosticCode.link_noSuchHeaderInOwnFile:
|
||||
case DiagnosticCode.link_noSuchFile:
|
||||
case DiagnosticCode.link_noSuchHeaderInFile: {
|
||||
const hrefText = (diagnostic as any).data?.hrefText;
|
||||
if (hrefText) {
|
||||
const fix = new vscode.CodeAction(
|
||||
localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", hrefText),
|
||||
vscode.CodeActionKind.QuickFix);
|
||||
|
||||
fix.command = {
|
||||
command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId,
|
||||
title: '',
|
||||
arguments: [document.uri, diagnostic.link]
|
||||
};
|
||||
fixes.push(fix);
|
||||
fix.command = {
|
||||
command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId,
|
||||
title: '',
|
||||
arguments: [document.uri, hrefText],
|
||||
};
|
||||
fixes.push(fix);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,26 +74,10 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
export function registerDiagnosticSupport(
|
||||
selector: vscode.DocumentSelector,
|
||||
workspace: IMdWorkspace,
|
||||
linkProvider: MdLinkProvider,
|
||||
commandManager: CommandManager,
|
||||
referenceProvider: MdReferencesProvider,
|
||||
tocProvider: MdTableOfContentsProvider,
|
||||
logger: ILogger,
|
||||
): vscode.Disposable {
|
||||
const configuration = new VSCodeDiagnosticConfiguration();
|
||||
const manager = new DiagnosticManager(
|
||||
workspace,
|
||||
new DiagnosticComputer(workspace, linkProvider, tocProvider),
|
||||
configuration,
|
||||
new DiagnosticCollectionReporter(),
|
||||
referenceProvider,
|
||||
tocProvider,
|
||||
logger);
|
||||
return vscode.Disposable.from(
|
||||
configuration,
|
||||
manager,
|
||||
AddToIgnoreLinksQuickFixProvider.register(selector, commandManager));
|
||||
return AddToIgnoreLinksQuickFixProvider.register(selector, commandManager);
|
||||
}
|
||||
|
|
|
@ -1,540 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as uri from 'vscode-uri';
|
||||
import { ILogger } from '../logging';
|
||||
import { IMdParser } from '../markdownEngine';
|
||||
import { getLine, ITextDocument } from '../types/textDocument';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { Schemes } from '../util/schemes';
|
||||
import { MdDocumentInfoCache } from '../util/workspaceCache';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
|
||||
export interface ExternalHref {
|
||||
readonly kind: 'external';
|
||||
readonly uri: vscode.Uri;
|
||||
}
|
||||
|
||||
export interface InternalHref {
|
||||
readonly kind: 'internal';
|
||||
readonly path: vscode.Uri;
|
||||
readonly fragment: string;
|
||||
}
|
||||
|
||||
export interface ReferenceHref {
|
||||
readonly kind: 'reference';
|
||||
readonly ref: string;
|
||||
}
|
||||
|
||||
export type LinkHref = ExternalHref | InternalHref | ReferenceHref;
|
||||
|
||||
|
||||
function resolveLink(
|
||||
document: ITextDocument,
|
||||
link: string,
|
||||
): ExternalHref | InternalHref | undefined {
|
||||
const cleanLink = stripAngleBrackets(link);
|
||||
|
||||
if (/^[a-z\-][a-z\-]+:/i.test(cleanLink)) {
|
||||
// Looks like a uri
|
||||
return { kind: 'external', uri: vscode.Uri.parse(cleanLink) };
|
||||
}
|
||||
|
||||
// Assume it must be an relative or absolute file path
|
||||
// Use a fake scheme to avoid parse warnings
|
||||
const tempUri = vscode.Uri.parse(`vscode-resource:${link}`);
|
||||
|
||||
let resourceUri: vscode.Uri | undefined;
|
||||
if (!tempUri.path) {
|
||||
resourceUri = document.uri;
|
||||
} else if (tempUri.path[0] === '/') {
|
||||
const root = getWorkspaceFolder(document);
|
||||
if (root) {
|
||||
resourceUri = vscode.Uri.joinPath(root, tempUri.path);
|
||||
}
|
||||
} else {
|
||||
if (document.uri.scheme === Schemes.untitled) {
|
||||
const root = getWorkspaceFolder(document);
|
||||
if (root) {
|
||||
resourceUri = vscode.Uri.joinPath(root, tempUri.path);
|
||||
}
|
||||
} else {
|
||||
const base = uri.Utils.dirname(document.uri);
|
||||
resourceUri = vscode.Uri.joinPath(base, tempUri.path);
|
||||
}
|
||||
}
|
||||
|
||||
if (!resourceUri) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If we are in a notebook cell, resolve relative to notebook instead
|
||||
if (resourceUri.scheme === Schemes.notebookCell) {
|
||||
const notebook = vscode.workspace.notebookDocuments
|
||||
.find(notebook => notebook.getCells().some(cell => cell.document === document));
|
||||
|
||||
if (notebook) {
|
||||
resourceUri = resourceUri.with({ scheme: notebook.uri.scheme });
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
kind: 'internal',
|
||||
path: resourceUri.with({ fragment: '' }),
|
||||
fragment: tempUri.fragment,
|
||||
};
|
||||
}
|
||||
|
||||
function getWorkspaceFolder(document: ITextDocument) {
|
||||
return vscode.workspace.getWorkspaceFolder(document.uri)?.uri
|
||||
|| vscode.workspace.workspaceFolders?.[0]?.uri;
|
||||
}
|
||||
|
||||
export interface MdLinkSource {
|
||||
/**
|
||||
* The full range of the link.
|
||||
*/
|
||||
readonly range: vscode.Range;
|
||||
|
||||
/**
|
||||
* The file where the link is defined.
|
||||
*/
|
||||
readonly resource: vscode.Uri;
|
||||
|
||||
/**
|
||||
* The original text of the link destination in code.
|
||||
*/
|
||||
readonly hrefText: string;
|
||||
|
||||
/**
|
||||
* The original text of just the link's path in code.
|
||||
*/
|
||||
readonly pathText: string;
|
||||
|
||||
/**
|
||||
* The range of the path.
|
||||
*/
|
||||
readonly hrefRange: vscode.Range;
|
||||
|
||||
/**
|
||||
* The range of the fragment within the path.
|
||||
*/
|
||||
readonly fragmentRange: vscode.Range | undefined;
|
||||
}
|
||||
|
||||
export interface MdInlineLink {
|
||||
readonly kind: 'link';
|
||||
readonly source: MdLinkSource;
|
||||
readonly href: LinkHref;
|
||||
}
|
||||
|
||||
export interface MdLinkDefinition {
|
||||
readonly kind: 'definition';
|
||||
readonly source: MdLinkSource;
|
||||
readonly ref: {
|
||||
readonly range: vscode.Range;
|
||||
readonly text: string;
|
||||
};
|
||||
readonly href: ExternalHref | InternalHref;
|
||||
}
|
||||
|
||||
export type MdLink = MdInlineLink | MdLinkDefinition;
|
||||
|
||||
function extractDocumentLink(
|
||||
document: ITextDocument,
|
||||
pre: string,
|
||||
rawLink: string,
|
||||
matchIndex: number,
|
||||
fullMatch: string,
|
||||
): MdLink | undefined {
|
||||
const isAngleBracketLink = rawLink.startsWith('<');
|
||||
const link = stripAngleBrackets(rawLink);
|
||||
|
||||
let linkTarget: ExternalHref | InternalHref | undefined;
|
||||
try {
|
||||
linkTarget = resolveLink(document, link);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
if (!linkTarget) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const linkStart = document.positionAt(matchIndex);
|
||||
const linkEnd = linkStart.translate(0, fullMatch.length);
|
||||
const hrefStart = linkStart.translate(0, pre.length + (isAngleBracketLink ? 1 : 0));
|
||||
const hrefEnd = hrefStart.translate(0, link.length);
|
||||
return {
|
||||
kind: 'link',
|
||||
href: linkTarget,
|
||||
source: {
|
||||
hrefText: link,
|
||||
resource: document.uri,
|
||||
range: new vscode.Range(linkStart, linkEnd),
|
||||
hrefRange: new vscode.Range(hrefStart, hrefEnd),
|
||||
...getLinkSourceFragmentInfo(document, link, hrefStart, hrefEnd),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getFragmentRange(text: string, start: vscode.Position, end: vscode.Position): vscode.Range | undefined {
|
||||
const index = text.indexOf('#');
|
||||
if (index < 0) {
|
||||
return undefined;
|
||||
}
|
||||
return new vscode.Range(start.translate({ characterDelta: index + 1 }), end);
|
||||
}
|
||||
|
||||
function getLinkSourceFragmentInfo(document: ITextDocument, link: string, linkStart: vscode.Position, linkEnd: vscode.Position): { fragmentRange: vscode.Range | undefined; pathText: string } {
|
||||
const fragmentRange = getFragmentRange(link, linkStart, linkEnd);
|
||||
return {
|
||||
pathText: document.getText(new vscode.Range(linkStart, fragmentRange ? fragmentRange.start.translate(0, -1) : linkEnd)),
|
||||
fragmentRange,
|
||||
};
|
||||
}
|
||||
|
||||
const angleBracketLinkRe = /^<(.*)>$/;
|
||||
|
||||
/**
|
||||
* Used to strip brackets from the markdown link
|
||||
*
|
||||
* <http://example.com> will be transformed to http://example.com
|
||||
*/
|
||||
function stripAngleBrackets(link: string) {
|
||||
return link.replace(angleBracketLinkRe, '$1');
|
||||
}
|
||||
|
||||
const r = String.raw;
|
||||
|
||||
/**
|
||||
* Matches `[text](link)` or `[text](<link>)`
|
||||
*/
|
||||
const linkPattern = new RegExp(
|
||||
// text
|
||||
r`(\[` + // open prefix match -->
|
||||
/**/r`(?:` +
|
||||
/*****/r`[^\[\]\\]|` + // Non-bracket chars, or...
|
||||
/*****/r`\\.|` + // Escaped char, or...
|
||||
/*****/r`\[[^\[\]]*\]` + // Matched bracket pair
|
||||
/**/r`)*` +
|
||||
r`\]` +
|
||||
|
||||
// Destination
|
||||
r`\(\s*)` + // <-- close prefix match
|
||||
/**/r`(` +
|
||||
/*****/r`[^\s\(\)\<](?:[^\s\(\)]|\([^\s\(\)]*?\))*|` + // Link without whitespace, or...
|
||||
/*****/r`<[^<>]+>` + // In angle brackets
|
||||
/**/r`)` +
|
||||
|
||||
// Title
|
||||
/**/r`\s*(?:"[^"]*"|'[^']*'|\([^\(\)]*\))?\s*` +
|
||||
r`\)`,
|
||||
'g');
|
||||
|
||||
/**
|
||||
* Matches `[text][ref]` or `[shorthand]`
|
||||
*/
|
||||
const referenceLinkPattern = /(^|[^\]\\])(?:(?:(\[((?:\\\]|[^\]])+)\]\[\s*?)([^\s\]]*?)\]|\[\s*?([^\s\\\]]*?)\])(?![\:\(]))/gm;
|
||||
|
||||
/**
|
||||
* Matches `<http://example.com>`
|
||||
*/
|
||||
const autoLinkPattern = /\<(\w+:[^\>\s]+)\>/g;
|
||||
|
||||
/**
|
||||
* Matches `[text]: link`
|
||||
*/
|
||||
const definitionPattern = /^([\t ]*\[(?!\^)((?:\\\]|[^\]])+)\]:\s*)([^<]\S*|<[^>]+>)/gm;
|
||||
|
||||
const inlineCodePattern = /(?:^|[^`])(`+)(?:.+?|.*?(?:(?:\r?\n).+?)*?)(?:\r?\n)?\1(?:$|[^`])/gm;
|
||||
|
||||
class NoLinkRanges {
|
||||
public static async compute(tokenizer: IMdParser, document: ITextDocument): Promise<NoLinkRanges> {
|
||||
const tokens = await tokenizer.tokenize(document);
|
||||
const multiline = tokens.filter(t => (t.type === 'code_block' || t.type === 'fence' || t.type === 'html_block') && !!t.map).map(t => t.map) as [number, number][];
|
||||
|
||||
const inlineRanges = new Map</* line number */ number, vscode.Range[]>();
|
||||
const text = document.getText();
|
||||
for (const match of text.matchAll(inlineCodePattern)) {
|
||||
const startOffset = match.index ?? 0;
|
||||
const startPosition = document.positionAt(startOffset);
|
||||
|
||||
const range = new vscode.Range(startPosition, document.positionAt(startOffset + match[0].length));
|
||||
for (let line = range.start.line; line <= range.end.line; ++line) {
|
||||
let entry = inlineRanges.get(line);
|
||||
if (!entry) {
|
||||
entry = [];
|
||||
inlineRanges.set(line, entry);
|
||||
}
|
||||
entry.push(range);
|
||||
}
|
||||
}
|
||||
|
||||
return new NoLinkRanges(multiline, inlineRanges);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
/**
|
||||
* code blocks and fences each represented by [line_start,line_end).
|
||||
*/
|
||||
public readonly multiline: ReadonlyArray<[number, number]>,
|
||||
|
||||
/**
|
||||
* Inline code spans where links should not be detected
|
||||
*/
|
||||
public readonly inline: Map</* line number */ number, vscode.Range[]>
|
||||
) { }
|
||||
|
||||
contains(position: vscode.Position): boolean {
|
||||
return this.multiline.some(interval => position.line >= interval[0] && position.line < interval[1]) ||
|
||||
!!this.inline.get(position.line)?.some(inlineRange => inlineRange.contains(position));
|
||||
}
|
||||
|
||||
concatInline(inlineRanges: Iterable<vscode.Range>): NoLinkRanges {
|
||||
const newInline = new Map(this.inline);
|
||||
for (const range of inlineRanges) {
|
||||
for (let line = range.start.line; line <= range.end.line; ++line) {
|
||||
let entry = newInline.get(line);
|
||||
if (!entry) {
|
||||
entry = [];
|
||||
newInline.set(line, entry);
|
||||
}
|
||||
entry.push(range);
|
||||
}
|
||||
}
|
||||
return new NoLinkRanges(this.multiline, newInline);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stateless object that extracts link information from markdown files.
|
||||
*/
|
||||
export class MdLinkComputer {
|
||||
|
||||
constructor(
|
||||
private readonly tokenizer: IMdParser,
|
||||
) { }
|
||||
|
||||
public async getAllLinks(document: ITextDocument, token: vscode.CancellationToken): Promise<MdLink[]> {
|
||||
const noLinkRanges = await NoLinkRanges.compute(this.tokenizer, document);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const inlineLinks = Array.from(this.getInlineLinks(document, noLinkRanges));
|
||||
return Array.from([
|
||||
...inlineLinks,
|
||||
...this.getReferenceLinks(document, noLinkRanges.concatInline(inlineLinks.map(x => x.source.range))),
|
||||
...this.getLinkDefinitions(document, noLinkRanges),
|
||||
...this.getAutoLinks(document, noLinkRanges),
|
||||
]);
|
||||
}
|
||||
|
||||
private *getInlineLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable<MdLink> {
|
||||
const text = document.getText();
|
||||
for (const match of text.matchAll(linkPattern)) {
|
||||
const matchLinkData = extractDocumentLink(document, match[1], match[2], match.index ?? 0, match[0]);
|
||||
if (matchLinkData && !noLinkRanges.contains(matchLinkData.source.hrefRange.start)) {
|
||||
yield matchLinkData;
|
||||
|
||||
// Also check link destination for links
|
||||
for (const innerMatch of match[1].matchAll(linkPattern)) {
|
||||
const innerData = extractDocumentLink(document, innerMatch[1], innerMatch[2], (match.index ?? 0) + (innerMatch.index ?? 0), innerMatch[0]);
|
||||
if (innerData) {
|
||||
yield innerData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private *getAutoLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable<MdLink> {
|
||||
const text = document.getText();
|
||||
for (const match of text.matchAll(autoLinkPattern)) {
|
||||
const linkOffset = (match.index ?? 0);
|
||||
const linkStart = document.positionAt(linkOffset);
|
||||
if (noLinkRanges.contains(linkStart)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const link = match[1];
|
||||
const linkTarget = resolveLink(document, link);
|
||||
if (!linkTarget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const linkEnd = linkStart.translate(0, match[0].length);
|
||||
const hrefStart = linkStart.translate(0, 1);
|
||||
const hrefEnd = hrefStart.translate(0, link.length);
|
||||
yield {
|
||||
kind: 'link',
|
||||
href: linkTarget,
|
||||
source: {
|
||||
hrefText: link,
|
||||
resource: document.uri,
|
||||
hrefRange: new vscode.Range(hrefStart, hrefEnd),
|
||||
range: new vscode.Range(linkStart, linkEnd),
|
||||
...getLinkSourceFragmentInfo(document, link, hrefStart, hrefEnd),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private *getReferenceLinks(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable<MdLink> {
|
||||
const text = document.getText();
|
||||
for (const match of text.matchAll(referenceLinkPattern)) {
|
||||
const linkStartOffset = (match.index ?? 0) + match[1].length;
|
||||
const linkStart = document.positionAt(linkStartOffset);
|
||||
if (noLinkRanges.contains(linkStart)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let hrefStart: vscode.Position;
|
||||
let hrefEnd: vscode.Position;
|
||||
let reference = match[4];
|
||||
if (reference === '') { // [ref][],
|
||||
reference = match[3];
|
||||
const offset = linkStartOffset + 1;
|
||||
hrefStart = document.positionAt(offset);
|
||||
hrefEnd = document.positionAt(offset + reference.length);
|
||||
} else if (reference) { // [text][ref]
|
||||
const pre = match[2];
|
||||
const offset = linkStartOffset + pre.length;
|
||||
hrefStart = document.positionAt(offset);
|
||||
hrefEnd = document.positionAt(offset + reference.length);
|
||||
} else if (match[5]) { // [ref]
|
||||
reference = match[5];
|
||||
const offset = linkStartOffset + 1;
|
||||
hrefStart = document.positionAt(offset);
|
||||
const line = getLine(document, hrefStart.line);
|
||||
// See if link looks like a checkbox
|
||||
const checkboxMatch = line.match(/^\s*[\-\*]\s*\[x\]/i);
|
||||
if (checkboxMatch && hrefStart.character <= checkboxMatch[0].length) {
|
||||
continue;
|
||||
}
|
||||
hrefEnd = document.positionAt(offset + reference.length);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
const linkEnd = linkStart.translate(0, match[0].length - match[1].length);
|
||||
yield {
|
||||
kind: 'link',
|
||||
source: {
|
||||
hrefText: reference,
|
||||
pathText: reference,
|
||||
resource: document.uri,
|
||||
range: new vscode.Range(linkStart, linkEnd),
|
||||
hrefRange: new vscode.Range(hrefStart, hrefEnd),
|
||||
fragmentRange: undefined,
|
||||
},
|
||||
href: {
|
||||
kind: 'reference',
|
||||
ref: reference,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private *getLinkDefinitions(document: ITextDocument, noLinkRanges: NoLinkRanges): Iterable<MdLinkDefinition> {
|
||||
const text = document.getText();
|
||||
for (const match of text.matchAll(definitionPattern)) {
|
||||
const offset = (match.index ?? 0);
|
||||
const linkStart = document.positionAt(offset);
|
||||
if (noLinkRanges.contains(linkStart)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pre = match[1];
|
||||
const reference = match[2];
|
||||
const rawLinkText = match[3].trim();
|
||||
const target = resolveLink(document, rawLinkText);
|
||||
if (!target) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isAngleBracketLink = angleBracketLinkRe.test(rawLinkText);
|
||||
const linkText = stripAngleBrackets(rawLinkText);
|
||||
const hrefStart = linkStart.translate(0, pre.length + (isAngleBracketLink ? 1 : 0));
|
||||
const hrefEnd = hrefStart.translate(0, linkText.length);
|
||||
const hrefRange = new vscode.Range(hrefStart, hrefEnd);
|
||||
|
||||
const refStart = linkStart.translate(0, 1);
|
||||
const refRange = new vscode.Range(refStart, refStart.translate({ characterDelta: reference.length }));
|
||||
const linkEnd = linkStart.translate(0, match[0].length);
|
||||
yield {
|
||||
kind: 'definition',
|
||||
source: {
|
||||
hrefText: linkText,
|
||||
resource: document.uri,
|
||||
range: new vscode.Range(linkStart, linkEnd),
|
||||
hrefRange,
|
||||
...getLinkSourceFragmentInfo(document, rawLinkText, hrefStart, hrefEnd),
|
||||
},
|
||||
ref: { text: reference, range: refRange },
|
||||
href: target,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface MdDocumentLinks {
|
||||
readonly links: readonly MdLink[];
|
||||
readonly definitions: LinkDefinitionSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stateful object which provides links for markdown files the workspace.
|
||||
*/
|
||||
export class MdLinkProvider extends Disposable {
|
||||
|
||||
private readonly _linkCache: MdDocumentInfoCache<MdDocumentLinks>;
|
||||
|
||||
private readonly linkComputer: MdLinkComputer;
|
||||
|
||||
constructor(
|
||||
tokenizer: IMdParser,
|
||||
workspace: IMdWorkspace,
|
||||
logger: ILogger,
|
||||
) {
|
||||
super();
|
||||
this.linkComputer = new MdLinkComputer(tokenizer);
|
||||
this._linkCache = this._register(new MdDocumentInfoCache(workspace, async doc => {
|
||||
logger.verbose('LinkProvider', `compute - ${doc.uri}`);
|
||||
|
||||
const links = await this.linkComputer.getAllLinks(doc, noopToken);
|
||||
return {
|
||||
links,
|
||||
definitions: new LinkDefinitionSet(links),
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
public async getLinks(document: ITextDocument): Promise<MdDocumentLinks> {
|
||||
return this._linkCache.getForDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkDefinitionSet implements Iterable<[string, MdLinkDefinition]> {
|
||||
private readonly _map = new Map<string, MdLinkDefinition>();
|
||||
|
||||
constructor(links: Iterable<MdLink>) {
|
||||
for (const link of links) {
|
||||
if (link.kind === 'definition') {
|
||||
this._map.set(link.ref.text, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public [Symbol.iterator](): Iterator<[string, MdLinkDefinition]> {
|
||||
return this._map.entries();
|
||||
}
|
||||
|
||||
public lookup(ref: string): MdLinkDefinition | undefined {
|
||||
return this._map.get(ref);
|
||||
}
|
||||
}
|
|
@ -1,329 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as uri from 'vscode-uri';
|
||||
import { ILogger } from '../logging';
|
||||
import { IMdParser } from '../markdownEngine';
|
||||
import { MdTableOfContentsProvider, TocEntry } from '../tableOfContents';
|
||||
import { ITextDocument } from '../types/textDocument';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { Disposable } from '../util/dispose';
|
||||
import { looksLikeMarkdownPath } from '../util/file';
|
||||
import { MdWorkspaceInfoCache } from '../util/workspaceCache';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { InternalHref, MdLink, MdLinkComputer } from './documentLinks';
|
||||
|
||||
|
||||
/**
|
||||
* A link in a markdown file.
|
||||
*/
|
||||
export interface MdLinkReference {
|
||||
readonly kind: 'link';
|
||||
readonly isTriggerLocation: boolean;
|
||||
readonly isDefinition: boolean;
|
||||
readonly location: vscode.Location;
|
||||
|
||||
readonly link: MdLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* A header in a markdown file.
|
||||
*/
|
||||
export interface MdHeaderReference {
|
||||
readonly kind: 'header';
|
||||
|
||||
readonly isTriggerLocation: boolean;
|
||||
readonly isDefinition: boolean;
|
||||
|
||||
/**
|
||||
* The range of the header.
|
||||
*
|
||||
* In `# a b c #` this would be the range of `# a b c #`
|
||||
*/
|
||||
readonly location: vscode.Location;
|
||||
|
||||
/**
|
||||
* The text of the header.
|
||||
*
|
||||
* In `# a b c #` this would be `a b c`
|
||||
*/
|
||||
readonly headerText: string;
|
||||
|
||||
/**
|
||||
* The range of the header text itself.
|
||||
*
|
||||
* In `# a b c #` this would be the range of `a b c`
|
||||
*/
|
||||
readonly headerTextLocation: vscode.Location;
|
||||
}
|
||||
|
||||
export type MdReference = MdLinkReference | MdHeaderReference;
|
||||
|
||||
/**
|
||||
* Stateful object that computes references for markdown files.
|
||||
*/
|
||||
export class MdReferencesProvider extends Disposable {
|
||||
|
||||
private readonly _linkCache: MdWorkspaceInfoCache<readonly MdLink[]>;
|
||||
|
||||
public constructor(
|
||||
private readonly parser: IMdParser,
|
||||
private readonly workspace: IMdWorkspace,
|
||||
private readonly tocProvider: MdTableOfContentsProvider,
|
||||
private readonly logger: ILogger,
|
||||
) {
|
||||
super();
|
||||
|
||||
const linkComputer = new MdLinkComputer(parser);
|
||||
this._linkCache = this._register(new MdWorkspaceInfoCache(workspace, doc => linkComputer.getAllLinks(doc, noopToken)));
|
||||
}
|
||||
|
||||
public async getReferencesAtPosition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
this.logger.verbose('ReferencesProvider', `getReferencesAtPosition: ${document.uri}`);
|
||||
|
||||
const toc = await this.tocProvider.getForDocument(document);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const header = toc.entries.find(entry => entry.line === position.line);
|
||||
if (header) {
|
||||
return this.getReferencesToHeader(document, header);
|
||||
} else {
|
||||
return this.getReferencesToLinkAtPosition(document, position, token);
|
||||
}
|
||||
}
|
||||
|
||||
public async getReferencesToFileInWorkspace(resource: vscode.Uri, token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
this.logger.verbose('ReferencesProvider', `getAllReferencesToFileInWorkspace: ${resource}`);
|
||||
|
||||
const allLinksInWorkspace = (await this._linkCache.values()).flat();
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(this.findLinksToFile(resource, allLinksInWorkspace, undefined));
|
||||
}
|
||||
|
||||
public async getReferencesToFileInDocs(resource: vscode.Uri, otherDocs: readonly ITextDocument[], token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
this.logger.verbose('ReferencesProvider', `getAllReferencesToFileInFiles: ${resource}`);
|
||||
|
||||
const links = (await this._linkCache.getForDocs(otherDocs)).flat();
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(this.findLinksToFile(resource, links, undefined));
|
||||
}
|
||||
|
||||
private async getReferencesToHeader(document: ITextDocument, header: TocEntry): Promise<MdReference[]> {
|
||||
const links = (await this._linkCache.values()).flat();
|
||||
|
||||
const references: MdReference[] = [];
|
||||
|
||||
references.push({
|
||||
kind: 'header',
|
||||
isTriggerLocation: true,
|
||||
isDefinition: true,
|
||||
location: header.headerLocation,
|
||||
headerText: header.text,
|
||||
headerTextLocation: header.headerTextLocation
|
||||
});
|
||||
|
||||
for (const link of links) {
|
||||
if (link.href.kind === 'internal'
|
||||
&& this.looksLikeLinkToDoc(link.href, document.uri)
|
||||
&& this.parser.slugifier.fromHeading(link.href.fragment).value === header.slug.value
|
||||
) {
|
||||
references.push({
|
||||
kind: 'link',
|
||||
isTriggerLocation: false,
|
||||
isDefinition: false,
|
||||
link,
|
||||
location: new vscode.Location(link.source.resource, link.source.hrefRange),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return references;
|
||||
}
|
||||
|
||||
private async getReferencesToLinkAtPosition(document: ITextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
const docLinks = (await this._linkCache.getForDocs([document]))[0];
|
||||
|
||||
for (const link of docLinks) {
|
||||
if (link.kind === 'definition') {
|
||||
// We could be in either the ref name or the definition
|
||||
if (link.ref.range.contains(position)) {
|
||||
return Array.from(this.getReferencesToLinkReference(docLinks, link.ref.text, { resource: document.uri, range: link.ref.range }));
|
||||
} else if (link.source.hrefRange.contains(position)) {
|
||||
return this.getReferencesToLink(link, position, token);
|
||||
}
|
||||
} else {
|
||||
if (link.source.hrefRange.contains(position)) {
|
||||
return this.getReferencesToLink(link, position, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private async getReferencesToLink(sourceLink: MdLink, triggerPosition: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
|
||||
const allLinksInWorkspace = (await this._linkCache.values()).flat();
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (sourceLink.href.kind === 'reference') {
|
||||
return Array.from(this.getReferencesToLinkReference(allLinksInWorkspace, sourceLink.href.ref, { resource: sourceLink.source.resource, range: sourceLink.source.hrefRange }));
|
||||
}
|
||||
|
||||
if (sourceLink.href.kind === 'external') {
|
||||
const references: MdReference[] = [];
|
||||
|
||||
for (const link of allLinksInWorkspace) {
|
||||
if (link.href.kind === 'external' && link.href.uri.toString() === sourceLink.href.uri.toString()) {
|
||||
const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
||||
references.push({
|
||||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: false,
|
||||
link,
|
||||
location: new vscode.Location(link.source.resource, link.source.hrefRange),
|
||||
});
|
||||
}
|
||||
}
|
||||
return references;
|
||||
}
|
||||
|
||||
const resolvedResource = await tryResolveLinkPath(sourceLink.href.path, this.workspace);
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const references: MdReference[] = [];
|
||||
|
||||
if (resolvedResource && this.isMarkdownPath(resolvedResource) && sourceLink.href.fragment && sourceLink.source.fragmentRange?.contains(triggerPosition)) {
|
||||
const toc = await this.tocProvider.get(resolvedResource);
|
||||
const entry = toc.lookup(sourceLink.href.fragment);
|
||||
if (entry) {
|
||||
references.push({
|
||||
kind: 'header',
|
||||
isTriggerLocation: false,
|
||||
isDefinition: true,
|
||||
location: entry.headerLocation,
|
||||
headerText: entry.text,
|
||||
headerTextLocation: entry.headerTextLocation
|
||||
});
|
||||
}
|
||||
|
||||
for (const link of allLinksInWorkspace) {
|
||||
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resolvedResource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.parser.slugifier.fromHeading(link.href.fragment).equals(this.parser.slugifier.fromHeading(sourceLink.href.fragment))) {
|
||||
const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
||||
references.push({
|
||||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: false,
|
||||
link,
|
||||
location: new vscode.Location(link.source.resource, link.source.hrefRange),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments
|
||||
references.push(...this.findLinksToFile(resolvedResource ?? sourceLink.href.path, allLinksInWorkspace, sourceLink));
|
||||
}
|
||||
|
||||
return references;
|
||||
}
|
||||
|
||||
private isMarkdownPath(resolvedHrefPath: vscode.Uri) {
|
||||
return this.workspace.hasMarkdownDocument(resolvedHrefPath) || looksLikeMarkdownPath(resolvedHrefPath);
|
||||
}
|
||||
|
||||
private looksLikeLinkToDoc(href: InternalHref, targetDoc: vscode.Uri) {
|
||||
return href.path.fsPath === targetDoc.fsPath
|
||||
|| uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath;
|
||||
}
|
||||
|
||||
private *findLinksToFile(resource: vscode.Uri, links: readonly MdLink[], sourceLink: MdLink | undefined): Iterable<MdReference> {
|
||||
for (const link of links) {
|
||||
if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resource)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exclude cases where the file is implicitly referencing itself
|
||||
if (link.source.hrefText.startsWith('#') && link.source.resource.fsPath === resource.fsPath) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isTriggerLocation = !!sourceLink && sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange);
|
||||
const pathRange = this.getPathRange(link);
|
||||
yield {
|
||||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: false,
|
||||
link,
|
||||
location: new vscode.Location(link.source.resource, pathRange),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private *getReferencesToLinkReference(allLinks: Iterable<MdLink>, refToFind: string, from: { resource: vscode.Uri; range: vscode.Range }): Iterable<MdReference> {
|
||||
for (const link of allLinks) {
|
||||
let ref: string;
|
||||
if (link.kind === 'definition') {
|
||||
ref = link.ref.text;
|
||||
} else if (link.href.kind === 'reference') {
|
||||
ref = link.href.ref;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ref === refToFind && link.source.resource.fsPath === from.resource.fsPath) {
|
||||
const isTriggerLocation = from.resource.fsPath === link.source.resource.fsPath && (
|
||||
(link.href.kind === 'reference' && from.range.isEqual(link.source.hrefRange)) || (link.kind === 'definition' && from.range.isEqual(link.ref.range)));
|
||||
|
||||
const pathRange = this.getPathRange(link);
|
||||
yield {
|
||||
kind: 'link',
|
||||
isTriggerLocation,
|
||||
isDefinition: link.kind === 'definition',
|
||||
link,
|
||||
location: new vscode.Location(from.resource, pathRange),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get just the range of the file path, dropping the fragment
|
||||
*/
|
||||
private getPathRange(link: MdLink): vscode.Range {
|
||||
return link.source.fragmentRange
|
||||
? link.source.hrefRange.with(undefined, link.source.fragmentRange.start.translate(0, -1))
|
||||
: link.source.hrefRange;
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryResolveLinkPath(originalUri: vscode.Uri, workspace: IMdWorkspace): Promise<vscode.Uri | undefined> {
|
||||
if (await workspace.pathExists(originalUri)) {
|
||||
return originalUri;
|
||||
}
|
||||
|
||||
// We don't think the file exists. If it doesn't already have an extension, try tacking on a `.md` and using that instead
|
||||
if (uri.Utils.extname(originalUri) === '') {
|
||||
const dotMdResource = originalUri.with({ path: originalUri.path + '.md' });
|
||||
if (await workspace.pathExists(dotMdResource)) {
|
||||
return dotMdResource;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
|
@ -70,7 +70,7 @@ export class VsCodeOutputLogger extends Disposable implements ILogger {
|
|||
}
|
||||
|
||||
private readTrace(): Trace {
|
||||
return Trace.fromString(vscode.workspace.getConfiguration().get<string>('markdown.trace', 'off'));
|
||||
return Trace.fromString(vscode.workspace.getConfiguration().get<string>('markdown.trace.extension', 'off'));
|
||||
}
|
||||
|
||||
private static data2String(data: any): string {
|
||||
|
|
|
@ -6,13 +6,23 @@
|
|||
import Token = require('markdown-it/lib/token');
|
||||
import { RequestType } from 'vscode-languageclient';
|
||||
import type * as lsp from 'vscode-languageserver-types';
|
||||
import type * as md from 'vscode-markdown-languageservice';
|
||||
|
||||
// From server
|
||||
export const parseRequestType: RequestType<{ uri: string }, Token[], any> = new RequestType('markdown/parse');
|
||||
export const readFileRequestType: RequestType<{ uri: string }, number[], any> = new RequestType('markdown/readFile');
|
||||
export const statFileRequestType: RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any> = new RequestType('markdown/statFile');
|
||||
export const readDirectoryRequestType: RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any> = new RequestType('markdown/readDirectory');
|
||||
export const findFilesRequestTypes = new RequestType<{}, string[], any>('markdown/findFiles');
|
||||
//#region From server
|
||||
export const parse = new RequestType<{ uri: string }, Token[], any>('markdown/parse');
|
||||
|
||||
// To server
|
||||
export const fs_readFile = new RequestType<{ uri: string }, number[], any>('markdown/fs/readFile');
|
||||
export const fs_readDirectory = new RequestType<{ uri: string }, [string, { isDirectory: boolean }][], any>('markdown/fs/readDirectory');
|
||||
export const fs_stat = new RequestType<{ uri: string }, { isDirectory: boolean } | undefined, any>('markdown/fs/stat');
|
||||
|
||||
export const fs_watcher_create = new RequestType<{ id: number; uri: string; options: md.FileWatcherOptions }, void, any>('markdown/fs/watcher/create');
|
||||
export const fs_watcher_delete = new RequestType<{ id: number }, void, any>('markdown/fs/watcher/delete');
|
||||
|
||||
export const findMarkdownFilesInWorkspace = new RequestType<{}, string[], any>('markdown/findMarkdownFilesInWorkspace');
|
||||
//#endregion
|
||||
|
||||
//#region To server
|
||||
export const getReferencesToFileInWorkspace = new RequestType<{ uri: string }, lsp.Location[], any>('markdown/getReferencesToFileInWorkspace');
|
||||
|
||||
export const fs_watcher_onChange = new RequestType<{ id: number; uri: string; kind: 'create' | 'change' | 'delete' }, void, any>('markdown/fs/watcher/onChange');
|
||||
//#endregion
|
||||
|
|
|
@ -1,591 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import * as vscode from 'vscode';
|
||||
import { DiagnosticCollectionReporter, DiagnosticComputer, DiagnosticConfiguration, DiagnosticLevel, DiagnosticManager, DiagnosticOptions, DiagnosticReporter } from '../languageFeatures/diagnostics';
|
||||
import { MdLinkProvider } from '../languageFeatures/documentLinks';
|
||||
import { MdReferencesProvider } from '../languageFeatures/references';
|
||||
import { MdTableOfContentsProvider } from '../tableOfContents';
|
||||
import { ITextDocument } from '../types/textDocument';
|
||||
import { noopToken } from '../util/cancellation';
|
||||
import { DisposableStore } from '../util/dispose';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { ResourceMap } from '../util/resourceMap';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
|
||||
import { nulLogger } from './nulLogging';
|
||||
import { assertRangeEqual, joinLines, withStore, workspacePath } from './util';
|
||||
|
||||
const defaultDiagnosticsOptions = Object.freeze<DiagnosticOptions>({
|
||||
enabled: true,
|
||||
validateFileLinks: DiagnosticLevel.warning,
|
||||
validateMarkdownFileLinkFragments: undefined,
|
||||
validateFragmentLinks: DiagnosticLevel.warning,
|
||||
validateReferences: DiagnosticLevel.warning,
|
||||
ignoreLinks: [],
|
||||
});
|
||||
|
||||
async function getComputedDiagnostics(store: DisposableStore, doc: InMemoryDocument, workspace: IMdWorkspace, options: Partial<DiagnosticOptions> = {}): Promise<vscode.Diagnostic[]> {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const linkProvider = store.add(new MdLinkProvider(engine, workspace, nulLogger));
|
||||
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
|
||||
const computer = new DiagnosticComputer(workspace, linkProvider, tocProvider);
|
||||
return (
|
||||
await computer.getDiagnostics(doc, { ...defaultDiagnosticsOptions, ...options, }, noopToken)
|
||||
).diagnostics;
|
||||
}
|
||||
|
||||
function assertDiagnosticsEqual(actual: readonly vscode.Diagnostic[], expectedRanges: readonly vscode.Range[]) {
|
||||
assert.strictEqual(actual.length, expectedRanges.length, "Diagnostic count equal");
|
||||
|
||||
for (let i = 0; i < actual.length; ++i) {
|
||||
assertRangeEqual(actual[i].range, expectedRanges[i], `Range ${i} to be equal`);
|
||||
}
|
||||
}
|
||||
|
||||
function orderDiagnosticsByRange(diagnostics: Iterable<vscode.Diagnostic>): readonly vscode.Diagnostic[] {
|
||||
return Array.from(diagnostics).sort((a, b) => a.range.start.compareTo(b.range.start));
|
||||
}
|
||||
|
||||
class MemoryDiagnosticConfiguration implements DiagnosticConfiguration {
|
||||
|
||||
private readonly _onDidChange = new vscode.EventEmitter<void>();
|
||||
public readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
private _options: Partial<DiagnosticOptions>;
|
||||
|
||||
constructor(options: Partial<DiagnosticOptions>) {
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
public getOptions(_resource: vscode.Uri): DiagnosticOptions {
|
||||
return {
|
||||
...defaultDiagnosticsOptions,
|
||||
...this._options,
|
||||
};
|
||||
}
|
||||
|
||||
public update(newOptions: Partial<DiagnosticOptions>) {
|
||||
this._options = newOptions;
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryDiagnosticReporter extends DiagnosticReporter {
|
||||
|
||||
private readonly diagnostics = new ResourceMap<readonly vscode.Diagnostic[]>();
|
||||
|
||||
constructor(
|
||||
private readonly workspace: InMemoryMdWorkspace,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.clear();
|
||||
this.clear();
|
||||
}
|
||||
|
||||
override clear(): void {
|
||||
super.clear();
|
||||
this.diagnostics.clear();
|
||||
}
|
||||
|
||||
set(uri: vscode.Uri, diagnostics: readonly vscode.Diagnostic[]): void {
|
||||
this.diagnostics.set(uri, diagnostics);
|
||||
}
|
||||
|
||||
isOpen(_uri: vscode.Uri): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri): void {
|
||||
this.diagnostics.delete(uri);
|
||||
}
|
||||
|
||||
get(uri: vscode.Uri): readonly vscode.Diagnostic[] {
|
||||
return orderDiagnosticsByRange(this.diagnostics.get(uri) ?? []);
|
||||
}
|
||||
|
||||
getOpenDocuments(): ITextDocument[] {
|
||||
return this.workspace.values();
|
||||
}
|
||||
}
|
||||
|
||||
suite('markdown: Diagnostic Computer', () => {
|
||||
|
||||
test('Should not return any diagnostics for empty document', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`text`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
|
||||
assert.deepStrictEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Should generate diagnostic for link to file that does not exist', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`[bad](/no/such/file.md)`,
|
||||
`[good](/doc.md)`,
|
||||
`[good-ref]: /doc.md`,
|
||||
`[bad-ref]: /no/such/file.md`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
|
||||
assertDiagnosticsEqual(diagnostics, [
|
||||
new vscode.Range(0, 6, 0, 22),
|
||||
new vscode.Range(3, 11, 3, 27),
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Should generate diagnostics for links to header that does not exist in current file', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`[good](#good-header)`,
|
||||
`# Good Header`,
|
||||
`[bad](#no-such-header)`,
|
||||
`[good](#good-header)`,
|
||||
`[good-ref]: #good-header`,
|
||||
`[bad-ref]: #no-such-header`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
|
||||
assertDiagnosticsEqual(diagnostics, [
|
||||
new vscode.Range(2, 6, 2, 21),
|
||||
new vscode.Range(5, 11, 5, 26),
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Should generate diagnostics for links to non-existent headers in other files', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`# My header`,
|
||||
`[good](#my-header)`,
|
||||
`[good](/doc1.md#my-header)`,
|
||||
`[good](doc1.md#my-header)`,
|
||||
`[good](/doc2.md#other-header)`,
|
||||
`[bad](/doc2.md#no-such-other-header)`,
|
||||
));
|
||||
|
||||
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(
|
||||
`# Other header`,
|
||||
));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1, doc2]));
|
||||
assertDiagnosticsEqual(diagnostics, [
|
||||
new vscode.Range(5, 14, 5, 35),
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Should support links both with and without .md file extension', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`# My header`,
|
||||
`[good](#my-header)`,
|
||||
`[good](/doc.md#my-header)`,
|
||||
`[good](doc.md#my-header)`,
|
||||
`[good](/doc#my-header)`,
|
||||
`[good](doc#my-header)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Should generate diagnostics for non-existent link reference', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc.md'), joinLines(
|
||||
`[good link][good]`,
|
||||
`[bad link][no-such]`,
|
||||
``,
|
||||
`[good]: http://example.com`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
|
||||
assertDiagnosticsEqual(diagnostics, [
|
||||
new vscode.Range(1, 11, 1, 18),
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Should not generate diagnostics when validate is disabled', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`[text](#no-such-header)`,
|
||||
`[text][no-such-ref]`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, new MemoryDiagnosticConfiguration({ enabled: false }).getOptions(doc1.uri));
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Should not generate diagnostics for email autolink', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`a <user@example.com> c`,
|
||||
));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1]));
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Should not generate diagnostics for html tag that looks like an autolink', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`a <tag>b</tag> c`,
|
||||
`a <scope:tag>b</scope:tag> c`,
|
||||
));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, new InMemoryMdWorkspace([doc1]));
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Should allow ignoring invalid file link using glob', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`[text](/no-such-file)`,
|
||||
`![img](/no-such-file)`,
|
||||
`[text]: /no-such-file`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Should be able to disable fragment validation for external files', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`![i](/doc2.md#no-such)`,
|
||||
));
|
||||
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));
|
||||
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateMarkdownFileLinkFragments: DiagnosticLevel.ignore });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Disabling own fragment validation should also disable path fragment validation by default', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`[b](#no-head)`,
|
||||
`![i](/doc2.md#no-such)`,
|
||||
));
|
||||
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));
|
||||
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
|
||||
|
||||
{
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}
|
||||
{
|
||||
// But we should be able to override the default
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { validateFragmentLinks: DiagnosticLevel.ignore, validateMarkdownFileLinkFragments: DiagnosticLevel.warning });
|
||||
assertDiagnosticsEqual(diagnostics, [
|
||||
new vscode.Range(1, 13, 1, 21),
|
||||
]);
|
||||
}
|
||||
}));
|
||||
|
||||
test('ignoreLinks should allow skipping link to non-existent file', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`[text](/no-such-file#header)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('ignoreLinks should not consider link fragment', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`[text](/no-such-file#header)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/no-such-file'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('ignoreLinks should support globs', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`![i](/images/aaa.png)`,
|
||||
`![i](/images/sub/bbb.png)`,
|
||||
`![i](/images/sub/sub2/ccc.png)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/images/**/*.png'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('ignoreLinks should support ignoring header', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`![i](#no-such)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['#no-such'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('ignoreLinks should support ignoring header in file', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`![i](/doc2.md#no-such)`,
|
||||
));
|
||||
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
|
||||
|
||||
{
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md#no-such'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}
|
||||
{
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md#*'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}
|
||||
}));
|
||||
|
||||
test('ignoreLinks should support ignore header links if file is ignored', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`![i](/doc2.md#no-such)`,
|
||||
));
|
||||
const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines(''));
|
||||
const workspace = new InMemoryMdWorkspace([doc1, doc2]);
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Should not detect checkboxes as invalid links', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`- [x]`,
|
||||
`- [X]`,
|
||||
`- [ ]`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, { ignoreLinks: ['/doc2.md'] });
|
||||
assertDiagnosticsEqual(diagnostics, []);
|
||||
}));
|
||||
|
||||
test('Should detect invalid links with titles', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('doc1.md'), joinLines(
|
||||
`[link](<no such.md> "text")`,
|
||||
`[link](<no such.md> 'text')`,
|
||||
`[link](<no such.md> (text))`,
|
||||
`[link](no-such.md "text")`,
|
||||
`[link](no-such.md 'text')`,
|
||||
`[link](no-such.md (text))`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
|
||||
assertDiagnosticsEqual(diagnostics, [
|
||||
new vscode.Range(0, 8, 0, 18),
|
||||
new vscode.Range(1, 8, 1, 18),
|
||||
new vscode.Range(2, 8, 2, 18),
|
||||
new vscode.Range(3, 7, 3, 17),
|
||||
new vscode.Range(4, 7, 4, 17),
|
||||
new vscode.Range(5, 7, 5, 17),
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Should generate diagnostics for non-existent header using file link to own file', withStore(async (store) => {
|
||||
const doc = new InMemoryDocument(workspacePath('sub', 'doc.md'), joinLines(
|
||||
`[bad](doc.md#no-such)`,
|
||||
`[bad](doc#no-such)`,
|
||||
`[bad](/sub/doc.md#no-such)`,
|
||||
`[bad](/sub/doc#no-such)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc, workspace);
|
||||
assertDiagnosticsEqual(orderDiagnosticsByRange(diagnostics), [
|
||||
new vscode.Range(0, 12, 0, 20),
|
||||
new vscode.Range(1, 9, 1, 17),
|
||||
new vscode.Range(2, 17, 2, 25),
|
||||
new vscode.Range(3, 14, 3, 22),
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Own header link using file path link should be controlled by "validateMarkdownFileLinkFragments" instead of "validateFragmentLinks"', withStore(async (store) => {
|
||||
const doc1 = new InMemoryDocument(workspacePath('sub', 'doc.md'), joinLines(
|
||||
`[bad](doc.md#no-such)`,
|
||||
`[bad](doc#no-such)`,
|
||||
`[bad](/sub/doc.md#no-such)`,
|
||||
`[bad](/sub/doc#no-such)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1]));
|
||||
|
||||
const diagnostics = await getComputedDiagnostics(store, doc1, workspace, {
|
||||
validateFragmentLinks: DiagnosticLevel.ignore,
|
||||
validateMarkdownFileLinkFragments: DiagnosticLevel.warning,
|
||||
});
|
||||
assertDiagnosticsEqual(orderDiagnosticsByRange(diagnostics), [
|
||||
new vscode.Range(0, 12, 0, 20),
|
||||
new vscode.Range(1, 9, 1, 17),
|
||||
new vscode.Range(2, 17, 2, 25),
|
||||
new vscode.Range(3, 14, 3, 22),
|
||||
]);
|
||||
}));
|
||||
});
|
||||
|
||||
suite('Markdown: Diagnostics manager', () => {
|
||||
|
||||
function createDiagnosticsManager(
|
||||
store: DisposableStore,
|
||||
workspace: IMdWorkspace,
|
||||
configuration = new MemoryDiagnosticConfiguration({}),
|
||||
reporter: DiagnosticReporter = new DiagnosticCollectionReporter(),
|
||||
) {
|
||||
const engine = createNewMarkdownEngine();
|
||||
const linkProvider = store.add(new MdLinkProvider(engine, workspace, nulLogger));
|
||||
const tocProvider = store.add(new MdTableOfContentsProvider(engine, workspace, nulLogger));
|
||||
const referencesProvider = store.add(new MdReferencesProvider(engine, workspace, tocProvider, nulLogger));
|
||||
const manager = store.add(new DiagnosticManager(
|
||||
workspace,
|
||||
new DiagnosticComputer(workspace, linkProvider, tocProvider),
|
||||
configuration,
|
||||
reporter,
|
||||
referencesProvider,
|
||||
tocProvider,
|
||||
nulLogger,
|
||||
0));
|
||||
return manager;
|
||||
}
|
||||
|
||||
test('Changing enable/disable should recompute diagnostics', withStore(async (store) => {
|
||||
const doc1Uri = workspacePath('doc1.md');
|
||||
const doc2Uri = workspacePath('doc2.md');
|
||||
const workspace = store.add(new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(doc1Uri, joinLines(
|
||||
`[text](#no-such-1)`,
|
||||
)),
|
||||
new InMemoryDocument(doc2Uri, joinLines(
|
||||
`[text](#no-such-2)`,
|
||||
))
|
||||
]));
|
||||
|
||||
const reporter = store.add(new MemoryDiagnosticReporter(workspace));
|
||||
const config = new MemoryDiagnosticConfiguration({ enabled: true });
|
||||
|
||||
const manager = createDiagnosticsManager(store, workspace, config, reporter);
|
||||
await manager.ready;
|
||||
|
||||
// Check initial state (Enabled)
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), [
|
||||
new vscode.Range(0, 7, 0, 17),
|
||||
]);
|
||||
assertDiagnosticsEqual(reporter.get(doc2Uri), [
|
||||
new vscode.Range(0, 7, 0, 17),
|
||||
]);
|
||||
|
||||
// Disable
|
||||
config.update({ enabled: false });
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), []);
|
||||
assertDiagnosticsEqual(reporter.get(doc2Uri), []);
|
||||
|
||||
// Enable
|
||||
config.update({ enabled: true });
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), [
|
||||
new vscode.Range(0, 7, 0, 17),
|
||||
]);
|
||||
assertDiagnosticsEqual(reporter.get(doc2Uri), [
|
||||
new vscode.Range(0, 7, 0, 17),
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Should revalidate linked files when header changes', withStore(async (store) => {
|
||||
const doc1Uri = workspacePath('doc1.md');
|
||||
const doc1 = new InMemoryDocument(doc1Uri, joinLines(
|
||||
`[text](#no-such)`,
|
||||
`[text](/doc2.md#header)`,
|
||||
));
|
||||
const doc2Uri = workspacePath('doc2.md');
|
||||
const doc2 = new InMemoryDocument(doc2Uri, joinLines(
|
||||
`# Header`,
|
||||
`[text](#header)`,
|
||||
`[text](#no-such-2)`,
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
|
||||
const reporter = store.add(new MemoryDiagnosticReporter(workspace));
|
||||
|
||||
const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter);
|
||||
await manager.ready;
|
||||
|
||||
// Check initial state
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), [
|
||||
new vscode.Range(0, 7, 0, 15),
|
||||
]);
|
||||
assertDiagnosticsEqual(reporter.get(doc2Uri), [
|
||||
new vscode.Range(2, 7, 2, 17),
|
||||
]);
|
||||
|
||||
// Edit header
|
||||
workspace.updateDocument(new InMemoryDocument(doc2Uri, joinLines(
|
||||
`# new header`,
|
||||
`[text](#new-header)`,
|
||||
`[text](#no-such-2)`,
|
||||
)));
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), [
|
||||
new vscode.Range(0, 7, 0, 15),
|
||||
new vscode.Range(1, 15, 1, 22),
|
||||
]);
|
||||
assertDiagnosticsEqual(reporter.get(doc2Uri), [
|
||||
new vscode.Range(2, 7, 2, 17),
|
||||
]);
|
||||
|
||||
// Revert to original file
|
||||
workspace.updateDocument(new InMemoryDocument(doc2Uri, joinLines(
|
||||
`# header`,
|
||||
`[text](#header)`,
|
||||
`[text](#no-such-2)`,
|
||||
)));
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), [
|
||||
new vscode.Range(0, 7, 0, 15)
|
||||
]);
|
||||
assertDiagnosticsEqual(reporter.get(doc2Uri), [
|
||||
new vscode.Range(2, 7, 2, 17),
|
||||
]);
|
||||
}));
|
||||
|
||||
test('Should revalidate linked files when file is deleted/created', withStore(async (store) => {
|
||||
const doc1Uri = workspacePath('doc1.md');
|
||||
const doc1 = new InMemoryDocument(doc1Uri, joinLines(
|
||||
`[text](/doc2.md)`,
|
||||
`[text](/doc2.md#header)`,
|
||||
));
|
||||
const doc2Uri = workspacePath('doc2.md');
|
||||
const doc2 = new InMemoryDocument(doc2Uri, joinLines(
|
||||
`# Header`
|
||||
));
|
||||
const workspace = store.add(new InMemoryMdWorkspace([doc1, doc2]));
|
||||
const reporter = store.add(new MemoryDiagnosticReporter(workspace));
|
||||
|
||||
const manager = createDiagnosticsManager(store, workspace, new MemoryDiagnosticConfiguration({}), reporter);
|
||||
await manager.ready;
|
||||
|
||||
// Check initial state
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), []);
|
||||
|
||||
// Edit header
|
||||
workspace.deleteDocument(doc2Uri);
|
||||
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), [
|
||||
new vscode.Range(0, 7, 0, 15),
|
||||
new vscode.Range(1, 7, 1, 22),
|
||||
]);
|
||||
|
||||
// Revert to original file
|
||||
workspace.createDocument(doc2);
|
||||
await reporter.waitPendingWork();
|
||||
assertDiagnosticsEqual(reporter.get(doc1Uri), []);
|
||||
}));
|
||||
});
|
|
@ -1,33 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { MdDocumentInfoCache } from '../util/workspaceCache';
|
||||
import { InMemoryMdWorkspace } from './inMemoryWorkspace';
|
||||
import { workspacePath } from './util';
|
||||
|
||||
suite('DocumentInfoCache', () => {
|
||||
test('Repeated calls should only compute value once', async () => {
|
||||
const doc = workspacePath('doc.md');
|
||||
const workspace = new InMemoryMdWorkspace([
|
||||
new InMemoryDocument(doc, '')
|
||||
]);
|
||||
|
||||
let i = 0;
|
||||
const cache = new MdDocumentInfoCache<number>(workspace, async () => {
|
||||
return ++i;
|
||||
});
|
||||
|
||||
const a = cache.get(doc);
|
||||
const b = cache.get(doc);
|
||||
|
||||
assert.strictEqual(await a, 1);
|
||||
assert.strictEqual(i, 1);
|
||||
assert.strictEqual(await b, 1);
|
||||
assert.strictEqual(i, 1);
|
||||
});
|
||||
});
|
|
@ -1,136 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import * as vscode from 'vscode';
|
||||
import { TableOfContents } from '../tableOfContents';
|
||||
import { ITextDocument } from '../types/textDocument';
|
||||
import { InMemoryDocument } from '../util/inMemoryDocument';
|
||||
import { createNewMarkdownEngine } from './engine';
|
||||
|
||||
|
||||
const testFileName = vscode.Uri.file('test.md');
|
||||
|
||||
function createToc(doc: ITextDocument): Promise<TableOfContents> {
|
||||
const engine = createNewMarkdownEngine();
|
||||
return TableOfContents.create(engine, doc);
|
||||
}
|
||||
|
||||
suite('markdown.TableOfContentsProvider', () => {
|
||||
test('Lookup should not return anything for empty document', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, '');
|
||||
const provider = await createToc(doc);
|
||||
|
||||
assert.strictEqual(provider.lookup(''), undefined);
|
||||
assert.strictEqual(provider.lookup('foo'), undefined);
|
||||
});
|
||||
|
||||
test('Lookup should not return anything for document with no headers', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, 'a *b*\nc');
|
||||
const provider = await createToc(doc);
|
||||
|
||||
assert.strictEqual(provider.lookup(''), undefined);
|
||||
assert.strictEqual(provider.lookup('foo'), undefined);
|
||||
assert.strictEqual(provider.lookup('a'), undefined);
|
||||
assert.strictEqual(provider.lookup('b'), undefined);
|
||||
});
|
||||
|
||||
test('Lookup should return basic #header', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, `# a\nx\n# c`);
|
||||
const provider = await createToc(doc);
|
||||
|
||||
{
|
||||
const entry = provider.lookup('a');
|
||||
assert.ok(entry);
|
||||
assert.strictEqual(entry!.line, 0);
|
||||
}
|
||||
{
|
||||
assert.strictEqual(provider.lookup('x'), undefined);
|
||||
}
|
||||
{
|
||||
const entry = provider.lookup('c');
|
||||
assert.ok(entry);
|
||||
assert.strictEqual(entry!.line, 2);
|
||||
}
|
||||
});
|
||||
|
||||
test('Lookups should be case in-sensitive', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, `# fOo\n`);
|
||||
const provider = await createToc(doc);
|
||||
|
||||
assert.strictEqual((provider.lookup('fOo'))!.line, 0);
|
||||
assert.strictEqual((provider.lookup('foo'))!.line, 0);
|
||||
assert.strictEqual((provider.lookup('FOO'))!.line, 0);
|
||||
});
|
||||
|
||||
test('Lookups should ignore leading and trailing white-space, and collapse internal whitespace', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, `# f o o \n`);
|
||||
const provider = await createToc(doc);
|
||||
|
||||
assert.strictEqual((provider.lookup('f o o'))!.line, 0);
|
||||
assert.strictEqual((provider.lookup(' f o o'))!.line, 0);
|
||||
assert.strictEqual((provider.lookup(' f o o '))!.line, 0);
|
||||
assert.strictEqual((provider.lookup('f o o'))!.line, 0);
|
||||
assert.strictEqual((provider.lookup('f o o'))!.line, 0);
|
||||
|
||||
assert.strictEqual(provider.lookup('f'), undefined);
|
||||
assert.strictEqual(provider.lookup('foo'), undefined);
|
||||
assert.strictEqual(provider.lookup('fo o'), undefined);
|
||||
});
|
||||
|
||||
test('should handle special characters #44779', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, `# Indentação\n`);
|
||||
const provider = await createToc(doc);
|
||||
|
||||
assert.strictEqual((provider.lookup('indentação'))!.line, 0);
|
||||
});
|
||||
|
||||
test('should handle special characters 2, #48482', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, `# Инструкция - Делай Раз, Делай Два\n`);
|
||||
const provider = await createToc(doc);
|
||||
|
||||
assert.strictEqual((provider.lookup('инструкция---делай-раз-делай-два'))!.line, 0);
|
||||
});
|
||||
|
||||
test('should handle special characters 3, #37079', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, `## Header 2
|
||||
### Header 3
|
||||
## Заголовок 2
|
||||
### Заголовок 3
|
||||
### Заголовок Header 3
|
||||
## Заголовок`);
|
||||
|
||||
const provider = await createToc(doc);
|
||||
|
||||
assert.strictEqual((provider.lookup('header-2'))!.line, 0);
|
||||
assert.strictEqual((provider.lookup('header-3'))!.line, 1);
|
||||
assert.strictEqual((provider.lookup('Заголовок-2'))!.line, 2);
|
||||
assert.strictEqual((provider.lookup('Заголовок-3'))!.line, 3);
|
||||
assert.strictEqual((provider.lookup('Заголовок-header-3'))!.line, 4);
|
||||
assert.strictEqual((provider.lookup('Заголовок'))!.line, 5);
|
||||
});
|
||||
|
||||
test('Lookup should support suffixes for repeated headers', async () => {
|
||||
const doc = new InMemoryDocument(testFileName, `# a\n# a\n## a`);
|
||||
const provider = await createToc(doc);
|
||||
|
||||
{
|
||||
const entry = provider.lookup('a');
|
||||
assert.ok(entry);
|
||||
assert.strictEqual(entry!.line, 0);
|
||||
}
|
||||
{
|
||||
const entry = provider.lookup('a-1');
|
||||
assert.ok(entry);
|
||||
assert.strictEqual(entry!.line, 1);
|
||||
}
|
||||
{
|
||||
const entry = provider.lookup('a-2');
|
||||
assert.ok(entry);
|
||||
assert.strictEqual(entry!.line, 2);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,89 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { MdTableOfContentsProvider, TableOfContents } from '../tableOfContents';
|
||||
import { ITextDocument } from '../types/textDocument';
|
||||
import { IMdWorkspace } from '../workspace';
|
||||
import { equals } from './arrays';
|
||||
import { Delayer } from './async';
|
||||
import { Disposable } from './dispose';
|
||||
import { ResourceMap } from './resourceMap';
|
||||
|
||||
/**
|
||||
* Check if the items in a table of contents have changed.
|
||||
*
|
||||
* This only checks for changes in the entries themselves, not for any changes in their locations.
|
||||
*/
|
||||
function hasTableOfContentsChanged(a: TableOfContents, b: TableOfContents): boolean {
|
||||
const aSlugs = a.entries.map(entry => entry.slug.value).sort();
|
||||
const bSlugs = b.entries.map(entry => entry.slug.value).sort();
|
||||
return !equals(aSlugs, bSlugs);
|
||||
}
|
||||
|
||||
export class MdTableOfContentsWatcher extends Disposable {
|
||||
|
||||
private readonly _files = new ResourceMap<{
|
||||
readonly toc: TableOfContents;
|
||||
}>();
|
||||
|
||||
private readonly _pending = new ResourceMap<void>();
|
||||
|
||||
private readonly _onTocChanged = this._register(new vscode.EventEmitter<{ readonly uri: vscode.Uri }>);
|
||||
public readonly onTocChanged = this._onTocChanged.event;
|
||||
|
||||
private readonly delayer: Delayer<void>;
|
||||
|
||||
public constructor(
|
||||
private readonly workspace: IMdWorkspace,
|
||||
private readonly tocProvider: MdTableOfContentsProvider,
|
||||
private readonly delay: number,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.delayer = this._register(new Delayer<void>(delay));
|
||||
|
||||
this._register(this.workspace.onDidChangeMarkdownDocument(this.onDidChangeDocument, this));
|
||||
this._register(this.workspace.onDidCreateMarkdownDocument(this.onDidCreateDocument, this));
|
||||
this._register(this.workspace.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this));
|
||||
}
|
||||
|
||||
private async onDidCreateDocument(document: ITextDocument) {
|
||||
const toc = await this.tocProvider.getForDocument(document);
|
||||
this._files.set(document.uri, { toc });
|
||||
}
|
||||
|
||||
private async onDidChangeDocument(document: ITextDocument) {
|
||||
if (this.delay > 0) {
|
||||
this._pending.set(document.uri);
|
||||
this.delayer.trigger(() => this.flushPending());
|
||||
} else {
|
||||
this.updateForResource(document.uri);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidDeleteDocument(resource: vscode.Uri) {
|
||||
this._files.delete(resource);
|
||||
this._pending.delete(resource);
|
||||
}
|
||||
|
||||
private async flushPending() {
|
||||
const pending = [...this._pending.keys()];
|
||||
this._pending.clear();
|
||||
|
||||
return Promise.all(pending.map(resource => this.updateForResource(resource)));
|
||||
}
|
||||
|
||||
private async updateForResource(resource: vscode.Uri) {
|
||||
const existing = this._files.get(resource);
|
||||
const newToc = await this.tocProvider.get(resource);
|
||||
|
||||
if (!existing || hasTableOfContentsChanged(existing.toc, newToc)) {
|
||||
this._onTocChanged.fire({ uri: resource });
|
||||
}
|
||||
|
||||
this._files.set(resource, { toc: newToc });
|
||||
}
|
||||
}
|
|
@ -114,74 +114,3 @@ export class MdDocumentInfoCache<T> extends Disposable {
|
|||
this._cache.delete(resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache of information across all markdown files in the workspace.
|
||||
*
|
||||
* Unlike {@link MdDocumentInfoCache}, the entries here are computed eagerly for every file in the workspace.
|
||||
* However the computation of the values is still lazy.
|
||||
*/
|
||||
export class MdWorkspaceInfoCache<T> extends Disposable {
|
||||
|
||||
private readonly _cache = new LazyResourceMap<T>();
|
||||
private _init?: Promise<void>;
|
||||
|
||||
public constructor(
|
||||
private readonly workspace: IMdWorkspace,
|
||||
private readonly getValue: (document: ITextDocument) => Promise<T>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public async entries(): Promise<Array<[vscode.Uri, T]>> {
|
||||
await this.ensureInit();
|
||||
return this._cache.entries();
|
||||
}
|
||||
|
||||
public async values(): Promise<Array<T>> {
|
||||
await this.ensureInit();
|
||||
return Array.from(await this._cache.entries(), x => x[1]);
|
||||
}
|
||||
|
||||
public async getForDocs(docs: readonly ITextDocument[]): Promise<T[]> {
|
||||
for (const doc of docs) {
|
||||
if (!this._cache.has(doc.uri)) {
|
||||
this.update(doc);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(docs.map(doc => this._cache.get(doc.uri) as Promise<T>));
|
||||
}
|
||||
|
||||
private async ensureInit(): Promise<void> {
|
||||
if (!this._init) {
|
||||
this._init = this.populateCache();
|
||||
|
||||
this._register(this.workspace.onDidChangeMarkdownDocument(this.onDidChangeDocument, this));
|
||||
this._register(this.workspace.onDidCreateMarkdownDocument(this.onDidChangeDocument, this));
|
||||
this._register(this.workspace.onDidDeleteMarkdownDocument(this.onDidDeleteDocument, this));
|
||||
}
|
||||
await this._init;
|
||||
}
|
||||
|
||||
private async populateCache(): Promise<void> {
|
||||
const markdownDocumentUris = await this.workspace.getAllMarkdownDocuments();
|
||||
for (const document of markdownDocumentUris) {
|
||||
if (!this._cache.has(document.uri)) {
|
||||
this.update(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private update(document: ITextDocument): void {
|
||||
this._cache.set(document.uri, lazy(() => this.getValue(document)));
|
||||
}
|
||||
|
||||
private onDidChangeDocument(document: ITextDocument) {
|
||||
this.update(document);
|
||||
}
|
||||
|
||||
private onDidDeleteDocument(resource: vscode.Uri) {
|
||||
this._cache.delete(resource);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,21 +237,42 @@ vscode-languageserver-textdocument@^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.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-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.2:
|
||||
vscode-languageserver-types@^3.17.1, vscode-languageserver-types@^3.17.2:
|
||||
version "3.17.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz#b2c2e7de405ad3d73a883e91989b850170ffc4f2"
|
||||
integrity sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==
|
||||
|
||||
vscode-markdown-languageservice@^0.0.0-alpha.10:
|
||||
version "0.0.0-alpha.10"
|
||||
resolved "https://registry.yarnpkg.com/vscode-markdown-languageservice/-/vscode-markdown-languageservice-0.0.0-alpha.10.tgz#53b69c981eed7fd5efa155ab8c0f169995568681"
|
||||
integrity sha512-rJ85nJ+d45yCz9lBhipavoWXz/vW5FknqqUpLqhe3/2xkrhxt8zcekhSoDepgkKFcTORAFV6g1SnnqxbVhX+uA==
|
||||
dependencies:
|
||||
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-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==
|
||||
|
||||
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"
|
||||
|
|
|
@ -554,8 +554,8 @@ export default class TypeScriptServiceClient extends Disposable implements IType
|
|||
}
|
||||
|
||||
// Reconfigure any plugins
|
||||
for (const [config, pluginName] of this.pluginManager.configurations()) {
|
||||
this.configurePlugin(config, pluginName);
|
||||
for (const [pluginName, config] of this.pluginManager.configurations()) {
|
||||
this.configurePlugin(pluginName, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -258,7 +258,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = {
|
|||
// });
|
||||
});
|
||||
|
||||
test('edit API batch edits', async function () {
|
||||
test.skip('edit API batch edits', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/155808
|
||||
const notebook = await openRandomNotebookDocument();
|
||||
const editor = await vscode.window.showNotebookDocument(notebook);
|
||||
|
||||
|
@ -284,7 +284,7 @@ const apiTestContentProvider: vscode.NotebookContentProvider = {
|
|||
assert.ok(cell.metadata.extraCellMetadata, `Test cell metdata not found`);
|
||||
});
|
||||
|
||||
test('edit API batch edits undo/redo', async function () {
|
||||
test.skip('edit API batch edits undo/redo', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/155825
|
||||
const notebook = await openRandomNotebookDocument();
|
||||
const editor = await vscode.window.showNotebookDocument(notebook);
|
||||
|
||||
|
|
19
package.json
19
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "code-oss-dev",
|
||||
"version": "1.70.0",
|
||||
"distro": "1a72c46622967eab6ea48516a2153c55d7e18e53",
|
||||
"distro": "990065ff739688c0d4ad94e172eeccd6bd5ef124",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
},
|
||||
|
@ -56,7 +56,8 @@
|
|||
"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",
|
||||
"extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci"
|
||||
"extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci",
|
||||
"webview-generate-csp-hash": "npx github:apaatsio/csp-hash-from-html csp-hash ./src/vs/workbench/contrib/webview/browser/pre/index.html"
|
||||
},
|
||||
"dependencies": {
|
||||
"@microsoft/1ds-core-js": "^3.2.2",
|
||||
|
@ -85,12 +86,12 @@
|
|||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "4.20.0-beta.13",
|
||||
"xterm-addon-search": "0.10.0-beta.2",
|
||||
"xterm-addon-serialize": "0.8.0-beta.2",
|
||||
"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.7",
|
||||
"xterm-headless": "4.20.0-beta.13",
|
||||
"xterm-addon-webgl": "0.13.0-beta.9",
|
||||
"xterm-headless": "4.20.0-beta.20",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
@ -124,7 +125,7 @@
|
|||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||
"@typescript-eslint/parser": "^5.10.0",
|
||||
"@vscode/telemetry-extractor": "^1.9.6",
|
||||
"@vscode/test-web": "^0.0.22",
|
||||
"@vscode/test-web": "^0.0.29",
|
||||
"ansi-colors": "^3.2.3",
|
||||
"asar": "^3.0.3",
|
||||
"chromium-pickle-js": "^0.2.0",
|
||||
|
@ -199,7 +200,7 @@
|
|||
"style-loader": "^1.3.0",
|
||||
"ts-loader": "^9.2.7",
|
||||
"tsec": "0.1.4",
|
||||
"typescript": "^4.8.0-dev.20220711",
|
||||
"typescript": "^4.8.0-dev.20220719",
|
||||
"typescript-formatter": "7.1.0",
|
||||
"underscore": "^1.12.1",
|
||||
"util": "^0.12.4",
|
||||
|
|
|
@ -24,12 +24,12 @@
|
|||
"vscode-proxy-agent": "^0.12.0",
|
||||
"vscode-regexpp": "^3.1.0",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "4.20.0-beta.13",
|
||||
"xterm-addon-search": "0.10.0-beta.2",
|
||||
"xterm-addon-serialize": "0.8.0-beta.2",
|
||||
"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.7",
|
||||
"xterm-headless": "4.20.0-beta.13",
|
||||
"xterm-addon-webgl": "0.13.0-beta.9",
|
||||
"xterm-headless": "4.20.0-beta.20",
|
||||
"yauzl": "^2.9.2",
|
||||
"yazl": "^2.4.3"
|
||||
},
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
"tas-client-umd": "0.1.6",
|
||||
"vscode-oniguruma": "1.6.1",
|
||||
"vscode-textmate": "7.0.1",
|
||||
"xterm": "4.20.0-beta.13",
|
||||
"xterm-addon-search": "0.10.0-beta.2",
|
||||
"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.7"
|
||||
"xterm-addon-webgl": "0.13.0-beta.9"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,22 +68,22 @@ vscode-textmate@7.0.1:
|
|||
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79"
|
||||
integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw==
|
||||
|
||||
xterm-addon-search@0.10.0-beta.2:
|
||||
version "0.10.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.2.tgz#a937d1e9a70fde8eeb7d1df485039b2d5fc1d707"
|
||||
integrity sha512-ybafAbX9V4sfkzmUsWmtfEYExG8jj73bTF9pEa/Lhd5q4bviW4LcFaw/n3lKHn/1tSgSVgzoD13u1ZaZR78SfQ==
|
||||
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-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-webgl@0.13.0-beta.7:
|
||||
version "0.13.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.7.tgz#9b514fa9792ced755c8321ddd06529f9912db2a1"
|
||||
integrity sha512-U3sKzkziZRwb13MyNp0eMwt5BWyM938epAv0ZOnI5Vjq06S2naS37GgO/FY+0eu5wuBKkZhT9lNDv7n8rMqd+w==
|
||||
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@4.20.0-beta.13:
|
||||
version "4.20.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.13.tgz#7526e19310daa56a11c26eb81a2706593c1378ec"
|
||||
integrity sha512-ovXGhvM/qDRNGlGO653WY1pxIZrnI4+ayVM7pCnxO/tRwUIym6LGS3NurYrhGYrXOjoLJZxQ4EjToAfHvAg2Gg==
|
||||
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==
|
||||
|
|
|
@ -788,35 +788,35 @@ wrappy@1:
|
|||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
xterm-addon-search@0.10.0-beta.2:
|
||||
version "0.10.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.2.tgz#a937d1e9a70fde8eeb7d1df485039b2d5fc1d707"
|
||||
integrity sha512-ybafAbX9V4sfkzmUsWmtfEYExG8jj73bTF9pEa/Lhd5q4bviW4LcFaw/n3lKHn/1tSgSVgzoD13u1ZaZR78SfQ==
|
||||
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-serialize@0.8.0-beta.2:
|
||||
version "0.8.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.2.tgz#f30656d4ff1570ac105bacffe443385666654598"
|
||||
integrity sha512-IDaRxO1zwjF9fDJp6u27Lv8852kEZ0HlbB0wLZbcIGZxDuPDLfvw8s/BV7f6MFB+mZq19CjyHGH4oPzZkc0rLQ==
|
||||
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-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-webgl@0.13.0-beta.7:
|
||||
version "0.13.0-beta.7"
|
||||
resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.7.tgz#9b514fa9792ced755c8321ddd06529f9912db2a1"
|
||||
integrity sha512-U3sKzkziZRwb13MyNp0eMwt5BWyM938epAv0ZOnI5Vjq06S2naS37GgO/FY+0eu5wuBKkZhT9lNDv7n8rMqd+w==
|
||||
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-headless@4.20.0-beta.13:
|
||||
version "4.20.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.13.tgz#a7d9d8837e3f78e106006cc94cf63ec13a9fd991"
|
||||
integrity sha512-y4YI+Ogv2R2I++tsyvx5Q7csAaN7mG2yMMMBb/u4dXnrFmSGYs/R8ZFkeHgAW4Ju4uI3Rizb+ZdwtN1uG043Rw==
|
||||
xterm-headless@4.20.0-beta.20:
|
||||
version "4.20.0-beta.20"
|
||||
resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.20.tgz#da2d8131b02d6f1e37f47cc17e578f2c2980fbb6"
|
||||
integrity sha512-JK4jUIiUH7TdzvMrpfDnbGxTuC4s7byjqnMHR8+gIpY8qCFjz0xcMFSbp+ZshxGwVyziI4jtJqTHZjFToT2/kw==
|
||||
|
||||
xterm@4.20.0-beta.13:
|
||||
version "4.20.0-beta.13"
|
||||
resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.13.tgz#7526e19310daa56a11c26eb81a2706593c1378ec"
|
||||
integrity sha512-ovXGhvM/qDRNGlGO653WY1pxIZrnI4+ayVM7pCnxO/tRwUIym6LGS3NurYrhGYrXOjoLJZxQ4EjToAfHvAg2Gg==
|
||||
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==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
Package: @@NAME@@
|
||||
Version: @@VERSION@@
|
||||
Section: devel
|
||||
Depends: libnss3 (>= 2:3.26), gnupg, apt, libxkbfile1, libsecret-1-0, libgtk-3-0 (>= 3.10.0), libxss1, libgbm1
|
||||
Depends: @@DEPENDS@@
|
||||
Recommends: @@RECOMMENDS@@
|
||||
Priority: optional
|
||||
Architecture: @@ARCHITECTURE@@
|
||||
Maintainer: Microsoft Corporation <vscode-linux@microsoft.com>
|
||||
|
|
|
@ -1738,56 +1738,81 @@ type HTMLElementAttributeKeys<T> = Partial<{ [K in keyof T]: T[K] extends Functi
|
|||
type ElementAttributes<T> = HTMLElementAttributeKeys<T> & Record<string, any>;
|
||||
type RemoveHTMLElement<T> = T extends HTMLElement ? never : T;
|
||||
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
||||
type ArrayToObj<T extends any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>;
|
||||
type ArrayToObj<T extends readonly any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>;
|
||||
type HHTMLElementTagNameMap = HTMLElementTagNameMap & { '': HTMLDivElement };
|
||||
|
||||
type TagToElement<T> = T extends `.${string}`
|
||||
? HTMLDivElement
|
||||
: T extends `#${string}`
|
||||
? HTMLDivElement
|
||||
: T extends `${infer TStart}#${string}`
|
||||
? TStart extends keyof HTMLElementTagNameMap
|
||||
? HTMLElementTagNameMap[TStart]
|
||||
type TagToElement<T> = T extends `${infer TStart}#${string}`
|
||||
? TStart extends keyof HHTMLElementTagNameMap
|
||||
? HHTMLElementTagNameMap[TStart]
|
||||
: HTMLElement
|
||||
: T extends `${infer TStart}.${string}`
|
||||
? TStart extends keyof HTMLElementTagNameMap
|
||||
? HTMLElementTagNameMap[TStart]
|
||||
? TStart extends keyof HHTMLElementTagNameMap
|
||||
? HHTMLElementTagNameMap[TStart]
|
||||
: HTMLElement
|
||||
: T extends keyof HTMLElementTagNameMap
|
||||
? HTMLElementTagNameMap[T]
|
||||
: HTMLElement;
|
||||
|
||||
type TagToElementAndId<TTag> = TTag extends `${infer TTag}@${infer TId}`
|
||||
? { element: TagToElement<TTag>; id: TId }
|
||||
: { element: TagToElement<TTag>; id: 'root' };
|
||||
|
||||
type TagToRecord<TTag> = TagToElementAndId<TTag> extends { element: infer TElement; id: infer TId }
|
||||
? Record<(TId extends string ? TId : never) | 'root', TElement>
|
||||
: 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\_])+))?/;
|
||||
|
||||
/**
|
||||
* A helper function to create nested dom nodes.
|
||||
*
|
||||
*
|
||||
* ```ts
|
||||
* private readonly htmlElements = h('div.code-view', [
|
||||
* h('div.title', { $: 'title' }),
|
||||
* const elements = h('div.code-view', [
|
||||
* h('div.title@title'),
|
||||
* h('div.container', [
|
||||
* h('div.gutter', { $: 'gutterDiv' }),
|
||||
* h('div', { $: 'editor' }),
|
||||
* h('div.gutter@gutterDiv'),
|
||||
* h('div@editor'),
|
||||
* ]),
|
||||
* ]);
|
||||
* private readonly editor = createEditor(this.htmlElements.editor);
|
||||
* const editor = createEditor(elements.editor);
|
||||
* ```
|
||||
*/
|
||||
export function h<TTag extends string>(
|
||||
tag: TTag
|
||||
): (Record<'root', TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
|
||||
export function h<TTag extends string, TId extends string>(
|
||||
tag: TTag,
|
||||
attributes: { $: TId } & Partial<ElementAttributes<TagToElement<TTag>>>
|
||||
): Record<TId | 'root', TagToElement<TTag>>;
|
||||
export function h<TTag extends string, T extends (HTMLElement | string | Record<string, HTMLElement>)[]>(
|
||||
tag: TTag,
|
||||
children: T
|
||||
): (ArrayToObj<T> & Record<'root', TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
|
||||
export function h<TTag extends string>(tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>): Record<'root', TagToElement<TTag>>;
|
||||
export function h<TTag extends string, TId extends string, T extends (HTMLElement | string | Record<string, HTMLElement>)[]>(
|
||||
tag: TTag,
|
||||
attributes: { $: TId } & Partial<ElementAttributes<TagToElement<TTag>>>,
|
||||
children: T
|
||||
): (ArrayToObj<T> & Record<TId, TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
|
||||
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):
|
||||
(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):
|
||||
(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> {
|
||||
let attributes: { $?: string } & Partial<ElementAttributes<HTMLElement>>;
|
||||
let children: (Record<string, HTMLElement> | HTMLElement)[] | undefined;
|
||||
|
@ -1800,25 +1825,29 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia
|
|||
children = args[1];
|
||||
}
|
||||
|
||||
const match = SELECTOR_REGEX.exec(tag);
|
||||
const match = H_REGEX.exec(tag);
|
||||
|
||||
if (!match) {
|
||||
if (!match || !match.groups) {
|
||||
throw new Error('Bad use of h');
|
||||
}
|
||||
|
||||
const tagName = match[1] || 'div';
|
||||
const tagName = match.groups['tag'] || 'div';
|
||||
const el = document.createElement(tagName);
|
||||
|
||||
if (match[3]) {
|
||||
el.id = match[3];
|
||||
if (match.groups['id']) {
|
||||
el.id = match.groups['id'];
|
||||
}
|
||||
|
||||
if (match[4]) {
|
||||
el.className = match[4].replace(/\./g, ' ').trim();
|
||||
if (match.groups['class']) {
|
||||
el.className = match.groups['class'].replace(/\./g, ' ').trim();
|
||||
}
|
||||
|
||||
const result: Record<string, HTMLElement> = {};
|
||||
|
||||
if (match.groups['name']) {
|
||||
result[match.groups['name']] = el;
|
||||
}
|
||||
|
||||
if (children) {
|
||||
for (const c of children) {
|
||||
if (c instanceof HTMLElement) {
|
||||
|
@ -1833,10 +1862,6 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia
|
|||
}
|
||||
|
||||
for (const [key, value] of Object.entries(attributes)) {
|
||||
if (key === '$') {
|
||||
result[value] = el;
|
||||
continue;
|
||||
}
|
||||
if (key === 'style') {
|
||||
for (const [cssKey, cssValue] of Object.entries(value)) {
|
||||
el.style.setProperty(
|
||||
|
|
|
@ -9,8 +9,6 @@ import { DomEmitter } from 'vs/base/browser/event';
|
|||
import { createElement, FormattedTextRenderOptions } from 'vs/base/browser/formattedTextRenderer';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { raceCancellation } from 'vs/base/common/async';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IMarkdownString, escapeDoubleQuotes, parseHrefAndDimensions, removeMarkdownEscapes } from 'vs/base/common/htmlContent';
|
||||
|
@ -44,8 +42,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
const disposables = new DisposableStore();
|
||||
let isDisposed = false;
|
||||
|
||||
const cts = disposables.add(new CancellationTokenSource());
|
||||
|
||||
const element = createElement(options);
|
||||
|
||||
const _uriMassage = function (part: string): string {
|
||||
|
@ -96,11 +92,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
return uri.toString();
|
||||
};
|
||||
|
||||
// signal to code-block render that the
|
||||
// element has been created
|
||||
let signalInnerHTML: () => void;
|
||||
const withInnerHTML = new Promise<void>(c => signalInnerHTML = c);
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
||||
renderer.image = (href: string, title: string, text: string) => {
|
||||
|
@ -146,24 +137,14 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
return `<p>${text}</p>`;
|
||||
};
|
||||
|
||||
// Will collect [id, renderedElement] tuples
|
||||
const codeBlocks: Promise<[string, HTMLElement]>[] = [];
|
||||
|
||||
if (options.codeBlockRenderer) {
|
||||
renderer.code = (code, lang) => {
|
||||
const value = options.codeBlockRenderer!(lang ?? '', code);
|
||||
// when code-block rendering is async we return sync
|
||||
// but update the node with the real result later.
|
||||
const id = defaultGenerator.nextId();
|
||||
raceCancellation(Promise.all([value, withInnerHTML]), cts.token).then(values => {
|
||||
if (!isDisposed && values) {
|
||||
const span = element.querySelector<HTMLDivElement>(`div[data-code="${id}"]`);
|
||||
if (span) {
|
||||
DOM.reset(span, values[0]);
|
||||
}
|
||||
options.asyncRenderCallback?.();
|
||||
}
|
||||
}).catch(() => {
|
||||
// ignore
|
||||
});
|
||||
|
||||
const value = options.codeBlockRenderer!(lang ?? '', code);
|
||||
codeBlocks.push(value.then(element => [id, element]));
|
||||
return `<div class="code" data-code="${id}">${escape(code)}</div>`;
|
||||
};
|
||||
}
|
||||
|
@ -277,8 +258,22 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
|
||||
element.innerHTML = sanitizeRenderedMarkdown(markdown, markdownHtmlDoc.body.innerHTML) as unknown as string;
|
||||
|
||||
// signal that async code blocks can be now be inserted
|
||||
signalInnerHTML!();
|
||||
if (codeBlocks.length > 0) {
|
||||
Promise.all(codeBlocks).then((tuples) => {
|
||||
if (isDisposed) {
|
||||
return;
|
||||
}
|
||||
const renderedElements = new Map(tuples);
|
||||
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);
|
||||
}
|
||||
}
|
||||
options.asyncRenderCallback?.();
|
||||
});
|
||||
}
|
||||
|
||||
// signal size changes for image tags
|
||||
if (options.asyncRenderCallback) {
|
||||
|
@ -294,7 +289,6 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
|||
element,
|
||||
dispose: () => {
|
||||
isDisposed = true;
|
||||
cts.cancel();
|
||||
disposables.dispose();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -49,7 +49,11 @@
|
|||
.monaco-button-dropdown.disabled > .monaco-button.disabled,
|
||||
.monaco-button-dropdown.disabled > .monaco-button.disabled:focus,
|
||||
.monaco-button-dropdown.disabled > .monaco-button-dropdown-separator {
|
||||
opacity: 0.4;
|
||||
opacity: 0.4 !important;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown > .monaco-button.monaco-text-button {
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown .monaco-button-dropdown-separator {
|
||||
|
@ -62,6 +66,10 @@
|
|||
width: 1px;
|
||||
}
|
||||
|
||||
.monaco-button-dropdown > .monaco-button.monaco-dropdown-button {
|
||||
border-left-width: 0 !important;
|
||||
}
|
||||
|
||||
.monaco-description-button {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
|
@ -316,6 +316,16 @@ export class ButtonWithDropdown extends Disposable implements IButton {
|
|||
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() ?? '';
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -424,6 +424,7 @@ class TypeNavigationController<T> implements IDisposable {
|
|||
private list: List<T>,
|
||||
private view: ListView<T>,
|
||||
private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider<T>,
|
||||
private keyboardNavigationEventFilter: IKeyboardNavigationEventFilter,
|
||||
private delegate: IKeyboardNavigationDelegate
|
||||
) {
|
||||
this.updateOptions(list.options);
|
||||
|
@ -448,12 +449,15 @@ class TypeNavigationController<T> implements IDisposable {
|
|||
return;
|
||||
}
|
||||
|
||||
let typing = false;
|
||||
|
||||
const onChar = this.enabledDisposables.add(Event.chain(this.enabledDisposables.add(new DomEmitter(this.view.domNode, 'keydown')).event))
|
||||
.filter(e => !isInputElement(e.target as HTMLElement))
|
||||
.filter(() => this.mode === TypeNavigationMode.Automatic || this.triggered)
|
||||
.map(event => new StandardKeyboardEvent(event))
|
||||
.filter(e => typing || this.keyboardNavigationEventFilter(e))
|
||||
.filter(e => this.delegate.mightProducePrintableCharacter(e))
|
||||
.forEach(e => { e.preventDefault(); e.stopPropagation(); })
|
||||
.forEach(stopEvent)
|
||||
.map(event => event.browserEvent.key)
|
||||
.event;
|
||||
|
||||
|
@ -463,6 +467,9 @@ class TypeNavigationController<T> implements IDisposable {
|
|||
onInput(this.onInput, this, this.enabledDisposables);
|
||||
onClear(this.onClear, this, this.enabledDisposables);
|
||||
|
||||
onChar(() => typing = true, undefined, this.enabledDisposables);
|
||||
onClear(() => typing = false, undefined, this.enabledDisposables);
|
||||
|
||||
this.enabled = true;
|
||||
this.triggered = false;
|
||||
}
|
||||
|
@ -919,6 +926,10 @@ export class DefaultStyleController implements IStyleController {
|
|||
}
|
||||
}
|
||||
|
||||
export interface IKeyboardNavigationEventFilter {
|
||||
(e: StandardKeyboardEvent): boolean;
|
||||
}
|
||||
|
||||
export interface IListOptionsUpdate extends IListViewOptionsUpdate {
|
||||
readonly typeNavigationEnabled?: boolean;
|
||||
readonly typeNavigationMode?: TypeNavigationMode;
|
||||
|
@ -934,6 +945,7 @@ export interface IListOptions<T> extends IListOptionsUpdate {
|
|||
readonly multipleSelectionController?: IMultipleSelectionController<T>;
|
||||
readonly styleController?: (suffix: string) => IStyleController;
|
||||
readonly accessibilityProvider?: IListAccessibilityProvider<T>;
|
||||
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
|
||||
|
||||
// list view options
|
||||
readonly useShadows?: boolean;
|
||||
|
@ -1373,7 +1385,7 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
|||
|
||||
if (_options.keyboardNavigationLabelProvider) {
|
||||
const delegate = _options.keyboardNavigationDelegate || DefaultKeyboardNavigationDelegate;
|
||||
this.typeNavigationController = new TypeNavigationController(this, this.view, _options.keyboardNavigationLabelProvider, delegate);
|
||||
this.typeNavigationController = new TypeNavigationController(this, this.view, _options.keyboardNavigationLabelProvider, _options.keyboardNavigationEventFilter ?? (() => true), delegate);
|
||||
this.disposables.add(this.typeNavigationController);
|
||||
}
|
||||
|
||||
|
@ -1785,6 +1797,10 @@ export class List<T> implements ISpliceable<T>, IThemable, IDisposable {
|
|||
return this.view.domNode;
|
||||
}
|
||||
|
||||
getElementID(index: number): string {
|
||||
return this.view.getElementDomId(index);
|
||||
}
|
||||
|
||||
style(styles: IListStyles): void {
|
||||
this.styleController.style(styles);
|
||||
}
|
||||
|
|
|
@ -684,10 +684,10 @@ export enum TreeFindMode {
|
|||
|
||||
class FindWidget<T, TFilterData> extends Disposable {
|
||||
|
||||
private readonly elements = h('div.monaco-tree-type-filter', [
|
||||
h('div.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper', { $: 'grab' }),
|
||||
h('div.monaco-tree-type-filter-input', { $: 'findInput' }),
|
||||
h('div.monaco-tree-type-filter-actionbar', { $: 'actionbar' }),
|
||||
private readonly elements = h('.monaco-tree-type-filter', [
|
||||
h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab'),
|
||||
h('.monaco-tree-type-filter-input@findInput'),
|
||||
h('.monaco-tree-type-filter-actionbar@actionbar'),
|
||||
]);
|
||||
|
||||
set mode(mode: TreeFindMode) {
|
||||
|
@ -1003,10 +1003,6 @@ function asTreeContextMenuEvent<T>(event: IListContextMenuEvent<ITreeNode<T, any
|
|||
};
|
||||
}
|
||||
|
||||
export interface IKeyboardNavigationEventFilter {
|
||||
(e: StandardKeyboardEvent): boolean;
|
||||
}
|
||||
|
||||
export interface IAbstractTreeOptionsUpdate extends ITreeRendererOptions {
|
||||
readonly multipleSelectionSupport?: boolean;
|
||||
readonly typeNavigationEnabled?: boolean;
|
||||
|
@ -1025,7 +1021,6 @@ export interface IAbstractTreeOptions<T, TFilterData = void> extends IAbstractTr
|
|||
readonly collapseByDefault?: boolean; // defaults to false
|
||||
readonly filter?: ITreeFilter<T, TFilterData>;
|
||||
readonly dnd?: ITreeDragAndDrop<T>;
|
||||
readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter;
|
||||
readonly additionalScrollHeight?: number;
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue